Initial commit

This commit is contained in:
Jonas Karneboge 2026-09-22 18:35:43 +02:00
commit 3cba772836
1341 changed files with 532924 additions and 0 deletions

BIN
SOMS-7T/.RData Normal file

Binary file not shown.

1
SOMS-7T/.Rprofile Normal file
View file

@ -0,0 +1 @@
source("renv/activate.R")

13
SOMS-7T/SOMS-7T.Rproj Normal file
View file

@ -0,0 +1,13 @@
Version: 1.0
RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default
EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8
RnwWeave: Sweave
LaTeX: pdfLaTeX

795
SOMS-7T/app.R Normal file
View file

@ -0,0 +1,795 @@
# Präambel ####
PFAD_DOWNLOAD_SKRIPT = "../API/get_data_soms7t.R" # liefert: daten_soms7t
PFAD_PSEUDONYM_SKRIPT = "../get_pseudo.R" # liefert: pseudo
AKZENT_FARBE = "#8B2635"
PFAD_NORM_B1 = "normen/soms7t_norm_b1_patienten.csv"
PFAD_NORM_B2 = "normen/soms7t_norm_b2_beschwerdenanzahl_gesunde.csv"
PFAD_NORM_B3 = "normen/soms7t_norm_b3_dsmiv_gesunde.csv"
PFAD_NORM_B4 = "normen/soms7t_norm_b4_intensitaet_gesunde.csv"
PFAD_NORM_B5 = "normen/soms7t_norm_b5_nach_staerke_gesunde.csv"
SOMS7T_DISCLAIMER = paste0(
"Diese Auswertung ist ein Hilfsmittel fuer klinisches Fachpersonal und ersetzt ",
"keine klinische Diagnose. Die Interpretation obliegt der behandelnden Person."
)
SOMS7T_KRITERIUM_A_TEXT = paste0(
"Bei Personen, die im SOMS-7T mindestens 3 Items als »mittel ausgeprägt« ",
"bewertet bzw. einen Prozentrang > 75 haben, besteht ein hohes Risiko auf ein ",
"vorliegendes Somatisierungssyndrom."
)
SOMS7T_KRITERIUM_B_HINWEIS = paste0(
"Das Manual benennt hierzu einen Prozentrang > 75, ohne eindeutig zu spezifizieren, ",
"auf welchen der beiden Kennwerte sich dieser bezieht - beide werden daher zur ",
"Einordnung angezeigt."
)
# DSM-IV-33-Item-Liste (gleiche Itemliste wie in der SOMS-2-Schwester-App).
SOMS7T_DSMIV_ITEMS = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 16, 20, 32, 34, 35, 36, 37,
38, 39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 53)
SOMS7T_STUFEN_TEXTE = c("gar nicht", "leicht", "mittelmäßig", "stark", "sehr stark")
library(shiny)
library(dplyr)
library(ggplot2)
library(haven)
library(officer)
# Infrastruktur ####
APP_VERZEICHNIS = normalizePath(getwd())
absPath = function(pfad) {
if (grepl("^([A-Za-z]:[/\\\\]|/)", pfad)) return(pfad)
file.path(APP_VERZEICHNIS, pfad)
}
PFAD_DOWNLOAD_SKRIPT = normalizePath(absPath(PFAD_DOWNLOAD_SKRIPT), mustWork = FALSE)
PFAD_PSEUDONYM_SKRIPT = normalizePath(absPath(PFAD_PSEUDONYM_SKRIPT), mustWork = FALSE)
PFAD_NORM_B1 = normalizePath(absPath(PFAD_NORM_B1), mustWork = FALSE)
PFAD_NORM_B2 = normalizePath(absPath(PFAD_NORM_B2), mustWork = FALSE)
PFAD_NORM_B3 = normalizePath(absPath(PFAD_NORM_B3), mustWork = FALSE)
PFAD_NORM_B4 = normalizePath(absPath(PFAD_NORM_B4), mustWork = FALSE)
PFAD_NORM_B5 = normalizePath(absPath(PFAD_NORM_B5), mustWork = FALSE)
# Helper ####
# In formr-Exporten stehen Markdown-Sternchen im Itemwortlaut und in Choice-Texten.
strip_stars = function(x) {
if (is.null(x) || length(x) == 0) return(x)
gsub("\\*\\*", "", as.character(x))
}
# Label-Text einer der fuenf bekannten Kategorien zuordnen. Laengste Kategorienamen
# zuerst pruefen, da "stark" sonst als Teilstring von "sehr stark" faelschlich zuerst
# matchen wuerde.
soms7t_kategorie_index = function(label_text) {
if (is.null(label_text) || length(label_text) == 0 || is.na(label_text[1]))
return(NA_integer_)
txt = tolower(trimws(strip_stars(label_text[1])))
reihenfolge = order(nchar(SOMS7T_STUFEN_TEXTE), decreasing = TRUE)
for (idx in reihenfolge) {
if (grepl(SOMS7T_STUFEN_TEXTE[idx], txt, fixed = TRUE)) return(idx - 1L)
}
NA_integer_
}
# Stufe (0-4) immer aus dem labels-Attribut der ORIGINAL-Spalte ableiten, nie aus dem
# formr-Rohwert 1-5 direkt, da die Kodierung je nach Setup variieren kann. Stufe = Position
# der zugeordneten Kategorie in der festen Reihenfolge "gar nicht".."sehr stark", nicht
# die numerische Position des Rohwerts (robust gegenueber abweichender formr-Kodierung).
soms7t_get_level = function(original_col, wert) {
if (is.null(wert) || length(wert) == 0 || is.na(wert[1])) return(0L)
lbl = attr(original_col, "labels")
if (is.null(lbl) || length(lbl) == 0) {
# Fallback ohne labels-Attribut: 1-basierte Kodierung annehmen.
return(max(0L, min(4L, as.integer(round(as.numeric(wert[1]))) - 1L)))
}
wert_num = as.numeric(wert[1])
pos = which(as.vector(lbl) == wert_num)
if (length(pos) == 0) return(0L)
kat = soms7t_kategorie_index(names(lbl)[pos[1]])
if (is.na(kat)) return(0L)
kat
}
# label-Attribut der Spalte (Fragetext) lesen und bereinigen. Entfernt neben den
# Markdown-Sternchen auch formr-Nummerierungsartefakte am Anfang (z.B. "11\. " oder
# "11. "), die aus dem escapeten Markdown-Listenpunkt im Label-Text stammen.
soms7t_item_text = function(original_col) {
txt = attr(original_col, "label")
if (is.null(txt) || length(txt) == 0 || is.na(txt[1])) return(NA_character_)
txt = strip_stars(txt[1])
txt = sub("^\\s*\\d+\\\\?[.)]\\s*", "", txt)
trimws(txt)
}
# Label-Text (z.B. Geschlecht) aus dem labels-Attribut lesen - nie hartkodiert 1/2.
soms7t_get_label_text = function(original_col, wert) {
if (is.null(wert) || length(wert) == 0 || is.na(wert[1])) return(NA_character_)
lbl = attr(original_col, "labels")
if (!is.null(lbl) && length(lbl) > 0) {
pos = which(as.vector(lbl) == as.numeric(wert[1]))
if (length(pos) > 0) return(trimws(strip_stars(names(lbl)[pos[1]])))
}
NA_character_
}
soms7t_geschlecht_spalte = function(geschlecht_text) {
if (is.na(geschlecht_text)) return("gesamt")
if (grepl("weiblich", geschlecht_text, ignore.case = TRUE)) return("frauen")
if (grepl("männlich", geschlecht_text, ignore.case = TRUE)) return("maenner")
"gesamt"
}
# B-2/B-4: Normtabellen mit rohwert_min/rohwert_max-Bereichen (mehrere Rohwerte pro Zeile
# moeglich). Rohwert oberhalb des hoechsten Tabelleneintrags -> PR 100.
pr_lookup_range = function(tabelle, rohwert, spalte,
min_col = "rohwert_min", max_col = "rohwert_max") {
if (is.na(rohwert)) return(NA_real_)
if (rohwert > max(tabelle[[max_col]], na.rm = TRUE)) return(100)
zeile = tabelle[tabelle[[min_col]] <= rohwert & tabelle[[max_col]] >= rohwert, ]
if (nrow(zeile) == 0) return(NA_real_)
as.numeric(zeile[[spalte]][1])
}
# B-1/B-3: Normtabellen mit exaktem rohwert pro Zeile.
pr_lookup_exact = function(tabelle, rohwert, spalte, rohwert_col = "rohwert") {
if (is.na(rohwert)) return(NA_real_)
if (rohwert > max(tabelle[[rohwert_col]], na.rm = TRUE)) return(100)
zeile = tabelle[tabelle[[rohwert_col]] == rohwert, ]
if (nrow(zeile) == 0) return(NA_real_)
as.numeric(zeile[[spalte]][1])
}
# B-5: PR der Beschwerdenanzahl nach Symptomstaerke-Schwelle. Leere Zellen bedeuten
# "bei dieser Schwelle nicht erreichbar" -> NA, kein erzwungener Wert.
pr_lookup_b5 = function(tabelle, beschwerdenanzahl, spalte = "staerke_2_4") {
if (is.na(beschwerdenanzahl)) return(NA_real_)
if (beschwerdenanzahl > max(tabelle[["beschwerdenanzahl"]], na.rm = TRUE)) return(100)
zeile = tabelle[tabelle[["beschwerdenanzahl"]] == beschwerdenanzahl, ]
if (nrow(zeile) == 0) return(NA_real_)
wert = suppressWarnings(as.numeric(zeile[[spalte]][1]))
if (is.na(wert)) return(NA_real_)
wert
}
# Dichotomisierung: Stufe 0-1 -> 0 Punkte, Stufe 2-4 -> 1 Punkt.
soms7t_berechne_beschwerdenanzahl = function(stufen_vektor) {
sum(stufen_vektor >= 2, na.rm = TRUE)
}
# Keine Dichotomisierung: Rohsumme der Stufenwerte (0-4).
soms7t_berechne_intensitaet = function(stufen_vektor) {
sum(stufen_vektor, na.rm = TRUE)
}
soms7t_kriterium_a = function(beschwerdenanzahl) {
isTRUE(beschwerdenanzahl >= 3)
}
soms7t_format_pr = function(pr) {
if (is.null(pr) || length(pr) == 0 || is.na(pr)) return("—")
paste0("PR ", format(round(pr, 1), nsmall = 1))
}
# Vier Stufen-Badge-Klassen (gruen -> dunkelrot), 1:1 aus der BDI-II-App uebernommen.
# Rohwert wird per min(stufe, 3L) gedeckelt - Stufe 3 "stark" und Stufe 4 "sehr stark"
# teilen sich damit die dunkelste Badge-Farbe (nur 4 Klassen vorgesehen, siehe Abschnitt 5).
soms7t_badge_klasse = function(stufe) {
paste0("stufe-badge-", min(max(stufe, 0L), 3L))
}
SOMS7T_BADGE_WORD_FARBEN = list(
"0" = list(bg = "#C8E6C9", text = "#1B5E20"),
"1" = list(bg = "#FFCDD2", text = "#B71C1C"),
"2" = list(bg = "#EF9A9A", text = "#7B0000"),
"3" = list(bg = "#B71C1C", text = "#FFFFFF")
)
# Datenaufbereitung ####
.soms7t_norm_pfade = c(
"B-1 (Patientennorm)" = PFAD_NORM_B1,
"B-2 (Beschwerdenanzahl, Gesunde)" = PFAD_NORM_B2,
"B-3 (DSM-IV, Gesunde)" = PFAD_NORM_B3,
"B-4 (Intensitaetsindex, Gesunde)" = PFAD_NORM_B4,
"B-5 (nach Symptomstaerke, Gesunde)" = PFAD_NORM_B5
)
.soms7t_fehlende_normen = names(.soms7t_norm_pfade)[!file.exists(.soms7t_norm_pfade)]
if (length(.soms7t_fehlende_normen) > 0) {
stop(
"SOMS-7T: Folgende Normtabellen-CSVs fehlen im Ordner 'normen/':\n",
paste0(" - ", .soms7t_fehlende_normen, ": ",
.soms7t_norm_pfade[.soms7t_fehlende_normen], collapse = "\n"),
"\nBitte die fehlenden Dateien ablegen und die App neu starten."
)
}
norm_b1 = read.csv(PFAD_NORM_B1, stringsAsFactors = FALSE)
norm_b2 = read.csv(PFAD_NORM_B2, stringsAsFactors = FALSE)
norm_b3 = read.csv(PFAD_NORM_B3, stringsAsFactors = FALSE)
norm_b4 = read.csv(PFAD_NORM_B4, stringsAsFactors = FALSE)
norm_b5 = read.csv(PFAD_NORM_B5, stringsAsFactors = FALSE)
# UI ####
app_css = "
body { font-family: 'Segoe UI', Arial, sans-serif; background: #f5f5f5; }
.app-header {
background: #8B2635; color: white; padding: 18px 24px 14px;
margin-bottom: 20px; border-radius: 0 0 6px 6px;
}
.app-header h2 { margin: 0; font-size: 1.5rem; font-weight: 600; }
.app-header p { margin: 4px 0 0; opacity: 0.85; font-size: 0.9rem; }
.input-panel {
background: white; border-radius: 6px; padding: 16px 20px;
margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.12);
display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
}
.input-panel .form-group { margin-bottom: 0; }
.input-panel label { font-weight: 600; color: #333; }
.btn-laden {
background: #8B2635 !important; color: white !important;
border: none !important; border-radius: 4px !important;
padding: 8px 20px !important; font-weight: 600 !important; cursor: pointer;
}
.btn-laden:hover { background: #6d1e29 !important; }
.alert-fehler {
background: #FFEBEE; border-left: 5px solid #C62828;
padding: 12px 16px; border-radius: 4px; color: #B71C1C;
margin-bottom: 12px; font-weight: 500;
}
.alert-warnung {
background: #FFF3E0; border-left: 5px solid #E65100;
padding: 10px 16px; border-radius: 4px; color: #BF360C;
margin-bottom: 12px; font-size: 0.93em; font-weight: 500;
}
.abschnitt-karte {
background: white; border-radius: 6px; padding: 20px 24px;
margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.12);
}
.abschnitt-titel {
color: #8B2635; font-size: 1.15rem; font-weight: 700;
border-bottom: 2px solid #8B2635; padding-bottom: 8px; margin-bottom: 14px;
}
.meta-block { margin-bottom: 10px; color: #555; font-size: 0.95em; }
.meta-block strong { color: #222; }
.kennwert-block {
display: flex; gap: 28px; flex-wrap: wrap; align-items: flex-start;
padding: 10px 0;
}
.kennwert-zahl { font-size: 2.0rem; font-weight: 800; color: #8B2635; }
.kennwert-label { color: #555; font-size: 0.88em; margin-top: 2px; }
.kennwert-pr { font-size: 0.95em; color: #333; margin-top: 4px; }
.kennwert-pr-patient { font-size: 0.85em; color: #777; margin-top: 2px; font-style: italic; }
.kriterium-box {
border-radius: 6px; padding: 14px 18px; margin: 10px 0;
border-left: 5px solid;
}
.kriterium-erfuellt { background: #FFEBEE; border-color: #EF9A9A; color: #B71C1C; }
.kriterium-nicht { background: #F5F5F5; border-color: #9E9E9E; color: #424242; }
.kriterium-titel { font-weight: 700; font-size: 1.0rem; margin-bottom: 6px; }
.kriterium-text { font-size: 0.92em; line-height: 1.5; }
.kriterium-b-werte {
display: flex; gap: 24px; margin-top: 8px; flex-wrap: wrap; font-size: 0.92em;
}
.kriterium-b-hinweis {
font-size: 0.82em; color: #777; font-style: italic; margin-top: 8px;
}
.item-zeile {
display: flex; align-items: flex-start; gap: 10px;
padding: 7px 0; border-bottom: 1px solid #F0F0F0;
}
.item-nr { font-weight: 600; color: #8B2635; min-width: 26px; flex-shrink: 0; }
.item-text { flex: 1; color: #333; font-size: 0.92em; }
.stufe-badge {
border-radius: 4px; padding: 2px 9px; font-weight: 700;
font-size: 0.82em; white-space: nowrap; display: inline-block; flex-shrink: 0;
}
.stufe-badge-0 { background-color: #C8E6C9; color: #1B5E20; }
.stufe-badge-1 { background-color: #FFCDD2; color: #B71C1C; }
.stufe-badge-2 { background-color: #EF9A9A; color: #7B0000; }
.stufe-badge-3 { background-color: #B71C1C; color: white; }
.disclaimer-zeile {
font-size: 0.82em; color: #777; font-style: italic;
margin-top: 10px; border-top: 1px solid rgba(0,0,0,.1); padding-top: 8px;
}
"
app_css = gsub("#8B2635", AKZENT_FARBE, app_css, fixed = TRUE)
ui = fluidPage(
tags$head(
tags$meta(charset = "UTF-8"),
tags$style(HTML(app_css))
),
div(class = "app-header",
tags$h2("SOMS-7T Screening für somatoforme Störungen (7-Tage-Version)"),
tags$p("Beschwerdenanzahl & Intensitätsindex mit Normvergleich")
),
div(class = "container-fluid",
div(class = "input-panel",
div(style = "min-width: 360px; white-space: nowrap;",
textInput("pseudonym",
label = tagList(
"Pseudonym",
tags$span(style = "font-weight: normal; font-style: italic; font-size: 0.78em; color: #888; margin-left: 4px; white-space: nowrap;",
"optional, hat Vorrang vor Chiffre")
),
placeholder = "optional", width = "340px")
),
div(style = "min-width: 200px;",
textInput("chiffre", label = "Patientenchiffre",
placeholder = "z.B. P000123", width = "100%")
),
actionButton("btn_suchen", "Auswerten", class = "btn btn-primary btn-laden"),
div(style = "margin-left: auto;",
downloadButton("download_word", "Word-Export (.docx)")
)
),
uiOutput("fehler_ui"),
uiOutput("warnung_ui"),
uiOutput("ergebnis_ui")
)
)
# Word-Export ####
erstelle_soms7t_docx = function(erg) {
doc = read_docx()
fp_titel = fp_text(color = AKZENT_FARBE, bold = TRUE, font.size = 18)
fp_abschnitt = fp_text(color = AKZENT_FARBE, bold = TRUE, font.size = 13)
fp_label = fp_text(bold = TRUE, font.size = 11)
fp_normal = fp_text(font.size = 11)
fp_disclaimer = fp_text(font.size = 9, italic = TRUE, color = "#777777")
doc = body_add_fpar(doc, fpar(ftext("SOMS-7T — Auswertung", fp_titel)))
doc = body_add_fpar(doc, fpar(
ftext("Chiffre: ", fp_label),
ftext(erg$chiffre, fp_normal),
ftext(" Datum: ", fp_label),
ftext(erg$datum_str, fp_normal),
ftext(" Geschlecht: ", fp_label),
ftext(if (is.na(erg$geschlecht_text)) "k. A." else erg$geschlecht_text, fp_normal)
))
if (!is.null(erg$info_mehrere)) {
doc = body_add_fpar(doc, fpar(
ftext(erg$info_mehrere, fp_text(font.size = 10, italic = TRUE, color = "#555555"))
))
}
doc = body_add_par(doc, "", style = "Normal")
doc = body_add_fpar(doc, fpar(ftext("Beschwerdenanzahl", fp_abschnitt)))
doc = body_add_fpar(doc, fpar(
ftext("Rohwert (alle 53 Items, Stufe ≥2): ", fp_label),
ftext(paste0(erg$beschwerden_gesamt, " ",
soms7t_format_pr(erg$pr_beschwerden_gesund),
" (Gesunden-Norm, geschlechtsgetrennt)"), fp_normal)
))
doc = body_add_fpar(doc, fpar(
ftext("Vergleichswert: psychosomatische Patienten (Veraenderungsmessung): ",
fp_text(font.size = 10, italic = TRUE, color = "#555555")),
ftext(soms7t_format_pr(erg$pr_beschwerden_patient),
fp_text(font.size = 10, italic = TRUE, color = "#555555"))
))
doc = body_add_fpar(doc, fpar(
ftext("Rohwert DSM-IV-Liste (33 Items, Stufe ≥2): ", fp_label),
ftext(paste0(erg$beschwerden_dsmiv, " ", soms7t_format_pr(erg$pr_dsmiv)), fp_normal)
))
doc = body_add_par(doc, "", style = "Normal")
doc = body_add_fpar(doc, fpar(ftext("Intensitätsindex", fp_abschnitt)))
doc = body_add_fpar(doc, fpar(
ftext("Rohwert (Summe aller 53 Item-Stufen, 0-212): ", fp_label),
ftext(paste0(erg$intensitaet, " ",
soms7t_format_pr(erg$pr_intensitaet_gesund),
" (Gesunden-Norm, geschlechtsgetrennt)"), fp_normal)
))
doc = body_add_fpar(doc, fpar(
ftext("Vergleichswert: psychosomatische Patienten (Veraenderungsmessung): ",
fp_text(font.size = 10, italic = TRUE, color = "#555555")),
ftext(soms7t_format_pr(erg$pr_intensitaet_patient),
fp_text(font.size = 10, italic = TRUE, color = "#555555"))
))
doc = body_add_par(doc, "", style = "Normal")
doc = body_add_fpar(doc, fpar(ftext("Screening-Kriterium", fp_abschnitt)))
if (erg$kriterium_a) {
doc = body_add_fpar(doc, fpar(ftext(
paste0("Screening-Kriterium A erfuellt (≥3 Items mittelmaessig+): ",
erg$beschwerden_gesamt, " Items."),
fp_text(bold = TRUE, font.size = 11, color = "#B71C1C")
)))
doc = body_add_fpar(doc, fpar(ftext(SOMS7T_KRITERIUM_A_TEXT,
fp_text(font.size = 10, color = "#B71C1C"))))
} else {
doc = body_add_fpar(doc, fpar(ftext(
paste0("Screening-Kriterium A nicht erfuellt (", erg$beschwerden_gesamt,
" von 3 Items mittelmaessig+)."),
fp_text(bold = TRUE, font.size = 11, color = "#424242")
)))
}
doc = body_add_fpar(doc, fpar(
ftext("Kriterium B (Zusatzinformation) - PR Beschwerdenanzahl (Schwelle 2-4): ",
fp_label),
ftext(soms7t_format_pr(erg$pr_staerke_2_4), fp_normal),
ftext(" PR Intensitätsindex: ", fp_label),
ftext(soms7t_format_pr(erg$pr_intensitaet_gesund), fp_normal)
))
doc = body_add_fpar(doc, fpar(ftext(SOMS7T_KRITERIUM_B_HINWEIS,
fp_text(font.size = 9, italic = TRUE, color = "#777777"))))
doc = body_add_par(doc, "", style = "Normal")
doc = body_add_fpar(doc, fpar(ftext("Items mit Stufe ≥ mittelmäßig", fp_abschnitt)))
if (nrow(erg$items_liste) == 0) {
doc = body_add_fpar(doc, fpar(ftext("Keine Items mit Stufe ≥2 vorhanden.", fp_normal)))
} else {
for (r in seq_len(nrow(erg$items_liste))) {
zeile = erg$items_liste[r, ]
badge_key = as.character(min(max(zeile$stufe, 0L), 3L))
farben = SOMS7T_BADGE_WORD_FARBEN[[badge_key]]
fp_badge = fp_text(color = farben$text, bold = TRUE,
shading.color = farben$bg, font.size = 10)
doc = body_add_fpar(doc, fpar(
ftext(paste0(zeile$nummer, ". ", zeile$text, " "), fp_normal),
ftext(paste0(" ", SOMS7T_STUFEN_TEXTE[zeile$stufe + 1L], " "), fp_badge)
))
}
}
doc = body_add_par(doc, "", style = "Normal")
doc = body_add_fpar(doc, fpar(ftext(SOMS7T_DISCLAIMER, fp_disclaimer)))
doc
}
# Server ####
server = function(input, output, session) {
observe({
query = parseQueryString(session$clientData$url_search)
if (!is.null(query$pseudonym) && nchar(trimws(query$pseudonym)) > 0) {
updateTextInput(session, "pseudonym", value = trimws(query$pseudonym))
}
})
observe({
query = parseQueryString(session$clientData$url_search)
if (!is.null(query$chiffre) && nchar(trimws(query$chiffre)) > 0) {
updateTextInput(session, "chiffre", value = toupper(trimws(query$chiffre)))
}
})
ergebnis_r = eventReactive(input$btn_suchen, {
chiffre = toupper(trimws(input$chiffre))
if (nchar(trimws(input$pseudonym)) == 0 && nchar(chiffre) == 0)
return(list(error = "Bitte eine Patientenchiffre eingeben."))
if (!(nchar(trimws(input$pseudonym)) > 0 || grepl("^[A-Z][0-9]{6}$", chiffre)))
return(list(error = paste0(
"Ungültige Chiffre. Erwartet: ein Großbuchstabe + 6 Ziffern (z.B. P000123).")))
if (!file.exists(PFAD_DOWNLOAD_SKRIPT))
return(list(error = paste0(
"Download-Skript nicht gefunden:\n", PFAD_DOWNLOAD_SKRIPT)))
if (!file.exists(PFAD_PSEUDONYM_SKRIPT))
return(list(error = paste0(
"Pseudonym-Skript nicht gefunden:\n", PFAD_PSEUDONYM_SKRIPT)))
res_dl = tryCatch(
{ source(PFAD_DOWNLOAD_SKRIPT, local = FALSE); list(ok = TRUE) },
error = function(e) list(ok = FALSE, msg = e$message)
)
if (!res_dl$ok)
return(list(error = paste0("Fehler im Download-Skript: ", res_dl$msg)))
db_ordner = local({
ordner = dirname(normalizePath(PFAD_PSEUDONYM_SKRIPT, mustWork = FALSE))
gefunden = NULL
for (i in 1:5) {
if (file.exists(file.path(ordner, "pseudonyme.db"))) {
gefunden = ordner
break
}
elternteil = dirname(ordner)
if (elternteil == ordner) break
ordner = elternteil
}
gefunden
})
alter_wd = getwd()
wd_ziel = if (!is.null(db_ordner)) db_ordner else
dirname(normalizePath(PFAD_PSEUDONYM_SKRIPT, mustWork = FALSE))
setwd(wd_ziel)
on.exit(setwd(alter_wd), add = TRUE)
res_ps = tryCatch(
{ source(PFAD_PSEUDONYM_SKRIPT, local = FALSE)
if (nchar(trimws(input$pseudonym)) > 0) {
.pw_wert = trimws(input$pseudonym)
.pw_tab = get("pseudo", envir = .GlobalEnv)
.pw_treffer = .pw_tab[.pw_tab$pseudonym == .pw_wert, ]
if (nrow(.pw_treffer) > 0) chiffre = toupper(trimws(.pw_treffer$chiffre[1]))
}; list(ok = TRUE) },
error = function(e) list(ok = FALSE, msg = e$message)
)
if (!res_ps$ok)
return(list(error = paste0("Fehler im Pseudonym-Skript: ", res_ps$msg)))
if (!exists("daten_soms7t", envir = .GlobalEnv))
return(list(error = paste0(
"Objekt 'daten_soms7t' nach dem Sourcen nicht gefunden. ",
"Bitte Download-Skript prüfen.")))
if (!exists("pseudo", envir = .GlobalEnv))
return(list(error = paste0(
"Objekt 'pseudo' nach dem Sourcen nicht gefunden. ",
"Bitte Pseudonym-Skript prüfen.")))
daten = get("daten_soms7t", envir = .GlobalEnv)
pseudo_df = get("pseudo", envir = .GlobalEnv)
treffer_ps = pseudo_df[pseudo_df$chiffre == chiffre, ]
if (nrow(treffer_ps) == 0)
return(list(error = paste0(
"Chiffre '", chiffre, "' wurde in der Pseudonym-Datenbank nicht gefunden.")))
alle_session_ids = unique(treffer_ps$pseudonym)
if (nchar(trimws(input$pseudonym)) > 0) alle_session_ids = trimws(input$pseudonym)
treffer_dat = daten[daten$session %in% alle_session_ids, ]
if (nrow(treffer_dat) == 0)
return(list(error = paste0(
"Kein SOMS-7T-Datensatz für Chiffre '", chiffre, "' gefunden. ",
"(", length(alle_session_ids), " Pseudonym(e) geprüft)")))
info_mehrere = NULL
if (nrow(treffer_dat) > 1) {
n = nrow(treffer_dat)
treffer_dat = treffer_dat[order(treffer_dat$created, decreasing = TRUE), ]
datum_neu = tryCatch(
format(as.POSIXct(treffer_dat$created[1]), "%d.%m.%Y %H:%M"),
error = function(e) "unbekanntes Datum"
)
info_mehrere = paste0(
"Mehrere Ausfüllungen gefunden (", n, " Einträge). ",
"Angezeigt wird die neueste vom ", datum_neu, "."
)
treffer_dat = treffer_dat[1, , drop = FALSE]
}
zeile = treffer_dat[1, , drop = FALSE]
datum_str = tryCatch(
format(as.POSIXct(zeile[["created"]][1]), "%d.%m.%Y"),
error = function(e) format(Sys.Date(), "%d.%m.%Y")
)
geschlecht_text = soms7t_get_label_text(daten[["soms7t_geschlecht"]],
zeile[["soms7t_geschlecht"]])
geschlecht_spalte = soms7t_geschlecht_spalte(geschlecht_text)
stufen = integer(53)
item_texte = character(53)
for (i in seq_len(53)) {
var = paste0("soms7t_", sprintf("%02d", i))
stufen[i] = soms7t_get_level(daten[[var]], zeile[[var]])
item_texte[i] = soms7t_item_text(daten[[var]])
if (is.na(item_texte[i])) item_texte[i] = paste0("Item ", i)
}
beschwerden_gesamt = soms7t_berechne_beschwerdenanzahl(stufen)
beschwerden_dsmiv = soms7t_berechne_beschwerdenanzahl(stufen[SOMS7T_DSMIV_ITEMS])
intensitaet = soms7t_berechne_intensitaet(stufen)
pr_beschwerden_gesund = pr_lookup_range(norm_b2, beschwerden_gesamt, geschlecht_spalte)
pr_beschwerden_patient = pr_lookup_exact(norm_b1, beschwerden_gesamt, "pr_beschwerdenanzahl")
pr_dsmiv = pr_lookup_exact(norm_b3, beschwerden_dsmiv, geschlecht_spalte)
pr_intensitaet_gesund = pr_lookup_range(norm_b4, intensitaet, geschlecht_spalte)
pr_intensitaet_patient = pr_lookup_exact(norm_b1, intensitaet, "pr_intensitaetsindex")
pr_staerke_2_4 = pr_lookup_b5(norm_b5, beschwerden_gesamt, "staerke_2_4")
kriterium_a = soms7t_kriterium_a(beschwerden_gesamt)
items_idx = which(stufen >= 2)
items_liste = data.frame(
nummer = items_idx,
text = item_texte[items_idx],
stufe = stufen[items_idx],
stringsAsFactors = FALSE
)
list(
chiffre = chiffre,
datum_str = datum_str,
info_mehrere = info_mehrere,
geschlecht_text = geschlecht_text,
beschwerden_gesamt = beschwerden_gesamt,
beschwerden_dsmiv = beschwerden_dsmiv,
intensitaet = intensitaet,
pr_beschwerden_gesund = pr_beschwerden_gesund,
pr_beschwerden_patient = pr_beschwerden_patient,
pr_dsmiv = pr_dsmiv,
pr_intensitaet_gesund = pr_intensitaet_gesund,
pr_intensitaet_patient = pr_intensitaet_patient,
pr_staerke_2_4 = pr_staerke_2_4,
kriterium_a = kriterium_a,
items_liste = items_liste,
error = NULL
)
})
output$fehler_ui = renderUI({
req(input$btn_suchen)
d = ergebnis_r()
if (!is.null(d$error)) div(class = "alert-fehler", d$error)
})
output$warnung_ui = renderUI({
req(input$btn_suchen)
d = ergebnis_r()
if (!is.null(d$error) || is.null(d$info_mehrere)) return(NULL)
div(class = "alert-warnung", d$info_mehrere)
})
output$ergebnis_ui = renderUI({
req(input$btn_suchen)
d = ergebnis_r()
if (!is.null(d$error)) return(NULL)
items_ui = if (nrow(d$items_liste) == 0) {
div(style = "color:#777; font-style:italic;", "Keine Items mit Stufe ≥2 vorhanden.")
} else {
lapply(seq_len(nrow(d$items_liste)), function(r) {
zeile = d$items_liste[r, ]
div(class = "item-zeile",
div(class = "item-nr", paste0(zeile$nummer, ".")),
div(class = "item-text", zeile$text),
span(class = paste0("stufe-badge ", soms7t_badge_klasse(zeile$stufe)),
SOMS7T_STUFEN_TEXTE[zeile$stufe + 1L])
)
})
}
div(class = "abschnitt-karte",
div(class = "abschnitt-titel", "SOMS-7T Auswertung"),
div(class = "meta-block",
tags$strong("Chiffre: "), d$chiffre,
tags$span(" | ", style = "color:#ccc;"),
tags$strong("Ausfülldatum: "), d$datum_str,
tags$span(" | ", style = "color:#ccc;"),
tags$strong("Geschlecht: "), if (is.na(d$geschlecht_text)) "k. A." else d$geschlecht_text
),
tags$hr(),
tags$h5("Beschwerdenanzahl"),
div(class = "kennwert-block",
div(
div(class = "kennwert-zahl", d$beschwerden_gesamt),
div(class = "kennwert-label", "Rohwert (alle 53 Items, Stufe ≥2)"),
div(class = "kennwert-pr", soms7t_format_pr(d$pr_beschwerden_gesund),
" (Gesunden-Norm, geschlechtsgetrennt)"),
div(class = "kennwert-pr-patient",
"Vergleichswert: psychosomatische Patienten (Veränderungsmessung): ",
soms7t_format_pr(d$pr_beschwerden_patient))
),
div(
div(class = "kennwert-zahl", d$beschwerden_dsmiv),
div(class = "kennwert-label", "Rohwert DSM-IV-Liste (33 Items, Stufe ≥2)"),
div(class = "kennwert-pr", soms7t_format_pr(d$pr_dsmiv))
)
),
tags$hr(),
tags$h5("Intensitätsindex"),
div(class = "kennwert-block",
div(
div(class = "kennwert-zahl", d$intensitaet),
div(class = "kennwert-label", "Rohwert (Summe aller 53 Item-Stufen, 0-212)"),
div(class = "kennwert-pr", soms7t_format_pr(d$pr_intensitaet_gesund),
" (Gesunden-Norm, geschlechtsgetrennt)"),
div(class = "kennwert-pr-patient",
"Vergleichswert: psychosomatische Patienten (Veränderungsmessung): ",
soms7t_format_pr(d$pr_intensitaet_patient))
)
),
tags$hr(),
tags$h5("Screening-Kriterium"),
div(class = paste0("kriterium-box ", if (d$kriterium_a) "kriterium-erfuellt" else "kriterium-nicht"),
div(class = "kriterium-titel",
if (d$kriterium_a)
paste0("Screening-Kriterium A erfüllt (≥3 Items mittelmäßig+): ",
d$beschwerden_gesamt, " Items")
else
paste0("Screening-Kriterium A nicht erfüllt (", d$beschwerden_gesamt,
" von 3 Items mittelmäßig+)")
),
if (d$kriterium_a) div(class = "kriterium-text", SOMS7T_KRITERIUM_A_TEXT),
div(class = "kriterium-b-werte",
div(tags$strong("Kriterium B PR Beschwerdenanzahl (Schwelle 2-4): "),
soms7t_format_pr(d$pr_staerke_2_4)),
div(tags$strong("PR Intensitätsindex: "), soms7t_format_pr(d$pr_intensitaet_gesund))
),
div(class = "kriterium-b-hinweis", SOMS7T_KRITERIUM_B_HINWEIS)
),
tags$hr(),
tags$h5("Items mit Stufe ≥ mittelmäßig"),
div(items_ui),
div(class = "disclaimer-zeile", SOMS7T_DISCLAIMER)
)
})
output$download_word = downloadHandler(
filename = function() {
d = tryCatch(ergebnis_r(), error = function(e) NULL)
chiffre_esc = if (is.list(d) && is.null(d$error) && nchar(d$chiffre) > 0)
gsub("[^A-Za-z0-9_-]", "_", d$chiffre) else "export"
datum = if (is.list(d) && is.null(d$error) && !is.null(d$datum_str))
tryCatch(
format(as.Date(d$datum_str, "%d.%m.%Y"), "%Y%m%d"),
error = function(e) format(Sys.Date(), "%Y%m%d")
)
else
format(Sys.Date(), "%Y%m%d")
paste0("SOMS7T_", chiffre_esc, "_", datum, ".docx")
},
content = function(file) {
d = tryCatch(ergebnis_r(), error = function(e) NULL)
daten_ok = is.list(d) && is.null(d$error)
if (!daten_ok) {
doc = read_docx()
doc = body_add_par(doc,
"Kein Datensatz geladen. Bitte zuerst Chiffre eingeben und 'Auswerten' klicken.",
style = "Normal")
print(doc, target = file)
return()
}
doc = tryCatch(
erstelle_soms7t_docx(d),
error = function(e) {
err_doc = read_docx()
body_add_par(err_doc,
paste0("Fehler beim Erstellen des Word-Dokuments: ", e$message),
style = "Normal")
}
)
print(doc, target = file)
}
)
}
# Start ####
shinyApp(ui, server)

View file

@ -0,0 +1,102 @@
rohwert,pr_beschwerdenanzahl,pr_intensitaetsindex
0,0,0
1,1,1
2,2,2
3,4,3
4,8,3
5,12,3
6,15,4
7,20,6
8,23,9
9,27,13
10,32,16
11,36,18
12,41,18
13,47,20
14,49,22
15,53,25
16,58,26
17,60,29
18,64,31
19,65,33
20,71,34
21,73,36
22,76,38
23,80,40
24,81,42
25,82,47
26,84,49
27,84,53
28,85,55
29,87,56
30,88,57
31,89,60
32,92,62
33,94,65
34,96,68
35,97,68
36,97,69
37,98,70
38,98,71
39,98,72
40,99,74
41,99,76
42,100,76
43,100,77
44,100,77
45,100,78
46,100,80
47,100,81
48,100,82
49,100,82
50,100,82
51,100,84
52,100,84
53,100,85
54,,85
55,,85
56,,85
57,,85
58,,85
59,,85
60,,86
61,,88
62,,88
63,,88
64,,89
65,,89
66,,89
67,,89
68,,90
69,,91
70,,91
71,,92
72,,93
73,,93
74,,93
75,,94
76,,94
77,,94
78,,94
79,,94
80,,95
81,,96
82,,96
83,,96
84,,97
85,,98
86,,98
87,,98
88,,98
89,,98
90,,100
91,,100
92,,100
93,,100
94,,100
95,,100
96,,100
97,,100
98,,100
99,,100
100,,100
1 rohwert pr_beschwerdenanzahl pr_intensitaetsindex
2 0 0 0
3 1 1 1
4 2 2 2
5 3 4 3
6 4 8 3
7 5 12 3
8 6 15 4
9 7 20 6
10 8 23 9
11 9 27 13
12 10 32 16
13 11 36 18
14 12 41 18
15 13 47 20
16 14 49 22
17 15 53 25
18 16 58 26
19 17 60 29
20 18 64 31
21 19 65 33
22 20 71 34
23 21 73 36
24 22 76 38
25 23 80 40
26 24 81 42
27 25 82 47
28 26 84 49
29 27 84 53
30 28 85 55
31 29 87 56
32 30 88 57
33 31 89 60
34 32 92 62
35 33 94 65
36 34 96 68
37 35 97 68
38 36 97 69
39 37 98 70
40 38 98 71
41 39 98 72
42 40 99 74
43 41 99 76
44 42 100 76
45 43 100 77
46 44 100 77
47 45 100 78
48 46 100 80
49 47 100 81
50 48 100 82
51 49 100 82
52 50 100 82
53 51 100 84
54 52 100 84
55 53 100 85
56 54 85
57 55 85
58 56 85
59 57 85
60 58 85
61 59 85
62 60 86
63 61 88
64 62 88
65 63 88
66 64 89
67 65 89
68 66 89
69 67 89
70 68 90
71 69 91
72 70 91
73 71 92
74 72 93
75 73 93
76 74 93
77 75 94
78 76 94
79 77 94
80 78 94
81 79 94
82 80 95
83 81 96
84 82 96
85 83 96
86 84 97
87 85 98
88 86 98
89 87 98
90 88 98
91 89 98
92 90 100
93 91 100
94 92 100
95 93 100
96 94 100
97 95 100
98 96 100
99 97 100
100 98 100
101 99 100
102 100 100

View file

@ -0,0 +1,38 @@
rohwert_min,rohwert_max,gesamt,maenner,frauen
0,0,48.7,56.5,41.7
1,1,63.4,68.6,58.6
2,2,72.9,77.2,69.0
3,3,79.0,83.0,75.5
4,4,83.8,87.0,80.9
5,5,86.6,89.2,84.2
6,6,88.8,90.8,87.1
7,7,90.7,92.4,89.1
8,8,92.0,93.4,90.8
9,9,93.1,94.1,92.2
10,10,94.2,95.2,93.3
11,11,94.8,95.7,94.0
12,12,95.5,96.2,94.8
13,13,96.2,96.7,95.7
14,14,96.5,97.1,96.0
15,15,96.8,97.6,96.2
16,16,97.3,98.0,96.7
17,17,97.7,98.3,97.2
18,18,98.1,98.5,97.8
19,19,98.2,98.5,97.9
20,20,98.3,98.7,98.1
21,22,98.5,98.7,98.3
23,23,98.6,98.7,98.4
24,24,98.7,98.8,98.4
25,25,98.8,98.8,98.6
26,26,98.9,98.8,98.7
27,27,98.9,98.9,98.8
28,28,99.0,98.9,99.0
29,29,99.1,98.9,99.0
30,30,99.2,98.9,99.3
31,31,99.4,99.0,99.4
32,33,99.4,99.1,99.6
34,36,99.6,99.2,99.7
37,37,99.8,99.5,99.7
38,38,99.8,99.7,99.8
39,40,99.8,99.8,99.8
41,53,99.9,99.9,99.9
1 rohwert_min rohwert_max gesamt maenner frauen
2 0 0 48.7 56.5 41.7
3 1 1 63.4 68.6 58.6
4 2 2 72.9 77.2 69.0
5 3 3 79.0 83.0 75.5
6 4 4 83.8 87.0 80.9
7 5 5 86.6 89.2 84.2
8 6 6 88.8 90.8 87.1
9 7 7 90.7 92.4 89.1
10 8 8 92.0 93.4 90.8
11 9 9 93.1 94.1 92.2
12 10 10 94.2 95.2 93.3
13 11 11 94.8 95.7 94.0
14 12 12 95.5 96.2 94.8
15 13 13 96.2 96.7 95.7
16 14 14 96.5 97.1 96.0
17 15 15 96.8 97.6 96.2
18 16 16 97.3 98.0 96.7
19 17 17 97.7 98.3 97.2
20 18 18 98.1 98.5 97.8
21 19 19 98.2 98.5 97.9
22 20 20 98.3 98.7 98.1
23 21 22 98.5 98.7 98.3
24 23 23 98.6 98.7 98.4
25 24 24 98.7 98.8 98.4
26 25 25 98.8 98.8 98.6
27 26 26 98.9 98.8 98.7
28 27 27 98.9 98.9 98.8
29 28 28 99.0 98.9 99.0
30 29 29 99.1 98.9 99.0
31 30 30 99.2 98.9 99.3
32 31 31 99.4 99.0 99.4
33 32 33 99.4 99.1 99.6
34 34 36 99.6 99.2 99.7
35 37 37 99.8 99.5 99.7
36 38 38 99.8 99.7 99.8
37 39 40 99.8 99.8 99.8
38 41 53 99.9 99.9 99.9

View file

@ -0,0 +1,27 @@
rohwert,gesamt,maenner,frauen
0,51.3,58.5,45.0
1,68.1,72.1,64.5
2,78.3,80.8,76.0
3,85.4,88.4,82.7
4,89.6,91.9,87.6
5,92.1,93.5,90.9
6,93.9,94.7,93.1
7,95.4,96.2,94.7
8,96.5,97.0,96.0
9,97.0,97.6,96.6
10,97.8,98.2,97.4
11,98.2,98.5,97.9
12,98.3,98.5,98.2
13,98.5,98.7,98.4
14,98.8,98.7,98.8
15,99.0,98.8,99.1
16,99.1,99.0,99.1
17,99.2,99.1,99.3
18,99.5,99.2,99.7
19,99.5,99.2,99.8
20,99.6,99.4,99.8
21,99.7,99.5,99.9
22,99.8,99.7,99.9
23,99.9,99.9,99.9
24,100.0,100.0,99.9
25,100.0,100.0,100.0
1 rohwert gesamt maenner frauen
2 0 51.3 58.5 45.0
3 1 68.1 72.1 64.5
4 2 78.3 80.8 76.0
5 3 85.4 88.4 82.7
6 4 89.6 91.9 87.6
7 5 92.1 93.5 90.9
8 6 93.9 94.7 93.1
9 7 95.4 96.2 94.7
10 8 96.5 97.0 96.0
11 9 97.0 97.6 96.6
12 10 97.8 98.2 97.4
13 11 98.2 98.5 97.9
14 12 98.3 98.5 98.2
15 13 98.5 98.7 98.4
16 14 98.8 98.7 98.8
17 15 99.0 98.8 99.1
18 16 99.1 99.0 99.1
19 17 99.2 99.1 99.3
20 18 99.5 99.2 99.7
21 19 99.5 99.2 99.8
22 20 99.6 99.4 99.8
23 21 99.7 99.5 99.9
24 22 99.8 99.7 99.9
25 23 99.9 99.9 99.9
26 24 100.0 100.0 99.9
27 25 100.0 100.0 100.0

View file

@ -0,0 +1,89 @@
rohwert_min,rohwert_max,gesamt,maenner,frauen
0,0,18.4,24.1,13.2
1,1,27.0,34.2,20.6
2,2,35.2,43.0,28.2
3,3,42.7,50.1,36.0
4,4,48.7,55.6,42.4
5,5,54.0,60.8,47.9
6,6,57.8,64.7,51.7
7,7,61.3,67.4,55.8
8,8,65.2,71.2,59.7
9,9,68.3,73.8,63.3
10,10,70.9,75.7,66.6
11,11,73.2,77.5,69.3
12,12,75.2,79.2,71.6
13,13,77.5,81.4,73.9
14,14,79.1,82.9,75.7
15,15,80.8,84.7,77.3
16,16,82.2,86.0,78.8
17,17,83.1,86.7,80.0
18,18,84.0,87.2,81.2
19,19,85.1,88.2,82.3
20,20,86.0,89.1,83.2
21,21,86.9,89.6,84.5
22,22,87.5,90.3,85.0
23,23,88.2,90.8,85.9
24,24,88.8,91.2,86.8
25,25,89.8,91.8,87.9
26,26,90.3,92.2,88.6
27,27,91.1,92.6,89.7
28,28,91.6,93.0,90.3
29,29,92.1,93.2,91.2
30,30,92.5,93.5,91.6
31,31,93.0,93.9,92.2
32,32,93.5,94.2,92.8
33,33,93.7,94.5,93.0
34,34,94.0,95.0,93.1
35,35,94.4,95.2,93.7
36,36,94.5,95.3,93.7
37,37,94.7,95.5,94.0
38,38,95.2,95.9,94.5
39,39,95.3,96.1,94.5
40,40,95.5,96.3,94.8
41,41,95.6,96.4,94.9
42,43,95.7,96.5,94.9
44,44,96.1,96.9,95.3
45,45,96.2,97.0,95.4
46,46,96.3,97.0,95.6
47,47,96.5,97.1,95.9
48,48,96.7,97.2,96.2
49,49,96.9,97.4,96.4
50,50,97.2,97.7,96.7
51,51,97.4,97.9,96.9
52,52,97.5,98.0,97.1
53,53,97.8,98.1,97.5
54,54,97.8,98.2,97.5
55,55,97.9,98.2,97.5
56,56,98.0,98.2,97.6
57,57,98.1,98.3,97.8
58,59,98.1,98.4,97.8
60,60,98.1,98.4,97.9
61,61,98.2,98.4,98.1
62,62,98.3,98.6,98.1
63,63,98.4,98.6,98.2
64,64,98.4,98.6,98.3
65,65,98.5,98.7,98.3
66,66,98.6,98.7,98.4
67,67,98.6,98.7,98.5
68,68,98.7,98.8,98.5
69,71,98.7,98.9,98.5
72,74,98.8,98.9,98.7
75,75,98.8,98.9,98.7
76,76,98.9,98.9,98.9
77,77,98.9,99.0,98.9
78,79,99.0,99.0,99.0
80,80,99.1,99.0,99.1
81,84,99.1,99.0,99.2
85,85,99.3,99.2,99.4
86,88,99.4,99.2,99.6
89,89,99.4,99.2,99.6
90,92,99.6,99.3,99.8
93,93,99.6,99.5,99.8
94,95,99.7,99.5,99.9
96,98,99.7,99.6,99.9
99,99,99.8,99.7,99.9
100,101,99.8,99.7,99.9
102,103,99.9,99.8,99.9
104,104,99.9,99.9,99.9
105,112,100.0,100.0,99.9
113,113,100.0,100.0,100.0
1 rohwert_min rohwert_max gesamt maenner frauen
2 0 0 18.4 24.1 13.2
3 1 1 27.0 34.2 20.6
4 2 2 35.2 43.0 28.2
5 3 3 42.7 50.1 36.0
6 4 4 48.7 55.6 42.4
7 5 5 54.0 60.8 47.9
8 6 6 57.8 64.7 51.7
9 7 7 61.3 67.4 55.8
10 8 8 65.2 71.2 59.7
11 9 9 68.3 73.8 63.3
12 10 10 70.9 75.7 66.6
13 11 11 73.2 77.5 69.3
14 12 12 75.2 79.2 71.6
15 13 13 77.5 81.4 73.9
16 14 14 79.1 82.9 75.7
17 15 15 80.8 84.7 77.3
18 16 16 82.2 86.0 78.8
19 17 17 83.1 86.7 80.0
20 18 18 84.0 87.2 81.2
21 19 19 85.1 88.2 82.3
22 20 20 86.0 89.1 83.2
23 21 21 86.9 89.6 84.5
24 22 22 87.5 90.3 85.0
25 23 23 88.2 90.8 85.9
26 24 24 88.8 91.2 86.8
27 25 25 89.8 91.8 87.9
28 26 26 90.3 92.2 88.6
29 27 27 91.1 92.6 89.7
30 28 28 91.6 93.0 90.3
31 29 29 92.1 93.2 91.2
32 30 30 92.5 93.5 91.6
33 31 31 93.0 93.9 92.2
34 32 32 93.5 94.2 92.8
35 33 33 93.7 94.5 93.0
36 34 34 94.0 95.0 93.1
37 35 35 94.4 95.2 93.7
38 36 36 94.5 95.3 93.7
39 37 37 94.7 95.5 94.0
40 38 38 95.2 95.9 94.5
41 39 39 95.3 96.1 94.5
42 40 40 95.5 96.3 94.8
43 41 41 95.6 96.4 94.9
44 42 43 95.7 96.5 94.9
45 44 44 96.1 96.9 95.3
46 45 45 96.2 97.0 95.4
47 46 46 96.3 97.0 95.6
48 47 47 96.5 97.1 95.9
49 48 48 96.7 97.2 96.2
50 49 49 96.9 97.4 96.4
51 50 50 97.2 97.7 96.7
52 51 51 97.4 97.9 96.9
53 52 52 97.5 98.0 97.1
54 53 53 97.8 98.1 97.5
55 54 54 97.8 98.2 97.5
56 55 55 97.9 98.2 97.5
57 56 56 98.0 98.2 97.6
58 57 57 98.1 98.3 97.8
59 58 59 98.1 98.4 97.8
60 60 60 98.1 98.4 97.9
61 61 61 98.2 98.4 98.1
62 62 62 98.3 98.6 98.1
63 63 63 98.4 98.6 98.2
64 64 64 98.4 98.6 98.3
65 65 65 98.5 98.7 98.3
66 66 66 98.6 98.7 98.4
67 67 67 98.6 98.7 98.5
68 68 68 98.7 98.8 98.5
69 69 71 98.7 98.9 98.5
70 72 74 98.8 98.9 98.7
71 75 75 98.8 98.9 98.7
72 76 76 98.9 98.9 98.9
73 77 77 98.9 99.0 98.9
74 78 79 99.0 99.0 99.0
75 80 80 99.1 99.0 99.1
76 81 84 99.1 99.0 99.2
77 85 85 99.3 99.2 99.4
78 86 88 99.4 99.2 99.6
79 89 89 99.4 99.2 99.6
80 90 92 99.6 99.3 99.8
81 93 93 99.6 99.5 99.8
82 94 95 99.7 99.5 99.9
83 96 98 99.7 99.6 99.9
84 99 99 99.8 99.7 99.9
85 100 101 99.8 99.7 99.9
86 102 103 99.9 99.8 99.9
87 104 104 99.9 99.9 99.9
88 105 112 100.0 100.0 99.9
89 113 113 100.0 100.0 100.0

View file

@ -0,0 +1,50 @@
beschwerdenanzahl,staerke_4,staerke_3_4,staerke_2_4,staerke_1_4
0,92.3,77.9,48.7,18.4
1,97.0,87.0,63.4,29.7
2,98.5,91.9,72.9,38.7
3,99.1,94.5,79.0,48.1
4,99.5,96.0,83.8,54.8
5,99.6,96.8,86.6,60.7
6,99.7,97.6,88.8,66.3
7,99.8,98.0,90.7,70.3
8,99.9,98.3,92.0,73.7
9,99.9,98.6,93.1,76.7
10,99.9,98.9,94.2,79.4
11,99.9,99.1,94.8,81.4
12,99.9,99.2,95.5,83.5
13,99.9,99.4,96.2,85.3
14,99.9,99.5,96.5,86.7
15,99.9,99.6,96.8,88.5
16,100.0,99.7,97.3,89.6
17,,99.8,97.7,90.4
18,,99.8,98.1,91.3
19,,99.8,98.2,92.4
20,,99.9,98.3,93.2
21,,99.9,98.5,93.9
22,,100.0,98.5,94.7
23,,,98.6,95.2
24,,,98.7,95.7
25,,,98.8,96.0
26,,,98.9,96.3
27,,,98.9,96.6
28,,,99.0,96.7
29,,,99.1,96.9
30,,,99.2,97.3
31,,,99.4,97.6
32,,,99.4,97.8
33,,,99.4,97.9
34,,,99.6,98.1
35,,,99.6,98.3
36,,,99.6,98.5
37,,,99.8,98.7
38,,,99.8,98.9
39,,,99.9,99.0
40,,,99.9,99.1
41,,,100.0,99.3
42,,,,99.3
43,,,,99.4
44,,,,99.4
45,,,,99.6
46,,,,99.7
47,,,,99.9
48,,,,100.0
1 beschwerdenanzahl staerke_4 staerke_3_4 staerke_2_4 staerke_1_4
2 0 92.3 77.9 48.7 18.4
3 1 97.0 87.0 63.4 29.7
4 2 98.5 91.9 72.9 38.7
5 3 99.1 94.5 79.0 48.1
6 4 99.5 96.0 83.8 54.8
7 5 99.6 96.8 86.6 60.7
8 6 99.7 97.6 88.8 66.3
9 7 99.8 98.0 90.7 70.3
10 8 99.9 98.3 92.0 73.7
11 9 99.9 98.6 93.1 76.7
12 10 99.9 98.9 94.2 79.4
13 11 99.9 99.1 94.8 81.4
14 12 99.9 99.2 95.5 83.5
15 13 99.9 99.4 96.2 85.3
16 14 99.9 99.5 96.5 86.7
17 15 99.9 99.6 96.8 88.5
18 16 100.0 99.7 97.3 89.6
19 17 99.8 97.7 90.4
20 18 99.8 98.1 91.3
21 19 99.8 98.2 92.4
22 20 99.9 98.3 93.2
23 21 99.9 98.5 93.9
24 22 100.0 98.5 94.7
25 23 98.6 95.2
26 24 98.7 95.7
27 25 98.8 96.0
28 26 98.9 96.3
29 27 98.9 96.6
30 28 99.0 96.7
31 29 99.1 96.9
32 30 99.2 97.3
33 31 99.4 97.6
34 32 99.4 97.8
35 33 99.4 97.9
36 34 99.6 98.1
37 35 99.6 98.3
38 36 99.6 98.5
39 37 99.8 98.7
40 38 99.8 98.9
41 39 99.9 99.0
42 40 99.9 99.1
43 41 100.0 99.3
44 42 99.3
45 43 99.4
46 44 99.4
47 45 99.6
48 46 99.7
49 47 99.9
50 48 100.0

2879
SOMS-7T/renv.lock Normal file

File diff suppressed because it is too large Load diff

17
SOMS-7T/setup_renv.R Normal file
View file

@ -0,0 +1,17 @@
# Einmalig ausfuehren, bevor die App zum ersten Mal gestartet wird.
# Initialisiert renv und installiert alle benoetigten Pakete.
#
# formr wird hier installiert, weil das extern gesourcte Download-Skript
# (get_data_soms7t.R) es benoetigt - die App selbst laedt formr nicht per
# library() und spricht nie direkt mit der formr-API.
# DBI und RSQLite werden vom gesourcten Pseudonym-Skript benoetigt,
# nicht direkt von der App selbst.
renv::init()
pkgs = c("shiny", "dplyr", "ggplot2", "haven", "officer", "DBI", "RSQLite", "formr")
install.packages(pkgs)
renv::snapshot()
message("Setup abgeschlossen. App starten mit: shiny::runApp()")