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
BIT-C/.RData Normal file

Binary file not shown.

1
BIT-C/.Rprofile Normal file
View file

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

13
BIT-C/BIT-C.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

664
BIT-C/app.R Normal file
View file

@ -0,0 +1,664 @@
# Präambel ####
library(shiny)
library(dplyr)
library(ggplot2)
library(haven)
library(officer)
library(DBI)
library(RSQLite)
# Infrastruktur ####
PFAD_DOWNLOAD_SKRIPT = "../API/get_data_bitc.R"
PFAD_PSEUDONYM_SKRIPT = "../get_pseudo.R"
AKZENT_FARBE = "#8B2635"
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)
BITC_DISCLAIMER = paste0(
"Diese Auswertung ist ein Hilfsmittel fuer klinisches Fachpersonal und ersetzt ",
"keine klinische Diagnose. Die Interpretation obliegt der behandelnden Person."
)
bitc_items = data.frame(
nr = 1:63,
bereich = c(
rep("Bewältigung bestimmter Probleme und Symptome", 27),
rep("Ziele im zwischenmenschlichen Bereich", 16),
rep("Verbesserung des Wohlbefindens", 6),
rep("Orientierung im Leben", 3),
rep("Selbstbezogene Ziele", 11)
),
stichwort = c(
rep("Depressives Erleben", 4),
rep("Körperliche Selbstverletzung", 2),
rep("Ängste", 4),
rep("Zwanghafte Gedanken und Handlungen", 2),
rep("Traumatische Erlebnisse", 1),
rep("Suchtverhalten (bezogen auf Alkohol, illegale Drogen oder Medikamente)", 4),
rep("Essverhalten", 2),
rep("Schlaf", 1),
rep("Sexualität", 1),
rep("Körperliche Schmerzen und Krankheiten", 2),
rep("Schwierigkeiten in bestimmten Lebensbereichen", 3),
rep("Stress", 1),
rep("Bestehende Partnerschaft", 3),
rep("Elternschaft und aktuelle Familie", 3),
rep("Herkunftsfamilie", 1),
rep("Andere Beziehungen", 2),
rep("Alleinsein und Trauer", 2),
rep("Selbstbehauptung und Abgrenzung", 2),
rep("Kontakt und Nähe", 3),
rep("Bewegung und Aktivität", 2),
rep("Entspannung und Gelassenheit", 2),
rep("Wohlbefinden", 2),
rep("Vergangenheit, Gegenwart und Zukunft", 3),
rep("Einstellung zu mir selbst", 2),
rep("Bedürfnisse und Wünsche", 3),
rep("Verantwortung, Leistung und Kontrolle", 4),
rep("Umgang mit Gefühlen", 2)
),
text = c(
"negative, kreisende Gedanken oder Schuldgefühle überwinden.",
"aus meiner gedrückten Stimmung, Traurigkeit oder innerer Leere herauskommen.",
"mit Stimmungsschwankungen besser umgehen lernen.",
"wieder mehr Antrieb und Energie bekommen.",
"lernen, mir keine körperlichen Verletzungen mehr zuzufügen (z.B. mich absichtlich schneiden oder brennen).",
"Selbstmordgedanken zu überwinden und wieder Lebenswillen finden.",
"eine konkrete Angst bewältigen oder besser mit ihr umgehen lernen.",
"lernen, Angst- und Panikfällen in den Griff zu bekommen.",
"lernen, ohne Angst und unsicheres Verhalten (z.B. Erröten, Stottern) unter die Leute zu gehen.",
"lernen wieder Dinge zu tun, die ich jetzt aus Angst vermeide.",
"ständig wiederkehrende, quälende Gedanken oder Impulse besser kontrollieren lernen.",
"wiederholte, sinnlose und zeitraubende Handlungen (übertriebenes Händewaschen, Ordnen, Prüfen, Zählen etc.) einschränken lernen.",
"traumatische Erlebnisse verarbeiten (z.B. schwerer Unfall, Gewaltverbrechen, Vergewaltigung, Naturkatastrophe u.a.).",
"den körperlichen Entzug von Suchtmitteln durchführen.",
"ohne Suchtmittel leben lernen.",
"meinen Suchtmittelkonsum kontrollieren lernen.",
"mit schwierigen Situationen anders umgehen lernen, statt zu Suchtmittel zu greifen.",
"meine Essprobleme (Magersucht, Ess-Brechsucht, Esssucht etc.) bewältigen.",
"mit meinem Übergewicht umgehen lernen (es reduzieren oder so akzeptieren).",
"meine Schlafprobleme (Schwierigkeiten beim Ein- oder Durchschlafen, frühes Erwachen etc.) bewältigen.",
"sexuelle Probleme bewältigen.",
"mit körperlichen Schmerzen umgehen lernen.",
"mit meiner körperlichen Krankheit umgehen lernen.",
"meine Wohnsituation klären.",
"konkrete Probleme im Zusammenhang mit meiner Arbeit oder meiner Ausbildung bewältigen.",
"meinen Alltag besser organisieren lernen.",
"besser mit Stresssituationen umgehen lernen.",
"die Beziehung mit meinem Partner / meiner Partnerin verbessern.",
"das Sexualleben mit meinem Partner / meiner Partnerin verbessern.",
"meine Erwartungen und Gefühle bzgl. Partner / Partnerin klären.",
"mich mit meiner Vater- bzw. Mutterrolle auseinandersetzen.",
"die Beziehung zu meinem Kind / meinen Kindern verbessern.",
"versuchen, etwas an der ganzen familiären Situation zu verändern.",
"die Beziehung zu meinen Eltern verändern (mich ablösen, Schuldgefühle oder Abhängigkeit überwinden etc.).",
"die Beziehung zu bestimmten Personen aus dem privaten oder beruflichen Umfeld klären oder verbessern.",
"die Trennung von meinem Ex-Partner / meiner Ex-Partnerin besser verarbeiten.",
"meine Zeit allein verbringen lernen.",
"den Tod einer geliebten Person besser verarbeiten.",
"mich anderen gegenüber besser durchsetzen und abgrenzen lernen.",
"mit den Reaktionen anderer (Kritik, Ablehnung, Lob etc.) auf mein Verhalten besser umgehen lernen.",
"lernen, besser mit Menschen Kontakt aufzunehmen und zu pflegen (z.B. lernen, wie ich Leute kennenlerne, Freundschaften aufbaue etc.).",
"Nähe zulassen und Vertrauen zu anderen Menschen aufbauen lernen.",
"mich auf eine neue Partnerschaft vorbereiten.",
"mehr Sport und andere körperliche Aktivitäten betreiben.",
"meine Freizeit aktiver gestalten (Hobbies, kulturelle Aktivitäten etc.).",
"Techniken erlernen, die mir helfen, mich zu entspannen.",
"lernen, Probleme und Herausforderungen gelassener anzugehen.",
"mehr Optimismus und Lebensfreude entwickeln.",
"lernen, mich in meinem Körper wohl zu fühlen.",
"mit Teilen meiner Vergangenheit besser zurechtkommen.",
"mir klarer werden, wer ich bin, was ich kann und was ich will.",
"neue Zukunftsperspektiven (private oder berufliche) erarbeiten.",
"mehr Selbstvertrauen und Selbstsicherheit entwickeln.",
"mich akzeptieren lernen so wie ich bin.",
"meine Bedürfnisse wahrnehmen und ausdrücken lernen.",
"meine Grenzen besser erkennen und danach handeln lernen.",
"eigene Wünsche und Pläne besser verwirklichen lernen.",
"lernen, Verantwortung für mich selbst zu übernehmen (eigenständig leben, Entscheidungen treffen usw.).",
"mehr Selbstdisziplin und Durchhaltevermögen entwickeln.",
"meine hohen Ansprüche an mich oder an andere herabsetzen lernen.",
"Verantwortung und Kontrolle abgeben lernen.",
"Gefühle zulassen und äußern lernen.",
"mit starken negativen Gefühlen (z.B. Ärger, Wutausbrüchen) umgehen lernen."
),
stringsAsFactors = FALSE
)
# Helper ####
item_angekreuzt = function(x) {
if (!length(x)) return(FALSE)
x = suppressWarnings(as.numeric(x[[1]]))
isTRUE(!is.na(x) && x != 0)
}
item_volltext = function(nr, text) {
paste0(nr, ". Mit Hilfe der Psychotherapie möchte ich ", text)
}
lookup_item = function(nr) {
treffer = bitc_items[bitc_items$nr == nr, , drop = FALSE]
if (nrow(treffer) == 0) return(NULL)
treffer[1, ]
}
prio_item_aufloesen = function(ziel_nr, zusatzziel_texte) {
if (is.na(ziel_nr)) return(NULL)
ziel_nr = as.integer(ziel_nr)
if (ziel_nr >= 1 && ziel_nr <= 63) {
item = lookup_item(ziel_nr)
if (is.null(item)) {
return(list(nr = ziel_nr, text = NULL, warnung = paste0("Ziel Nr. ", ziel_nr, " konnte keinem bekannten Item zugeordnet werden.")))
}
return(list(nr = ziel_nr, text = item_volltext(item$nr, item$text), warnung = NULL))
}
if (ziel_nr >= 64 && ziel_nr <= 67) {
idx = ziel_nr - 63
freitext = zusatzziel_texte[[idx]]
if (is.null(freitext) || !nzchar(trimws(freitext))) {
return(list(nr = ziel_nr, text = paste0("(Eigenes Ziel ", idx, " kein Text eingetragen)"), warnung = NULL))
}
return(list(nr = ziel_nr, text = freitext, warnung = NULL))
}
list(nr = ziel_nr, text = NULL, warnung = paste0("Ziel Nr. ", ziel_nr, " liegt außerhalb des gültigen Bereichs (167)."))
}
render_alle_angekreuzten = function(angekreuzte_nrs, ausgefuellte_zusatzziele) {
if (length(angekreuzte_nrs) == 0 && length(ausgefuellte_zusatzziele) == 0) {
return(div(class = "item-zeile", "Keine angekreuzten Ziele gefunden."))
}
bereiche_geordnet = unique(bitc_items$bereich)
bereich_tags = lapply(bereiche_geordnet, function(bereich) {
items_b = bitc_items[bitc_items$bereich == bereich & bitc_items$nr %in% angekreuzte_nrs, , drop = FALSE]
if (nrow(items_b) == 0) return(NULL)
stichworte_geordnet = unique(items_b$stichwort)
sw_tags = lapply(stichworte_geordnet, function(sw) {
items_sw = items_b[items_b$stichwort == sw, , drop = FALSE]
item_divs = lapply(seq_len(nrow(items_sw)), function(i) {
it = items_sw[i, ]
div(class = "item-zeile",
span(class = "item-nr", paste0(it$nr, ".")),
span(class = "item-text",
paste0("Mit Hilfe der Psychotherapie möchte ich ", it$text))
)
})
tagList(
div(style = "font-weight:600; font-style:italic; margin-top:10px; margin-bottom:4px; color:#555;", sw),
tagList(item_divs)
)
})
tagList(
h4(style = "margin-top:16px; margin-bottom:4px;", bereich),
tagList(sw_tags)
)
})
bereich_tags = Filter(Negate(is.null), bereich_tags)
zusatz_tags = if (length(ausgefuellte_zusatzziele) > 0) {
z_divs = lapply(ausgefuellte_zusatzziele, function(z) {
div(class = "item-zeile",
span(class = "item-nr", paste0(z$nr, ".")),
span(class = "item-text", z$text)
)
})
tagList(h4(style = "margin-top:16px;", "Eigene Therapieziele"), tagList(z_divs))
} else NULL
tagList(bereich_tags, zusatz_tags)
}
# 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; }
.alert-fehler {
background: #FFEBEE; border-left: 5px solid #C62828;
padding: 12px 16px; margin-bottom: 16px;
border-radius: 0 4px 4px 0; color: #7a0000;
}
.alert-warnung {
background: #FFF8E1; border-left: 5px solid #F9A825;
padding: 12px 16px; margin-bottom: 16px;
border-radius: 0 4px 4px 0; color: #6d3600;
}
.abschnitt-karte {
background: #fff; border: 1px solid #e0e0e0; border-radius: 6px;
padding: 16px; margin-bottom: 14px; box-shadow: 0 1px 3px rgba(0,0,0,.06);
}
.abschnitt-titel {
font-size: 1.05em; font-weight: bold; color: #8B2635;
margin-bottom: 10px; border-bottom: 2px solid #8B2635; padding-bottom: 6px;
}
.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; }
.prio-originaltext { color: #555; font-size: 0.93em; margin-top: 6px; }
.item-zeile {
display: flex; align-items: baseline; padding: 5px 0;
border-bottom: 1px solid #f0f0f0;
}
.item-zeile:last-child { border-bottom: none; }
.item-nr { font-weight: 600; min-width: 36px; color: #555; flex-shrink: 0; }
.item-text { flex: 1; }
"
app_css = gsub("#8B2635", AKZENT_FARBE, app_css, fixed = TRUE)
ui = fluidPage(
tags$head(tags$style(HTML(app_css))),
div(class = "app-header",
tags$h2("BIT-C — Berner Inventar für Therapieziele"),
tags$p("Revidierte Version | Therapieziele und Priorisierung")
),
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: 180px;",
textInput("chiffre", label = "Patientenchiffre",
placeholder = "z.B. P000123", width = "180px")
),
actionButton("btn_suchen", "Auswerten", class = "btn-laden"),
div(style = "margin-left: auto;",
downloadButton("download_word", "Word-Export (.docx)",
style = paste0(
"background:", AKZENT_FARBE, "; color:white; border:none;",
" font-weight:600; padding:8px 20px; border-radius:4px;"
)
)
)
),
div(class = "ergebnis-container",
uiOutput("ergebnis_ui")
)
)
)
# Word-Export ####
erstelle_bitc_docx = function(erg) {
doc = read_docx()
stil_titel = fp_text(color = AKZENT_FARBE, bold = TRUE, font.size = 18)
stil_h2 = fp_text(bold = TRUE, font.size = 13)
stil_h3 = fp_text(bold = TRUE, font.size = 11, italic = TRUE)
stil_fett = fp_text(bold = TRUE, font.size = 11)
stil_kursiv = fp_text(italic = TRUE, font.size = 11)
stil_normal = fp_text(font.size = 11)
stil_disclaimer = fp_text(font.size = 9, color = "#666666", italic = TRUE)
doc = body_add_fpar(doc, fpar(ftext("BIT-C — Therapieziele", prop = stil_titel)))
doc = body_add_fpar(doc, fpar(ftext(paste0("Patientenchiffre: ", erg$chiffre), prop = stil_normal)))
doc = body_add_fpar(doc, fpar(ftext(paste0("Ausfülldatum: ", erg$ausfuelldatum_anzeige), prop = stil_normal)))
doc = body_add_fpar(doc, fpar(ftext("", prop = stil_normal)))
doc = body_add_fpar(doc, fpar(ftext("Priorisierte Ziele", prop = stil_h2)))
for (z in erg$prio_ziele) {
slot_label = paste0("Ziel ", z$slot)
if (!is.null(z$warnung)) {
doc = body_add_fpar(doc, fpar(ftext(paste0(slot_label, ": "), prop = stil_fett),
ftext(z$warnung, prop = stil_kursiv)))
} else {
formulierung = if (!is.null(z$eigene_formulierung) && nzchar(trimws(z$eigene_formulierung)))
z$eigene_formulierung else "(keine eigene Formulierung angegeben)"
doc = body_add_fpar(doc, fpar(ftext(paste0(slot_label, ": "), prop = stil_fett),
ftext(formulierung, prop = stil_fett)))
doc = body_add_fpar(doc, fpar(ftext(z$text, prop = stil_normal)))
}
doc = body_add_fpar(doc, fpar(ftext("", prop = stil_normal)))
}
doc = body_add_fpar(doc, fpar(ftext("Alle angekreuzten Therapieziele (Übersicht)", prop = stil_h2)))
bereiche_geordnet = unique(bitc_items$bereich)
for (bereich in bereiche_geordnet) {
items_b = bitc_items[bitc_items$bereich == bereich & bitc_items$nr %in% erg$angekreuzte_item_nrs, , drop = FALSE]
if (nrow(items_b) == 0) next
doc = body_add_fpar(doc, fpar(ftext(bereich, prop = stil_h2)))
stichworte = unique(items_b$stichwort)
for (sw in stichworte) {
items_sw = items_b[items_b$stichwort == sw, , drop = FALSE]
doc = body_add_fpar(doc, fpar(ftext(sw, prop = stil_h3)))
for (i in seq_len(nrow(items_sw))) {
it = items_sw[i, ]
doc = body_add_fpar(doc, fpar(ftext(item_volltext(it$nr, it$text), prop = stil_normal)))
}
}
}
if (length(erg$ausgefuellte_zusatzziele) > 0) {
doc = body_add_fpar(doc, fpar(ftext("Eigene Therapieziele", prop = stil_h2)))
for (z in erg$ausgefuellte_zusatzziele) {
doc = body_add_fpar(doc, fpar(ftext(z$text, prop = stil_normal)))
}
}
doc = body_add_fpar(doc, fpar(ftext("", prop = stil_normal)))
doc = body_add_fpar(doc, fpar(ftext(BITC_DISCLAIMER, prop = stil_disclaimer)))
doc
}
# Server ####
server = function(input, output, session) {
# --- pseudonym-support-injection v1 ---
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)))
}
})
erg = eventReactive(input$btn_suchen, {
chiffre = toupper(trimws(input$chiffre))
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 = "datei_fehlt", pfad = PFAD_DOWNLOAD_SKRIPT))
}
if (!file.exists(PFAD_PSEUDONYM_SKRIPT)) {
return(list(typ = "datei_fehlt", pfad = 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
})
if (is.null(db_ordner)) {
return(list(typ = "datei_fehlt",
pfad = "pseudonyme.db (nicht gefunden; bis 5 Ebenen oberhalb des Pseudonym-Skripts gesucht)"))
}
alter_wd = getwd()
on.exit(setwd(alter_wd), add = TRUE)
setwd(db_ordner)
ok_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 (!ok_ps$ok) return(list(typ = "skript_fehler", meldung = ok_ps$msg))
if (!exists("daten_bitc", envir = .GlobalEnv) || !exists("pseudo", envir = .GlobalEnv)) {
return(list(typ = "skript_fehler",
meldung = "Objekte 'daten_bitc' oder 'pseudo' nach dem Sourcen nicht vorhanden."))
}
daten_bitc = get("daten_bitc", envir = .GlobalEnv)
pseudo = get("pseudo", envir = .GlobalEnv)
treffer_pseudo = pseudo[pseudo$chiffre == chiffre, , drop = FALSE]
if (nrow(treffer_pseudo) == 0) {
return(list(typ = "chiffre_nicht_gefunden", chiffre = chiffre))
}
session_id = treffer_pseudo$pseudonym[1]
if (nchar(trimws(input$pseudonym)) > 0) session_id = trimws(input$pseudonym)
# formr-Exporte verwenden typischerweise 'session' als Session-ID-Spalte
if (!"session" %in% names(daten_bitc)) {
return(list(typ = "skript_fehler",
meldung = "Spalte 'session' nicht in daten_bitc gefunden. Spaltenstruktur des Exports prüfen."))
}
treffer_daten = daten_bitc[daten_bitc$session == session_id, , drop = FALSE]
if (nrow(treffer_daten) == 0) {
return(list(typ = "keine_daten", chiffre = chiffre))
}
warnung_mehrfach = NULL
if (nrow(treffer_daten) > 1) {
if ("created" %in% names(treffer_daten)) {
treffer_daten = treffer_daten[order(treffer_daten$created, decreasing = TRUE), , drop = FALSE]
datum_str = tryCatch(format(as.POSIXct(treffer_daten$created[1]), "%d.%m.%Y %H:%M"), error = function(e) as.character(treffer_daten$created[1]))
}
warnung_mehrfach = paste0(
"Achtung: Für diese Chiffre wurden ", nrow(treffer_daten),
" Ausfüllungen gefunden. Es wird die neueste verwendet (", datum_str, ")."
)
}
zeile = treffer_daten[1, , drop = FALSE]
# Ausfülldatum ermitteln
# Fallback auf Sys.Date() falls 'created' fehlt oder nicht parsebar
ausfuelldatum_anzeige = "unbekannt"
ausfuelldatum_fn = format(Sys.Date(), "%Y%m%d")
if ("created" %in% names(zeile)) {
parsed = tryCatch(as.POSIXct(zeile$created[1]), error = function(e) NULL)
if (!is.null(parsed) && !is.na(parsed)) {
ausfuelldatum_anzeige = format(parsed, "%d.%m.%Y")
ausfuelldatum_fn = format(parsed, "%Y%m%d")
}
}
# Angekreuzte Items ermitteln (item1 bis item63)
angekreuzte_item_nrs = integer(0)
for (i in 1:63) {
spalte = paste0("item", i)
if (spalte %in% names(zeile)) {
if (item_angekreuzt(zeile[[spalte]])) {
angekreuzte_item_nrs = c(angekreuzte_item_nrs, i)
}
}
}
# Ausgefüllte Zusatzziele ermitteln (Nummern 64-67)
ausgefuellte_zusatzziele = list()
for (i in 1:4) {
spalte = paste0("zusatzziel_text_", i)
if (spalte %in% names(zeile)) {
txt = trimws(as.character(zeile[[spalte]][[1]]))
if (!is.na(txt) && nzchar(txt) && txt != "NA") {
ausgefuellte_zusatzziele = c(ausgefuellte_zusatzziele, list(list(nr = 63L + i, text = txt)))
}
}
}
# Zusatzziel-Texte als benannter Vektor für die Auflösung
zusatzziel_texte = lapply(1:4, function(i) {
spalte = paste0("zusatzziel_text_", i)
if (spalte %in% names(zeile)) {
txt = trimws(as.character(zeile[[spalte]][[1]]))
if (!is.na(txt) && nzchar(txt) && txt != "NA") return(txt)
}
NULL
})
# Priorisierte Ziele aufbauen
prio_ziele = list()
warnung_ziele = character(0)
for (slot in 1:5) {
nr_spalte = paste0("ziel_nr_", slot)
prio_spalte = paste0("ziel_prio_", slot)
ziel_nr = if (nr_spalte %in% names(zeile)) zeile[[nr_spalte]][[1]] else NA_integer_
if (is.na(ziel_nr)) next
aufgeloest = prio_item_aufloesen(ziel_nr, zusatzziel_texte)
if (is.null(aufgeloest)) next
eigene_formulierung = ""
if (prio_spalte %in% names(zeile)) {
val = zeile[[prio_spalte]][[1]]
if (!is.na(val)) eigene_formulierung = as.character(val)
}
eintrag = list(
slot = slot,
nr = aufgeloest$nr,
text = aufgeloest$text,
eigene_formulierung = eigene_formulierung,
warnung = aufgeloest$warnung
)
if (!is.null(aufgeloest$warnung)) {
warnung_ziele = c(warnung_ziele, aufgeloest$warnung)
}
prio_ziele = c(prio_ziele, list(eintrag))
}
list(
typ = "erfolg",
chiffre = chiffre,
ausfuelldatum_anzeige = ausfuelldatum_anzeige,
ausfuelldatum_fn = ausfuelldatum_fn,
warnung_mehrfach = warnung_mehrfach,
warnung_ziele = warnung_ziele,
prio_ziele = prio_ziele,
angekreuzte_item_nrs = angekreuzte_item_nrs,
ausgefuellte_zusatzziele = ausgefuellte_zusatzziele
)
})
output$ergebnis_ui = renderUI({
req(input$btn_suchen)
e = erg()
if (e$typ == "format_fehler") {
return(div(class = "alert-fehler",
"Die Chiffre entspricht nicht dem erwarteten Format (Buchstabe gefolgt von 6 Ziffern, z.B. P000123)."))
}
if (e$typ == "datei_fehlt") {
return(div(class = "alert-fehler",
paste0("Skript-Datei nicht gefunden: ", e$pfad)))
}
if (e$typ == "skript_fehler") {
return(div(class = "alert-fehler",
paste0("Fehler beim Ausführen eines Skripts: ", e$meldung)))
}
if (e$typ == "chiffre_nicht_gefunden") {
return(div(class = "alert-fehler",
"Keine Pseudonymisierungs-Zuordnung für diese Chiffre gefunden."))
}
if (e$typ == "keine_daten") {
return(div(class = "alert-fehler",
"Für diese Person liegen keine BIT-C-Daten vor."))
}
warnungen = tagList(
if (!is.null(e$warnung_mehrfach)) div(class = "alert-warnung", e$warnung_mehrfach),
if (length(e$warnung_ziele) > 0)
div(class = "alert-warnung", lapply(e$warnung_ziele, function(w) div(w)))
)
prio_cards = tagList(lapply(e$prio_ziele, function(z) {
titel = paste0("Ziel ", z$slot)
inhalt = if (!is.null(z$warnung)) {
div(class = "alert-warnung", style = "margin-top:6px;", z$warnung)
} else {
tagList(
div(style = "font-weight:600;",
if (nzchar(trimws(z$eigene_formulierung)))
z$eigene_formulierung
else
em(style = "font-weight:normal;", "(keine eigene Formulierung angegeben)")
),
div(class = "prio-originaltext", z$text)
)
}
div(class = "abschnitt-karte",
div(class = "abschnitt-titel", titel),
inhalt
)
}))
uebersicht = div(class = "abschnitt-karte",
div(class = "abschnitt-titel", "Alle angekreuzten Therapieziele (Übersicht)"),
render_alle_angekreuzten(e$angekreuzte_item_nrs, e$ausgefuellte_zusatzziele)
)
tagList(
warnungen,
h3(style = "margin-top:8px;", "Priorisierte Ziele"),
prio_cards,
uebersicht
)
})
output$download_word = downloadHandler(
filename = function() {
e = tryCatch(erg(), error = function(x) NULL)
if (is.null(e) || e$typ != "erfolg") return("BITC_export.docx")
chiffre_esc = gsub("[^A-Za-z0-9]", "_", e$chiffre)
paste0("BITC_", chiffre_esc, "_", e$ausfuelldatum_fn, ".docx")
},
content = function(file) {
e = tryCatch(erg(), error = function(x) NULL)
if (is.null(e) || e$typ != "erfolg") return(invisible(NULL))
doc = erstelle_bitc_docx(e)
print(doc, target = file)
}
)
}
# Start ####
shinyApp(ui, server)

3019
BIT-C/renv.lock Normal file

File diff suppressed because it is too large Load diff