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

650
HASE ADHS-SB/app.R Normal file
View file

@ -0,0 +1,650 @@
# Präambel ####
AKZENT_FARBE = "#8B2635"
PFAD_DOWNLOAD_SKRIPT = "../API/get_data_hase-adhs-sb.R"
PFAD_PSEUDONYM_SKRIPT = "../get_pseudo.R"
ADHSSB_DISCLAIMER = paste0(
"Diese Auswertung ist ein Hilfsmittel fuer klinisches Fachpersonal und ersetzt ",
"keine klinische Diagnose. Die Interpretation, insbesondere eine etwaige Typzuordnung, ",
"obliegt der behandelnden Person."
)
ADHSSB_TYPKLASSIFIKATION_HINWEIS = paste0(
"Eine automatische Typklassifikation (Kombinierter Typus / Aufmerksamkeitsgestoerter Typus / ",
"Hyperaktiv-impulsiver Typus) ist auf Basis dieses Bogens nicht moeglich, da weder im Original ",
"noch im Auswertungsmanual numerische Cutoffs oder eine Berechnungsformel angegeben sind. ",
"Die Typzuordnung erfolgt durch die untersuchende Person."
)
# Subskalendefinition: Feldbereich und Maximalwert (0-3 je Item).
ADHSSB_SUBSKALEN = list(
aufmerksamkeit = list(
titel = "Aufmerksamkeitsstoerung",
items = 1:9,
max = 27
),
hyperaktivitaet = list(
titel = "Hyperaktivitaet",
items = 10:14,
max = 15
),
impulsivitaet = list(
titel = "Impulsivitaet",
items = 15:18,
max = 12
),
hyp_imp_kombiniert = list(
titel = "Hyperaktivitaet + Impulsivitaet (kombiniert)",
items = 10:18,
max = 27
),
gesamt = list(
titel = "Gesamtsumme",
items = 1:18,
max = 54
)
)
# Stufe 0-3 (4 Stufen): 0 = gruen, 1 = helles Rosa, 2 = mittleres Rot, 3 = volles Dunkelrot.
ADHSSB_BADGE_FARBEN = c(
"0" = "#4CAF50",
"1" = "#F48FB1",
"2" = "#EF5350",
"3" = "#B71C1C"
)
ADHSSB_BADGE_TEXT_FARBEN = c(
"0" = "white",
"1" = "#333333",
"2" = "white",
"3" = "white"
)
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)
# Helper ####
# Entfernt Markdown-Sternchen und loest die formr-Backslash-Maskierung
# ("\." vor der Itemnummer) zu einem literalen Punkt auf.
bereinige_text = function(x) {
if (is.null(x) || length(x) == 0 || is.na(x[1])) return(NA_character_)
x = as.character(x[1])
x = gsub("\\*\\*", "", x)
x = gsub("\\.", ".", x, fixed = TRUE)
x
}
# Entfernt die fuehrende Itemnummer samt Punkt aus dem bereinigten Label,
# damit .item-nr und .item-text nicht doppelt nummerieren.
entferne_itemnummer = function(x) {
if (is.null(x) || length(x) == 0 || is.na(x[1])) return(NA_character_)
gsub("^[0-9]+\\.\\s*", "", x)
}
# Ordnet einem Rohwert die 0-basierte Stufe zu, ausschliesslich ueber das
# labels-Attribut der jeweiligen Original-Spalte (nie hartkodierte Codes).
adhssb_get_stufe = function(original_col, wert) {
if (is.null(wert) || length(wert) == 0 || is.na(wert[1])) return(NA_integer_)
labels_vec = attr(original_col, "labels")
if (is.null(labels_vec) || length(labels_vec) == 0) return(NA_integer_)
sortiert = labels_vec[order(labels_vec)]
pos = match(as.numeric(wert[1]), sortiert)
if (is.na(pos)) return(NA_integer_)
as.integer(pos - 1L)
}
# Liefert den Antworttext (Stufenname) zu einem Rohwert ueber das
# labels-Attribut der Original-Spalte.
adhssb_get_stufentext = function(original_col, wert) {
if (is.null(wert) || length(wert) == 0 || is.na(wert[1])) return(NA_character_)
labels_vec = attr(original_col, "labels")
if (is.null(labels_vec) || length(labels_vec) == 0) return(NA_character_)
sortiert = labels_vec[order(labels_vec)]
pos = match(as.numeric(wert[1]), sortiert)
if (is.na(pos)) return(NA_character_)
names(sortiert)[pos]
}
# Liest den bereinigten Fragetext (ohne fuehrende Nummer) aus dem
# label-Attribut der Original-Spalte.
adhssb_get_itemtext = function(original_col) {
entferne_itemnummer(bereinige_text(attr(original_col, "label")))
}
# Summiert die Stufenwerte (0-3) der angegebenen Item-Nummern einer Zeile.
adhssb_summe = function(daten, zeile, item_nummern) {
werte = sapply(item_nummern, function(i) {
var = paste0("hasesb_", sprintf("%02d", i))
adhssb_get_stufe(daten[[var]], zeile[[var]])
})
as.numeric(sum(werte, na.rm = TRUE))
}
# 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; }
.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: #4CAF50; color: white; }
.stufe-badge-1 { background: #F48FB1; color: #333333; }
.stufe-badge-2 { background: #EF5350; color: white; }
.stufe-badge-3 { background: #B71C1C; color: white; }
.subskalen-gruppe { margin-bottom: 18px; }
.subskalen-gruppe h5 { color: #8B2635; font-weight: 700; margin-bottom: 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("HASE - ADHS-Selbstbeurteilungsskala (ADHS-SB)"),
tags$p("Homburger ADHS-Skalen fuer Erwachsene")
),
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_adhssb_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_warnung = fp_text(bold = TRUE, font.size = 10.5, color = "#BF360C",
shading.color = "#FFF3E0")
fp_disclaimer = fp_text(font.size = 9, italic = TRUE, color = "#777777")
doc = body_add_fpar(doc, fpar(ftext("HASE - ADHS-Selbstbeurteilungsskala (ADHS-SB)", 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)
))
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(ADHSSB_TYPKLASSIFIKATION_HINWEIS, fp_warnung)))
doc = body_add_par(doc, "", style = "Normal")
doc = body_add_fpar(doc, fpar(ftext("Subskalensummen", fp_abschnitt)))
for (sk_name in names(ADHSSB_SUBSKALEN)) {
sk = ADHSSB_SUBSKALEN[[sk_name]]
doc = body_add_fpar(doc, fpar(
ftext(paste0(sk$titel, ": "), fp_label),
ftext(paste0(erg$summen[[sk_name]], " / ", sk$max), fp_normal)
))
}
doc = body_add_par(doc, "", style = "Normal")
doc = body_add_fpar(doc, fpar(ftext("Einzelitems (1-18)", fp_abschnitt)))
for (sk_name in c("aufmerksamkeit", "hyperaktivitaet", "impulsivitaet")) {
sk = ADHSSB_SUBSKALEN[[sk_name]]
doc = body_add_fpar(doc, fpar(ftext(sk$titel, fp_text(bold = TRUE, font.size = 11.5, color = "#333333"))))
for (i in sk$items) {
stufe = erg$stufen[[i]]
sk_key = if (!is.na(stufe) && stufe >= 0L && stufe <= 3L) as.character(stufe) else "0"
stufentxt = if (!is.na(erg$stufentexte[[i]])) erg$stufentexte[[i]] else "k. A."
item_txt = if (!is.na(erg$itemtexte[[i]])) erg$itemtexte[[i]] else paste0("Item ", i)
fp_badge = fp_text(
color = ADHSSB_BADGE_TEXT_FARBEN[[sk_key]],
bold = TRUE,
shading.color = ADHSSB_BADGE_FARBEN[[sk_key]],
font.size = 10
)
doc = body_add_fpar(doc, fpar(
ftext(paste0(i, ". ", item_txt, " "), fp_normal),
ftext(paste0(" ", stufentxt, " "), fp_badge)
))
}
doc = body_add_par(doc, "", style = "Normal")
}
doc = body_add_fpar(doc, fpar(ftext("Zusatzkriterien (Items 19-22)", fp_abschnitt)))
for (i in 19:22) {
stufentxt = if (!is.na(erg$stufentexte[[i]])) erg$stufentexte[[i]] else "k. A."
item_txt = if (!is.na(erg$itemtexte[[i]])) erg$itemtexte[[i]] else paste0("Item ", i)
doc = body_add_fpar(doc, fpar(
ftext(paste0(i, ". ", item_txt, " "), fp_normal),
ftext(paste0(" ", stufentxt, " "), fp_text(bold = TRUE, font.size = 10, color = "#333333"))
))
}
doc = body_add_par(doc, "", style = "Normal")
doc = body_add_fpar(doc, fpar(ftext(ADHSSB_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)))
}
})
# Skripte werden NICHT beim App-Start gesourct, nur beim Klick auf 'Auswerten'.
ergebnis_r = eventReactive(input$btn_suchen, {
chiffre = toupper(trimws(input$chiffre))
if (nchar(trimws(input$pseudonym)) == 0 && nchar(chiffre) == 0) {
return(list(typ = "leere_eingabe", meldung = "Bitte Chiffre oder Pseudonym eingeben."))
}
if (nchar(trimws(input$pseudonym)) == 0 && !grepl("^[A-Z][0-9]{6}$", chiffre)) {
return(list(typ = "format_fehler", chiffre = chiffre))
}
if (!file.exists(PFAD_DOWNLOAD_SKRIPT)) {
return(list(typ = "pfad_fehler",
meldung = paste0("Download-Skript nicht gefunden:\n", PFAD_DOWNLOAD_SKRIPT)))
}
if (!file.exists(PFAD_PSEUDONYM_SKRIPT)) {
return(list(typ = "pfad_fehler",
meldung = paste0("Pseudonym-Skript nicht gefunden:\n", PFAD_PSEUDONYM_SKRIPT)))
}
ok_dl = tryCatch({
source(PFAD_DOWNLOAD_SKRIPT, local = FALSE)
list(ok = TRUE)
}, error = function(e) list(ok = FALSE, msg = e$message))
if (!ok_dl$ok) return(list(typ = "skript_fehler", meldung = ok_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)
ok_ps = tryCatch({
source(PFAD_PSEUDONYM_SKRIPT, local = FALSE)
list(ok = TRUE)
}, error = function(e) list(ok = FALSE, msg = e$message))
if (!ok_ps$ok) return(list(typ = "skript_fehler", meldung = ok_ps$msg))
if (!exists("daten_hasesb", envir = .GlobalEnv)) {
return(list(typ = "daten_fehlen",
meldung = "Objekt 'daten_hasesb' nach dem Sourcen nicht gefunden. Bitte Download-Skript pruefen."))
}
if (!exists("pseudo", envir = .GlobalEnv)) {
return(list(typ = "daten_fehlen",
meldung = "Objekt 'pseudo' nach dem Sourcen nicht gefunden. Bitte Pseudonym-Skript pruefen."))
}
daten = get("daten_hasesb", envir = .GlobalEnv)
pseudo_df = get("pseudo", envir = .GlobalEnv)
if (nchar(trimws(input$pseudonym)) > 0) {
pw_treffer = pseudo_df[pseudo_df$pseudonym == trimws(input$pseudonym), ]
if (nrow(pw_treffer) > 0) chiffre = toupper(trimws(pw_treffer$chiffre[1]))
}
treffer_ps = pseudo_df[pseudo_df$chiffre == chiffre, ]
if (nrow(treffer_ps) == 0) {
return(list(typ = "kein_treffer",
meldung = 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(typ = "kein_treffer",
meldung = paste0("Kein ADHS-SB-Datensatz fuer Chiffre '", chiffre, "' gefunden. ",
"(", length(alle_session_ids), " Pseudonym(e) geprueft)")))
}
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 Ausfuellungen gefunden (", n, " Eintraege). ",
"Angezeigt wird die neueste vom ", datum_neu, "."
)
treffer_dat = treffer_dat[1, , drop = FALSE]
}
zeile = treffer_dat[1, , drop = FALSE]
# Ausfuelldatum aus dem Zeitstempelfeld 'created' der formr-Ergebnisdaten,
# nicht aus Sys.Date().
datum_str = tryCatch(
format(as.POSIXct(zeile[["created"]][1]), "%d.%m.%Y"),
error = function(e) format(Sys.Date(), "%d.%m.%Y")
)
itemtexte = list()
stufen = list()
stufentexte = list()
for (i in 1:22) {
var = paste0("hasesb_", sprintf("%02d", i))
itemtexte[[i]] = adhssb_get_itemtext(daten[[var]])
stufen[[i]] = adhssb_get_stufe(daten[[var]], zeile[[var]])
stufentexte[[i]] = adhssb_get_stufentext(daten[[var]], zeile[[var]])
}
summen = list(
aufmerksamkeit = adhssb_summe(daten, zeile, ADHSSB_SUBSKALEN$aufmerksamkeit$items),
hyperaktivitaet = adhssb_summe(daten, zeile, ADHSSB_SUBSKALEN$hyperaktivitaet$items),
impulsivitaet = adhssb_summe(daten, zeile, ADHSSB_SUBSKALEN$impulsivitaet$items),
hyp_imp_kombiniert = adhssb_summe(daten, zeile, ADHSSB_SUBSKALEN$hyp_imp_kombiniert$items),
gesamt = adhssb_summe(daten, zeile, ADHSSB_SUBSKALEN$gesamt$items)
)
list(
typ = "erfolg",
chiffre = chiffre,
datum_str = datum_str,
info_mehrere = info_mehrere,
itemtexte = itemtexte,
stufen = stufen,
stufentexte = stufentexte,
summen = summen
)
})
output$fehler_ui = renderUI({
req(input$btn_suchen)
erg = ergebnis_r()
if (erg$typ == "leere_eingabe") {
div(class = "alert-fehler", erg$meldung)
} else if (erg$typ == "format_fehler") {
div(class = "alert-fehler",
paste0("Ungueltige Chiffre '", erg$chiffre, "'. Erwartet: ein Grossbuchstabe + 6 Ziffern (z.B. P000123)."))
} else if (erg$typ == "pfad_fehler") {
div(class = "alert-fehler", erg$meldung)
} else if (erg$typ == "skript_fehler") {
div(class = "alert-fehler", paste0("Fehler beim Ausfuehren eines Skripts: ", erg$meldung))
} else if (erg$typ == "daten_fehlen") {
div(class = "alert-fehler", erg$meldung)
} else if (erg$typ == "kein_treffer") {
div(class = "alert-fehler", erg$meldung)
}
})
output$warnung_ui = renderUI({
req(input$btn_suchen)
erg = ergebnis_r()
if (erg$typ != "erfolg") return(NULL)
tagList(
div(class = "alert-warnung", ADHSSB_TYPKLASSIFIKATION_HINWEIS),
if (!is.null(erg$info_mehrere)) div(class = "alert-warnung", erg$info_mehrere)
)
})
output$ergebnis_ui = renderUI({
req(input$btn_suchen)
erg = ergebnis_r()
if (erg$typ != "erfolg") return(NULL)
baue_item_zeile = function(i) {
stufe = erg$stufen[[i]]
sk = if (!is.na(stufe) && stufe >= 0L && stufe <= 3L) as.character(stufe) else "0"
stufentxt = if (!is.na(erg$stufentexte[[i]])) erg$stufentexte[[i]] else "k. A."
item_txt = if (!is.na(erg$itemtexte[[i]])) erg$itemtexte[[i]] else paste0("Item ", i)
div(class = "item-zeile",
div(class = "item-nr", paste0(i, ".")),
div(class = "item-text", item_txt),
span(class = paste0("stufe-badge stufe-badge-", sk), stufentxt)
)
}
subskalen_ui = lapply(c("aufmerksamkeit", "hyperaktivitaet", "impulsivitaet"), function(sk_name) {
sk = ADHSSB_SUBSKALEN[[sk_name]]
div(class = "subskalen-gruppe",
tags$h5(sk$titel),
lapply(sk$items, baue_item_zeile)
)
})
zusatz_ui = lapply(19:22, baue_item_zeile)
div(
div(class = "abschnitt-karte",
div(class = "abschnitt-titel", "HASE - ADHS-SB"),
div(class = "meta-block",
tags$strong("Chiffre: "), erg$chiffre,
tags$span(" | ", style = "color:#ccc;"),
tags$strong("Ausfuelldatum: "), erg$datum_str
),
tags$hr(),
tags$h5("Subskalensummen"),
plotOutput("balken_plot", height = "420px")
),
div(class = "abschnitt-karte",
div(class = "abschnitt-titel", "Einzelitems (1-18)"),
subskalen_ui
),
div(class = "abschnitt-karte",
div(class = "abschnitt-titel", "Zusatzkriterien (Items 19-22)"),
zusatz_ui
)
)
})
output$balken_plot = renderPlot({
req(input$btn_suchen)
erg = ergebnis_r()
req(erg$typ == "erfolg")
plot_daten = do.call(rbind, lapply(names(ADHSSB_SUBSKALEN), function(sk_name) {
sk = ADHSSB_SUBSKALEN[[sk_name]]
data.frame(
subskala = sk$titel,
erreicht = as.numeric(erg$summen[[sk_name]]),
maximum = as.numeric(sk$max),
stringsAsFactors = FALSE
)
}))
plot_daten$subskala = factor(plot_daten$subskala, levels = rev(plot_daten$subskala))
plot_daten$beschriftung = paste0(plot_daten$erreicht, " / ", plot_daten$maximum)
ggplot(plot_daten, aes(x = subskala, y = erreicht)) +
geom_col(fill = AKZENT_FARBE, width = 0.6) +
geom_blank(aes(y = maximum)) +
geom_text(aes(label = beschriftung),
hjust = -0.15, size = 3.6, color = "#333333") +
facet_wrap(~subskala, scales = "free", ncol = 1, strip.position = "left") +
coord_flip(clip = "off") +
scale_y_continuous(expand = expansion(mult = c(0, 0))) +
theme_minimal(base_size = 12) +
theme(
strip.text = element_blank(),
strip.background = element_blank(),
axis.text.y = element_text(face = "bold", size = 9, color = "#333333", hjust = 1),
axis.ticks.y = element_blank(),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
panel.spacing = unit(14, "pt"),
axis.title = element_blank(),
plot.margin = margin(t = 5, r = 48, b = 5, l = 10)
)
}, bg = "transparent")
output$download_word = downloadHandler(
filename = function() {
erg = tryCatch(ergebnis_r(), error = function(e) NULL)
chiffre = if (is.list(erg) && identical(erg$typ, "erfolg") && nchar(erg$chiffre) > 0)
erg$chiffre else "export"
datum = if (is.list(erg) && identical(erg$typ, "erfolg") && !is.null(erg$datum_str))
tryCatch(
format(as.Date(erg$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("HASE_ADHSSB_", chiffre, "_", datum, ".docx")
},
content = function(file) {
erg = tryCatch(ergebnis_r(), error = function(e) NULL)
daten_ok = is.list(erg) && identical(erg$typ, "erfolg")
if (!daten_ok) {
doc = read_docx()
doc = body_add_par(doc,
"Kein Datensatz geladen. Bitte zuerst Chiffre oder Pseudonym eingeben und 'Auswerten' klicken.",
style = "Normal")
print(doc, target = file)
return()
}
doc = tryCatch(
erstelle_adhssb_docx(erg),
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)