bfh_qic(ylim = ...) -- y-akse-visningsgrænser. Ny valgfri parameter
ylim = c(min, max) der zoomer y-aksen analogt med
ggplot2::coord_cartesian(ylim = ...). Værdier er i plottets data-skala
(proportion-charts p/pp bruger 0-1-skalaen, fx c(0, 1) viser 0%-100%;
øvrige charts bruger absolut skala). NA per ende giver fri grænse
(c(0, NA) = fast bund, datadrevet top). Grænsen sættes på det eksisterende
lemon::coord_capped_cart, så BFH-akse-caps bevares. Da coord zoomer og
ikke clipper, droppes datapunkter og kontrolgrænse-segmenter uden for
range IKKE (modsat scale_y-limits) -- charten zoomer blot til vinduet.
Ugyldig min >= max ignoreres med en advarsel. Default NULL bevarer
uændret, datadrevet adfærd.bfh_subsample_label_indices() dropper force-last anchor. I 0.22.0 blev
n_labels altid appendet som sidste anchor selv naar det braed step-rhythmen
(fx n=24/step=3 endte med diffs c(3,3,3,3,3,3,3,2) -- sidste gap=2 stak ud
visuelt). Nu inkluderes sidste index n_labels KUN naar (n_labels - 1)
er delelig med step; ellers slutter sekvensen ved den hoejeste step-alignede
position <= n_labels. Resultat: konstant rhythm hele vejen igennem.
| n | step | sidste index | 0.22.0 (force-last) | 0.22.1 (kun grid) | |-----|------|--------------|-------------------------|--------------------------| | 24 | 3 | 22 (ej 24) | length=9, gap-tail=2 | length=8, alle diffs=3 | | 36 | 4 | 33 (ej 36) | length=10, gap-tail=3 | length=9, alle diffs=4 | | 52 | 5 | 51 (ej 52) | length=12, gap-tail=1 | length=11, alle diffs=5 | | 100 | 9 | 100 (grid) | length=12, alle diffs=9 | uaendret |
Callers der har brug for sidste label som anker skal appende det eksplicit.
Downstream consumers (biSPCharts) der renderer subsampled labels via helperen
vil se sidste label droppet for n hvor (n - 1) ej er delelig med step.
bfh_subsample_label_indices() returnerer faerre indices for moderate n.
Algoritmen skifter fra round(seq(1, n, length.out = max_visible)) til
deterministisk step-baseret thinning med force-last anchor. Konsekvens:
| n | foer (0.21.0) | nu (0.22.0) | |-----|------------------------------------------|------------------------------------------| | 24 | 12 indices, gap 11->14 (skjulte 12+13) | 9 indices, konstant step=3 | | 36 | 12 indices | 10 indices, konstant step=4 | | 52 | 12 indices | 12 indices, konstant step=5 | | 100 | 12 indices | 12 indices, konstant step=9 |
Downstream consumers (biSPCharts) der renderer x-akse-labels via helperen faar sparser men deterministisk label-distribution uden gap-of-3 anti- pattern. API-signatur uaendret.
(#396) Strukturel gap-bug i bfh_subsample_label_indices() for n=24.
round(seq.int(1L, n, length.out = max_visible)) producerede ikke-heltals
positioner som snappede asymmetrisk via R's banker's rounding. For
n=24/max=12 gav det indices 1 3 5 7 9 11 14 16 18 20 22 24 -- gap 11->14
skjulte to konsekutive labels (12+13) midt i serien. I biSPCharts SPC-
eksport ramte dette aarsskifte-labels (dec 25 + jan 26) ved 2-aars
maaneds-serier.
Fix: deterministisk integer step
ceiling((n_labels - 1) / (max_visible - 1)) + force-last anchor
garanterer konstant interval i "showing" pattern -- aldrig to konsekutive
skjulte labels.
bfh_subsample_label_indices(n_labels, max_visible = BFH_MAX_X_LABELS_TEXT)
— eksporteret helper der returnerer evenly-spaced indices til
subsample af kategoriske x-akse-labels. Foerste og sidste indices
anchored som anker-points, intermediate via
round(seq(1, n, length.out = max_visible)). Skalerer smoothly:
n <= max_visible giver alle indices, n > max_visible thinner
progressivt. Designet til tekst-x-data (maanedsnavne, ugedage,
observation-id) hvor visning af alle labels ville overlappe.
BFH_MAX_X_LABELS_TEXT konstant (default 12L) — eksporteret
styling-default for [bfh_subsample_label_indices()]. Threshold valgt
til at passe standard A4 PDF eksport-bredde (200mm @ 300dpi) med
horisontale Roboto Medium labels.
bfh_generate_details(x, language, x_labels = NULL) udvidet med
valgfri x_labels-parameter. Naar sat (character vector, length =
nrow(qic_data)), bruges foerste og sidste label til Periode-felt
(fx "Periode: januar . december") i stedet for dato-baseret
formatering paa qic_data\$x. Interval-label bliver "kategori".
Bagudkompatibel: x_labels = NULL bevarer eksisterende dato-flow.
Use case: downstream pipelines (biSPCharts) konverterer tekst-x til
numerisk sekvens FOER bfh_qic()-kald for at stoette ggplot-rendering.
Tidligere reflekterede Periode-feltet i PDF-eksport den numeriske
sekvens (fx "Periode: 1970-01-01 . 1970-01-12") frem for original-
labels.
BFHtheme (>= 0.5.1) — kraever font-detection-fix hvor
BFHtheme::font_available() konsulterer baade system_fonts() og
registry_fonts(). Uden bumpet ville downstream konsumenter (fx
biSPCharts) der registrerer Mari via systemfonts::register_font() i
runtime fortsat se "Mari ikke fundet" og fallback til Roboto/sans paa
deploy-targets uden OS-installeret Mari (Posit Connect Cloud).Struktureret SPC-analyse via bfh_analyse() + bfh_render_analysis()
(ADR-004). Tre-lags arkitektur erstatter monolitisk
build_fallback_analysis()-cascade:
bfh_extract_spc_features(x, metadata) — pure deterministisk
feature-extraction af 12 ortogonale fortolknings-akserbfh_analyse(x, metadata, language) — komposerer struktureret
bfh_spc_analysis S3-objekt (key-only model: i18n-nøgler, ej
resolverede strenge)bfh_render_analysis(analysis, max_chars, texts_loader) —
resolverer nøgler til character via texts_loaderUse-cases: PDF-eksport (eksisterende bfh_generate_analysis()
delegerer til ny pipeline; backward-compatibel), app-UI badge-
rendering, AI-prompt-anker, audit-replay, JSON-export.
7 nye fortolknings-akser aktiveret via modifier-cascade:
features$magnitude lagrer sigma-shift-
bucket (small/medium/large). Rendres som "(~30% forandring)"-clause
appendet til stability-text.metadata$direction = higher_better / lower_better / neutral driver favorable/unfavorable-vurdering via
baseline-delta. Rendres som "i den ønskede retning"-clause.part >= 2
rendres "Niveauet er flyttet fra X til Y siden tidligere fase".n < N_MIN (= 12 per
Anhøj 2014) erstatter stability-base med "for kort serie"-tekst.
Target+action arms skippes ved low-confidence.cl_user_supplied +
cl_auto_mean caveats appender til prose (var hidtil kun i
PDF-sidebar).n_on_cl_ratio mapper til
4-tier enum (none/mild/moderate/extreme). Mild/moderate som tail-
caveat; extreme bevarer eksisterende majority_at_centerline-base-
override.Chart-type-aware confidence-tier: Run-charts har sigma_hat = NA
by design. is.na(sigma_hat) alene udløser IKKE confidence_tier = "low" — bruger sigma_data (sd(y)) som spread-estimat for run-
charts. Tidligere logik ville have fejlmarkeret valide 24-punkt run-
charts som "for kort serie".
analysis_date 3-vejs præcedens (R/utils_analysis_date.R):
Driver deterministisk freshness-detektion og audit-replay-evne.
Resolution-order: metadata$analysis_date >
getOption("BFHcharts.analysis_date") > Sys.Date(). Resolvet
værdi lagres i analysis$aux$analysis_date.
Cycle 04 (final-state branch-review, 2026-05-18) + cycle 05 (user-led audit efter cycle 04 fixes) identificerede 14 fund. Fixes:
H1 (HIGH, cycle 04): Mixed decimal-separators ved language="en".
render_context$centerline_formatted pre-computed med hardcoded "da";
baseline-delta-modifier mixede engelske/danske decimal-tegn. Fix:
centerline formaters ved render-time med caller-language.
H4 (HIGH, cycle 04): features$stability_pattern advertise
"not_evaluable" men sattes aldrig. Render erstattede template ved
low-confidence, men feature-key forblev "runs_only" etc. Fix:
.resolve_stability_pattern() returnerer "not_evaluable" naar
confidence_tier == "low".
H5 (MEDIUM, cycle 04): Fixed 200L modifier-budget producerede
brudt prose ved max_chars=200. Fix: proportional modifier-budget
baseret paa max_chars * 0.25 / n_modifiers.
N1 (MEDIUM, cycle 04): render_context$effective_window
spec-required men ikke implementeret. Fix: tilfoejet til render_context
H2 (MEDIUM, cycle 05): .compute_magnitude accepterede
microscopic sigma (sigma_hat ~ 1e-12 fra near-constant data) ->
float-noise baseline-delta klassificeret som "large". Fix:
MAGNITUDE_RATIO_CAP = 100 returnerer NA ved ratios over cap.
H3 (MEDIUM, cycle 05): Spec kraevede aux$data_age_days men
Slice 10 (Freshness) SKIP-besluttet -> ingen detection-use-case.
Fix: spec-konsistens (data_age_days fjernet fra required-list).
Cycle 05 finding #1 (HIGH): detect_variable_cl() udloeste falsk
caveat paa I-charts med phase-split (CL-variation drevet af phase,
ej n). Fix: chart-class-gate -- caveat aktiv kun paa subgrouped
chart-classes (proportion/rate/subgrouped).
Cycle 05 finding #2 (HIGH): Modifier-konkatenering producerede
grammatisk brudt prose ("... uden tegn paa systematiske aendringer.
(en betydelig forandring paa ~10%). i den oenskede retning. Fra X
til Y."). Fix: .compose_modifier_sentence() bygger EN sammen-
haengende saetning. Templates redesignet som clause-form (lowercase,
ingen trailing period); composer vaelger sentence-frame baseret paa
hvilke modifiers er aktive.
Cycle 05 finding #4 (MEDIUM): AI-path signals_detected ud-
ledtes af stability_pattern != no_signals/no_variation. Data-
quality states (not_evaluable, majority_at_centerline) lekkede
som "signaler" til BFHllm. Fix: brug runs/crossings/outliers flags
direkte (sande Anhoej-signaler).
Cycle 05 finding #5 (MEDIUM): not_evaluable-prose sagde altid
"Med kun {n_points} observationer..." selv naar n >= N_MIN men
centerline/spread manglede -- klinisk misvisende. Fix: ny
low_confidence_reason-feature-akse (few_obs/no_centerline/
no_spread) driver template-dispatch. Render-prose matcher faktisk
aarsag.
Cycle 05 finding #6 (LOW): chart_class mappede xbar/s til
"individuals". Fix: nu "subgrouped" (matcher hvordan sigma/CL
beregnes).
Cycle 05 tekst-stramninger:
Test-cleanup: test-spc_parity_phase1.R tautologisk post Phase 2
cut-over (bfh_generate_analysis() delegerer internt til samme
pipeline). 12 sammenligning-tests skip'es med rationale; determinism-
test bevares som idempotens-gate. Snapshot-tests mod frozen baseline
er planlagt follow-up.
Cycle 07 (post-merge audit via BFHddl-integration, 2026-05-18) identificerede 4 fund:
MEDIUM (priority-aware trim): bfh_render_analysis() brugte
blind ensure_within_max()-trim fra slutningen. Ved tight budget
med aktiv modifier-saetning kunne klinisk vigtig action-tekst
klippes vaek mens optional modifier + tail-caveats bevares. Fix:
priority-drop af modifier > tail > target FOR blind clip.
stability + action er klinisk must-keep.
LOW (prose-niveauforandring): magnitude.detailed brugte
dobbeltkonstruktion "i niveauet i den oenskede retning". Fix:
"niveauforandring" substantiv (begge locales).
LOW (variable_cl hedge): "varierer fordi stikproevestoerrelse aendrer sig" -> "varierer, typisk fordi naevner/stikproevestoerrelse aendrer sig". Koden infererer fra UCL/LCL-spaend, ej direkte fra n.
LOW (cl_auto_mean revert): Reviewer #3 anbefalede genindfoersel af Anhoej-rationale. Bruger-override efter klinisk test via BFHddl: kort variant tilstraekkelig (matcher PR #379-fix paa develop).
bfh_spc_analysis S3-class (R/bfh_spc_analysis_class.R):
constructor + validator + print / format / as.list-methods.
Schema-version "1.0.0" via BFH_SPC_ANALYSIS_SCHEMA_VERSION-konstant,
bumpes uafhængigt af pakke-version.
N_MIN = 12L-konstant + BFHCHARTS_OPT_ANALYSIS_DATE-konstant
tilføjet til R/globals.R.
Shared helpers i R/spc_analysis.R:
.compute_level_keys() — fælles (direction_key, vs_target_key)-
triplet for legacy build_fallback_analysis() + ny spc_render.R..normalize_target_operators() — fælles ASCII >=/<= → Unicode
≥/≤ konvertering for legacy + ny pipeline.build_fallback_analysis() markeret som @keywords internal @noRd
backward-compat-layer; vil blive fjernet i næste major release.
Bilingual parity CI-gate (tests/testthat/test-i18n_bilingual_parity.R,
61 tests): YAML key-paritet + placeholder-paritet (med
KNOWN_DIVERGENCES-whitelist for 11 pre-existing legacy-mismatches) +
format_target_value()-decimal-separator pr. sprog.
Schema-stability tests (tests/testthat/test-spc_schema_stability.R,
23 tests): Top-level fields invariant, JSON-roundtrip,
.simulate_downstream_consumer()-sentry mod breaking changes for
biSPCharts.
Golden-corpus snapshot (tests/testthat/test-spc_golden_corpus.R +
tests/testthat/_snaps/spc_golden_corpus.md, 12 snapshots):
Parametric sweep over stability × target × language × budget-matrix.
Regression-gate ved enhver render-output-aendring.
inst/adr/ADR-004-structured-spc-analysis-architecture.mdopenspec/changes/archive/2026-05-18- restructure-spc-analysis-architecture/docs/reviews/03-structured-spc-analysis- proposal-2026-05-17.mdAuto-substitution af median → gennemsnit håndterer nu multi-phase,
exclude= og Date+multiply korrekt (cycle 02 dual-review).
Tidligere version fra 0.18.0 havde tre distinkte bugs:
qic_data$include. Excluded outliers forurenede ny CL. Fix:
include-mask i begge steps.raw_x %in% qd$x returnerede all-
FALSE for Date-kolonner. Den nye cl-vektor blev all-NA →
qicharts2 fallback til median for hele trigger-fasen, men
cl_auto_mean-flag (og PDF-caveat) fyrede alligevel. Samtidig
multiplicerede qicharts2 vores user-cl med multiply så
percent-skalerede mean-værdier blev 100× for store. Fix: cross-
class x-normalisering + multiply-divider + guard mod silent-fail
flag-sætning.Cycle 02 review: docs/reviews/02-cl-auto-mean-validation-2026-05-16.md.
validate_denominator_data() tillader nu n = 0 for ratio-charts
(p/pp/u/up). Tidligere kastede funktionen hård fejl; nu beregnes
værdier af qicharts2 som NaN (punkter tegnes ej i plottet). Klinisk
fortolkning: n = 0 = "ingen patienter den periode" = valid input.
Centerline beregnes fra valide rækker (sum-aggregation), uændret af
n=0-rækker. Ramt downstream: biSPCharts fct_spc_plot_generation.R
pre-filter (linje 128-148) er nu overflødig og bør fjernes.y > n for proportion-charts (p/pp) tillades nu. qicharts2 plotter
proportion > 1 som outlier-signal over ucl-linjen (capped på 1.0).
Tidligere fejlede valideringen med "y <= n"-besked.n = Inf/-Inf fejler stadig hård — Inf forurener hele plottet med
cl/ucl=0 (empirisk verificeret).n < 0 fejler stadig hård. Fejlbesked opdateret fra "must be > 0"
til "must be >= 0, got negative values at row(s): X. Negative
denominators pollute centerline computation." Negative denominatorer
forurener cl-beregning globalt: ALL n<0 → cl<0; mixed n<0 → cl
forurenes af negative bidrag i sum-aggregation.Auto-substitution af median → gennemsnit ved diskret run-chart-data.
Når mindst 50 % af observationerne i seneste fase af et run chart
ligger eksakt på medianen (typisk symptom på grov rapportering eller
diskret målestok), skifter bfh_qic() automatisk seneste fases
centerlinje fra median til gennemsnit. Tidligere faser beholder
medianen. Anhøj-signalerne rekomputeres mod den nye centerlinje,
så analysen ikke kollapser i meningsløse tællinger på grund af
ligheder mellem y-værdier og CL. Detection sker via et
probe→retry-mønster (to qicharts2-kald) og piggybackbar på
qicharts2's cl=NA-fallback til at bevare tidligere fasers median.
PDF-caveat-feltet flagger substitutionen som "Niveaulinje skiftet
til gennemsnit". Eksplicit cl=-parameter overruler altid
auto-substitutionen.
Direction-aware "tæt på"-klassifikation for operator-targets.
.evaluate_target_arm() bruger nu samme proces-variation-cascade
(3*sigma_hat → sd(y) → eksakt-match) for retningsbevidste targets
(>= 90%, <= 5%) som allerede var aktiv for value-neutral path. Når
centerlinjen ligger på "forkert" side af målet men inden for processens
naturlige variation, klassificeres tilstanden som near_target i
stedet for goal_not_met. Prioritet: strikt goal_met (CL på korrekt
side) > near_target (forkert side + inden for tolerance) >
goal_not_met. Behavior change: scenarier hvor |CL - target| <= 3*sigma_hat flipper fra "ikke opfyldt" til "lige under/over målet" i
output. (OpenSpec: goal-direction-tolerance)
Nye action-arm-keys stable_near_target og unstable_near_target.
Renderes når near_target == TRUE i target-armen. Bruger
{level_direction}-placeholder til at sige "lige under målet"
(higher-direction-mangel) eller "lige over målet" (lower-direction-
overskridelse).
Ny stability-arm-key majority_at_centerline. Aktiveres når >= 50%
af datapunkterne ligger eksakt på centerlinjen (|y - cl| < 1e-9) men
ikke alle er identiske (no_variation har fortrinsret). Flagger typisk
diskret rapportering eller grov måleskala der forringer SPC-tolkningen.
Tilgængelig på dansk og engelsk.
usWeightClass-værdier (alle satte til 400
for Light/Book/Heavy/Poster). Konsekvensen var inkonsistent font-
substitution på macOS, hvor MariHeavy kunne blive valgt som "Mari"-
regular til brødtekst. Windows og Posit Connect Cloud rendrede korrekt.
Fix er udført i BFHchartsAssets (v0.1.1) — opdatér til mindst denne
version for korrekt PDF-rendering på alle platforms. Ingen ændringer
kræves i BFHcharts-koden eller -templates.{level_direction} og {level_vs_target} placeholders i analyseteksten.
Templates i analysis.target.* og analysis.action.* kan nu referere
centerlinjens position relativt til målet via to nye placeholders:
{level_direction} → "over"/"under"/"på" (enkeltord), og
{level_vs_target} → "ligger over målet"/"ligger under målet"/"ligger
på målet" (forhåndsformuleret klausul). Værdierne er tomme strenge når
target ikke er sat, så placeholders kun bør bruges i templates der
allerede er target-betingede. "Ligger på målet" rendres kun ved
strikt lighed (|centerline - target| < 1e-9).Budget-allokering justeret for analyse-tekster med target. Ratio
ændret fra 50/25/25 til 50/15/35 (stability/target/action).
Action-armen får nu plads til de fleste detailed-varianter (90-136
tegn) der tidligere faldt til short. Target-armen klarer sig med
mindre budget da langt de fleste target-varianter er korte (35-46 tegn).
max_chars-default forbliver 375 (matcher Typst-skabelonens
designgrænse).
Padding fjernet fra fallback-analysen. pad_to_minimum() og
YAML-sektionen analysis.padding er slettet — indholdsmæssigt
intetsigende tekster bidrog ikke til analyseteksten og forstyrrede
designet. min_chars bevares i bfh_generate_analysis()-signaturen
fordi det stadig videreføres til BFHllm::bfhllm_spc_suggestion() på
AI-stien, men har ingen effekt på fallback-tekstgenereringen.
.validate_inject_assets() accepterer nu BFHchartsAssets som
default-allowlist. Tidligere blokerede sikkerheds-guarden
BFHchartsAssets::inject_bfh_assets med fejlen
inject_assets must come from a trusted package namespace ... not from 'BFHchartsAssets', selv om BFHchartsAssets er den dokumenterede
companion-pakke (se threat-model i export_pdf.R). Det fik BFHddl-
pipeline til at fejle i Flush-fasen ved PDF-eksport. Default-
allowed_namespaces udvidet til
c("BFHcharts", "biSPCharts", "BFHchartsAssets"). Downstream
pipelines kan nu kalde bfh_export_pdf(..., inject_assets = BFHchartsAssets::inject_bfh_assets) uden workaround-options.PDF-eksport pa Windows virker nu med stier der indeholder mellemrum.
.safe_system2_capture() quotede tidligere kun path-args pa POSIX baseret
pa en forkert antagelse om at Windows system2() sender argv-tokens direkte
til child-processen. I virkeligheden paster Windows-versionen alle args
sammen til en command-line streng som MSVCRT's argv-parser re-splitter pa
uquotede mellemrum. Stier som C:/output/Behandling og pleje/foo.pdf blev
split saa Typst sa og som en uventet positional arg og afbroed med
error: unexpected argument 'og' found. Path-args quotes nu med
shQuote(type = "cmd") pa Windows; flag-args (i KNOWN_TYPST_FLAGS)
forbliver uquotede. Retter regressionen indfoert af commit a528f1b
(fix(typst): skip shQuote on Windows in .safe_system2_capture, 2026-05-01).
at_target-klassifikation bruger nu processens variation som
tolerance-skala i stedet for en relativ-til-target floor. Den gamle
regel (tolerance = max(|target| * target_tolerance, 0.01)) havde en
absolut 0.01-floor uden statistisk begrundelse der dominerede når
target selv var lille -- små targets (f.eks. 1 %) blev fejlagtigt
klassificeret som "tæt på målet" selv når centerlinjen afveg
signifikant (f.eks. 0.019 vs target 0.01 = 90 % afvigelse). Den nye
regel forankrer tolerancen i processens egne kontrolgrænser:
|CL - target| <= 3 * sigma_hat hvor sigma_hat = mean((UCL - LCL) / 6) over sidste fase. For run charts (ingen kontrolgrænser) falder
vi tilbage til sd(y); for degenererede tilfælde (konstant y) en
eksakt-match tolerance. over_target / under_target er nu rent
faktuelle (CL vs target uden tolerance). Berører output fra
bfh_generate_analysis() for berørte scenarier; action_key skifter
tilsvarende. Se OpenSpec change at-target-tolerance-process-variation.
bfh_generate_analysis(target_tolerance = ...) er deprecated.
Argumentet bevares i signaturen for backward compatibility men
ignoreres af value-neutral at_target-klassifikation der nu bruger
processens variation (UCL/LCL eller sd(y)). Eksplicit videregivelse
fyrer lifecycle_warning_deprecated. Parameter fjernes endeligt i
næste major release.Production-readiness audit (cycle 01, 2026-05-10) drevet af
dual-review-cycle-skill med Codex peer-review. 11 PRs merged til
develop. Verdict: APPROVE for produktion (multi-tenant Connect Cloud
**.normalize_percent_target() bevarer nu numeriske stretch-targets
1 på proportion-skalaen.** Tidligere heuristik
value > 1fejltolkede legitime stretch-mål som f.eks.target_value = 1.05(= 105% på multiply=1) som procent-skala-input og dividerede med 100, så downstream-narrative ibfh_generate_analysis()og PDF-export blev semantisk forkert ("centerlinje over maal" når processen faktisk lå under 105%-stretch). Tighten tilvalue > 1.5(validatorens upper bound for multiply=1) -- bryder ingen eksisterende tests, fixer både target=1.05 og boundary-case=1.5. Charten selv (qicharts2-output) var korrekt; kun den genererede analyse-tekst var forvrænget. Empirisk verificeret + Codex peer-review (cycle 01 E1).
bfh_qic(cl = Inf) afvises nu med klar fejl-besked. Tidligere
validate_numeric_parameter() admitterede Inf/-Inf (is.na(Inf) == FALSE, og Inf < Inf / Inf > Inf returnerer begge FALSE), saa
ugyldigt input flød til qicharts2/yA_npc-laget hvor det fejlede
kryptisk efter user-supplied-cl-warning allerede var udsendt. Tilføjet
eksplicit is.finite()-check (cycle 01 E2).
format_target_value() bruger nu locale-aware decimal-separator.
bfh_generate_analysis(..., language = "en") producerede tidligere
engelsk analyse-tekst med danske decimaler ("1,5" i stedet for
"1.5") fordi formatteren hardcodede decimal.mark = ",". Threadet
language-parameter igennem helper + call-sites; default forbliver
"da" (bagudkompatibel). Integer- og percent-paths uændret
(locale-uafhængige). Cycle 01 E4.
format_qic_summary() returnerer nu tom data.frame ved tom
qic_data i stedet for en 1-række NA-frame. bfh_qic() blokerer
nrow(data) == 0 upstream, men hvis qicharts2 selv returnerer
empty (extrem exclude=-konfiguration), undgår early-return nu
NA-propagation til runs_signal/crossings_signal. Defensive fix
(cycle 01 E6).
freeze = 1 på 1-række data afvises nu rent. Tidligere
validate_position_indices() floor max(nrow(data) - 1, 1L)
admitterede freeze = 1 på 1-row data hvor freeze == nrow lod
nul observationer tilbage efter baseline-split. Drop floor;
max = nrow(data) - 1. NULL-freeze stadig OK via allow_null=TRUE
(cycle 01 E7).
Count-style charts (c, g, t, p, pp, u, up) afviser
nu negative y-værdier ved validation. qicharts2 renderede tidligere
negative counts uden warning -- charten så valid ud men var
statistisk meningsløs for klinikere. Chart-type-aware check tilføjes
i validate_bfh_qic_inputs(); i-chart og run-chart accepterer stadig
negative værdier (continuous metrics som temperaturer/differencer)
(cycle 01 E8).
Staging-tempdirs oprettes nu atomisk med mode = "0700". De tre
staging-dir-creation-sites (R/utils_typst.R:330,
R/utils_export_helpers.R:341, R/export_session.R:89) brugte
tidligere to-trins-pattern dir.create() -> Sys.chmod(0700) der
efterlod et microsecond TOCTOU-vindue hvor dir var verden-læsbar.
dir.create(..., mode = "0700") lukker vinduet; Sys.chmod() bevares
som belt-and-suspenders for ældre R / Windows hvor mode= kan
honoreres inkonsistent (cycle 01 S1).
Filsystem-paths redacteres nu i user-visible stop()/warning()-
beskeder. stop()/warning()-paths i R/utils_typst.R afslørede
rå absolutte stier (template-cache, chart-staging, font_path) som
kunne lække home-dir-layout, R-library-install-path og per-session
tempdir-naming til co-tenants på Connect Cloud hvis biSPCharts
surfaceer conditionMessage(e). Ny shared .redact_paths()-helper
stripper tempdir() (både raw og normalized form), HOME, og
.libPaths()-prefixes (cycle 01 S2).
bfh_create_typst_document() re-validerer chart_image post-
symlink-resolution. validate_export_path() kørte tidligere kun
på det syntaktiske input før normalizePath() resolverede symlinks.
En symlink fra trusted staging-område til /var/lib/connect/tenant-A/
PHI-fil ville passere syntaktisk validering, men file.copy() følger
symlinks for source-filer og ville pulle anden tenants data ind i
output-PDF. Fix: re-validate chart_image_norm efter normalizePath
så path-traversal + shell-metachar guards anvendes på det reelle
target (cycle 01 S3).
bfh_qic_result.R @return-block dokumenterer nu $summary korrekt
som data.frame (ikke tibble). Implementation har altid returneret
plain data.frame; doc/code-drift afsløret af Codex peer-review.
Eksplicit kontrakt-note: klassen er public-API og vil ikke flippe
til tibble uden deprecation-cycle (cycle 01 E5).
Nyt review-tracker-konvention etableret i docs/reviews/ per
dual-review-cycle-skill. Cycle 01 audit-trail tilgængeligt i
docs/reviews/01-production-readiness-2026-05-10.md med Codex
reconcile-section + 5 dokumenterede læringer.
validate_numeric_parameter() bypasser param_msg() ved
finiteness-violations så fejl-beskeden ikke falder tilbage paa
generisk "must be a single numeric value" (Inf ER et single numeric
value -- problemet er finiteness specifikt).
Pre-push hook race-conditions ved parallelle pushes dokumenteret i memory; sequential push-rytme anbefalet for cycle-merge-driven.
get_plot() omdøbt til bfh_get_plot() for at følge bfh_* naming-konvention og undgå namespace-collision med ggplot2/plotly. Migration: erstat get_plot(result) med bfh_get_plot(result) eller brug result$plot direkte.<1% på p-chart med y-akse 0–10% —
klippede tidligere ved nederste plot-kant fordi marquee-label-højden
ikke fik plads i 5%-expansion-zonen. PR #164 reducerede default fra 20%
til 5% for at fixe whitespace-overskud (#113), men gik for langt for
boundary-cases. Ny default placerer data i de midterste ~80% af
plot-området, jf. SPC-litteraturens anbefaling. Boundary-aware
label-placement (fct_add_spc_labels.R:300-348) er uændret.
(Y_AXIS_BASE_EXPANSION_MULT i R/globals.R, refs #164)bfh_qic()s
column-name-validator har tidligere afvist danske tegn i kolonnenavne, hvilket
tvang downstream-applikationer (biSPCharts) til at ASCII-translit'e kolonnenavne
før hvert kald. Validator-regex er udvidet fra [a-zA-Z] til Unicode-letter-
klasse (\p{L}) via perl = TRUE, så Tæller, Nævner, Måned og lignende
hospital-system-eksporter accepteres nativt. Function-call-, operator- og
whitespace-detektion er uændret — kun letter-klassen er blevet bredere
(#327, fix biSPCharts #562).restrict_template argument-validering haerdet mod non-logical input.
Tidligere if (isTRUE(restrict_template) && !is.null(template_path)) lod
restrict_template = NA, 1L, "TRUE", eller en logical-vektor passere
guarden lydloest (isTRUE() returnerer FALSE for alt andet end et enkelt
TRUE). Type-validering tilfoejet FOER isTRUE()-checket -- ikke-logical
scalar fejler nu med klar fejl-besked. Vector for Shiny-/API-kontekster hvor
restrict_template kunne flyde fra deserialized JSON eller as.logical()-
coercion. Lukker production-readiness review item 1.1.
metadata$logo_path faar nu path-traversal + shell-metachar validering
(R/utils_export_helpers.R:153-181). Tidligere accepterede valideringen
enhver scalar non-empty character string -- "../../etc/secret.png" eller
"/home/x/private.png" passerede gennem escape_typst_string() direkte til
Typst's image(). Uden --root-sandbox (se naeste punkt) kunne dette
laese vilkaarlige filer paa hosten ind i PDF-output (PHI-eksfiltration i
multi-tenant deploys) eller fungere som file-existence oracle. Mirror den
validering som allerede findes paa font_path (utils_typst.R:389-390).
Lukker production-readiness review item 1.2.
Typst-compileren kører nu med --root <staged-tempdir> (defense in
depth, R/utils_typst.R:418-435). Confiner alle image()/read()/
include access til den staged template-directory, så selv hvis en
fremtidig metadata-felt-validering bliver glemt, kan compileren ikke
laese udenfor tempdir-traeet. Mitigerer hele klassen af path-traversal-
vektorer paa compiler-niveau. --root tilfoejet til
KNOWN_TYPST_FLAGS-allowlist så flag-vaerdien shell-quoteres korrekt.
bfh_extract_spc_stats.data.frame() overfører nu
cl_user_supplied-attributtet. Tidligere returnerede data.frame-
dispatch-pathen altid NULL for flaget, selv naar attr(x, "cl_user_supplied") var sat paa input -- så downstream-konsumenter der
kaldte bfh_extract_spc_stats(result$summary) direkte (i stedet for at
passere hele bfh_qic_result-objektet) tabte caveat-rendering i PDF
lydloest. Nu lazet via isTRUE(attr(x, "cl_user_supplied")) paralleelt
med bfh_qic_result-method'en. Lukker production-readiness review item 2.7.
restrict_template-error-besked vejleder nu om opt-out-pathen.
Tidligere besked: "template_path is not allowed when restrict_template =
TRUE." -- ingen migration-hint. Ny besked tilfoejer eksplicit guidance
om restrict_template = FALSE + warning om compile-niveau trust-model.
Lukker production-readiness review item 1.4.
auto-release-pr.yaml-workflow repareret -- shallow-fetch og
pipefail/SIGPIPE-bug der gav exit 141 ved post-merge runs paa develop.bfh_export_pdf(restrict_template = TRUE) er nu default. Tidligere
default FALSE tillod stiltigende custom Typst-templates via template_path
-- en silent privilege-escalation-vector hvis en konfigurations-pipeline
forwarder user-controlled input. Custom Typst-templates kompileres med
trust-niveau svarende til source() (læser/skriver vilkårlige paths under
compilation). Default-safe posture eliminerer denne vector.
Migration: Callers der eksplicit ønsker custom-template skal nu opt-in:
# Foer (BFHcharts <= 0.15.x): custom template tilladt by default
bfh_export_pdf(result, "out.pdf", template_path = "/my/template.typ")
# Efter (BFHcharts >= 0.16.0): eksplicit opt-out paakraevet
bfh_export_pdf(result, "out.pdf",
template_path = "/my/template.typ",
restrict_template = FALSE)
Migration er mekanisk: tilfoej restrict_template = FALSE til eksisterende
kald. Validation-error-besked nævner eksplicit opt-out parameteren.
Lukker production-readiness review item 2.1. Se ADR-003 for risk-modellen (warning-blind clinical readers).
PDF-eksport rendrer nu en caveat-note under SPC-tabellen, naar
bruger-defineret centerlinje (cl) er sat i bfh_qic(). Naar caller
passerer en non-NULL cl, bliver Anhøj run/crossing-signaler beregnet mod
den brugersatte centerlinje, ikke den data-estimerede process-mean.
Eksisterende R-side warning (R/bfh_qic.R:674-682) er bevaret -- PDF-caveat
er den ANDEN surface for warning-blind kliniske læsere.
Caveat-tekst (dansk default): "Centerlinje fastsat manuelt -- Anhøj-signal
beregnet mod denne, ikke data-estimeret middelværdi." Engelsk fallback
("Centerline manually specified ...") når language = "en".
Default-PDFs uden custom cl rendres uændret -- caveat-blokken er
betinget renderet. Lukker production-readiness review item 3.3.
attr(bfh_qic_result$summary, "cl_user_supplied") er ny stable
attribute (logical scalar) som downstream-konsumenter (PDF-template,
biSPCharts UI, analyse-tekst) kan læse uden at introspektere config-slotten.
Brug isTRUE(attr(result$summary, "cl_user_supplied")) for safe-check.
Attributtens placering bevarer column-iteration patterns (lapply(summary, ...),
dplyr::summarise(across(...))) -- ingen ny kolonne tilføjes.
bfh_extract_spc_stats(result)$cl_user_supplied eksponerer flaget for
power-users der querier SPC-stats via public API, parallelt med
eksisterende is_run_chart-felt.
inst/i18n/{da,en}.yaml: ny noegle labels.caveats.cl_user_supplied.inst/templates/typst/bfh-template/bfh-template.typ: nye parametre
cl_user_supplied: false + cl_caveat_text: none med betinget caveat-blok
under SPC-tabellen.R/utils_typst.R::build_typst_content(): emitter de nye Typst-parametre
naar spc_stats indikerer brugersat centerlinje.R/utils_export_helpers.R::compose_typst_document(): resolver caveat-tekst
server-side via i18n baseret paa language-config.caching-system: refit til at dokumentere de fire active package-private
caches (font, marquee_style, quarto, i18n) og bfh_reset_caches()-
helperen. Fjernet stale references til configure_grob_cache() /
clear_grob_cache() (begge fjernet i v0.5.0).code-organization requirement #7 (3-layer decomposition): fjernet
arbitraer 220-line cap. Strukturelle krav (named helpers,
isolation-testbarhed, cleanup-closures) bevaret. Fil-stoerrelse er ej
laengere et review-kriterium.public-api ↔ spc-analysis-api: praeciseret ejerskab. public-api
ejer API-kontrakter (signaturer, return-types, attribute-existence);
spc-analysis-api ejer signal-detection-semantik (Anhoej rules,
fallback-narrative dispatch, threshold semantics). Cross-references via
prose -- ingen indholds-duplication.cleanup-stale-spec-issues.PDF-eksport rendrer succesfuldt uden hospitals-logo, når companion-pakker
ikke har injiceret assets. Tidligere fejlede bfh_export_pdf() hårdt på et
rent install fordi Typst-templaten hard-refererede til
images/Hospital_Maerke_RGB_A1_str.png (proprietary BFH-asset, ikke bundlet
i public package). Templaten har nu en logo_path: none-parameter; den
forreste logo-slot rendres kun når logo_path er sat. R-siden auto-detekterer
staged logos via .detect_packaged_logo() parallelt med eksisterende
.detect_packaged_fonts()-mønster, så companion-pakker (BFHchartsAssets)
fortsat får hospital-branding uden caller-side ændringer. Implementerer
OpenSpec change add-conditional-template-image.
Migration:
metadata$logo_path = "/path/to/custom.png"
for at overstyre auto-detect.bfh-template.typ template-signature gains optional logo_path: none
parameter. Foreground place(image(...)) block er nu konditional.R/utils_typst.R: ny helper .detect_packaged_logo() + .stage_packaged_template_dir().
build_typst_content() emit logo_path i Typst-params-blokken når sat.R/utils_export_helpers.R::compose_typst_document() re-ordret: stager template
bfh_create_typst_document() skrives, så logo
auto-detect kan se injicerede filer.R/utils_metadata.R::bfh_merge_metadata() accepterer + viderefører logo_path.validate_bfh_export_pdf_inputs(): logo_path whitelist + scalar character-validering.summary$løbelaengde_signal erstattet af tre nye signal-kolonner.
qicharts2's runs.signal er det KOMBINEREDE Anhøj-signal (runs ELLER
crossings violation, beregnet i crsignal()). Det legacy navn
løbelaengde_signal blev læst som "kun runs", hvilket fik klinikere til
at fejlattribuere crossings-only-mønstre som niveauskift. Migration:
# FOR (BFHcharts <= 0.14.x)
if (result$summary$løbelaengde_signal[phase]) { ... }
# EFTER (BFHcharts >= 0.15.0)
if (isTRUE(result$summary$anhoej_signal[phase])) { ... } # samme kombinerede flag
if (isTRUE(result$summary$runs_signal[phase])) { ... } # kun runs-violation
if (isTRUE(result$summary$crossings_signal[phase])) { ... } # kun crossings-violation
De nye runs_signal og crossings_signal er pure derivationer fra
eksisterende længste_løb/længste_løb_max og antal_kryds/antal_kryds_min
per fase. NA inheriterer fra inputs (degenererede faser hvor qicharts2
returnerer NA). biSPCharts #468 sporer downstream-migration.
summary numeriske kolonner returnerer raw qicharts2-precision (ej
længere afrundet til 1-2 decimaler). Påvirker centerlinje,
nedre_kontrolgrænse, øvre_kontrolgrænse, nedre_kontrolgrænse_min/max,
øvre_kontrolgrænse_min/max, nedre_kontrolgrænse_95,
øvre_kontrolgrænse_95. Display-formattere (format_target_value(),
format_centerline_for_details()) afrunder selv ved string-emission, så
PDF-output forbliver byte-identisk. Logic-konsumere (target-sammenligning,
statistisk videreanalyse) får nu korrekte resultater nær afrundings-
grænser. Migration: konsumenter der bruger summary$centerlinje direkte
i UI-visning skal anvende egen round() ved display. biSPCharts #470 har
allerede migreret væk fra summary$centerlinje til qic_data$cl for
logisk vurdering.
summary$crossings_signal = TRUE eksplicit
(ej længere skjult under det misvisende løbelaengde_signal-navn).
Regression-test tilføjet for 4 alternerende fase-blokke a 5 punkter.kontrolgrænser_konstante) bevarer decimal_places + 2-
tolerance for at absorbere floating-point drift fra qicharts2 -- detektions-
logikken roundes, men de lagrede værdier forbliver raw.Decompose add_right_labels_marquee() (R/utils_add_right_labels_marquee.R,
510-line god function with 11 distinct responsibilities) into
orchestrator (~217 lines) plus 5 named private helpers:
.resolve_label_geometry(), .acquire_device_for_measurement(),
.measure_label_heights(), .detect_x_axis_type(),
.build_label_data(). Pure refactor; visual regression baselines
unchanged (zero .new.svg files produced by full pre-push test run).
.acquire_device_for_measurement() returns a cleanup_fn closure
that the orchestrator binds via on.exit(..., add = TRUE) for
unwind-safe device cleanup. Implements OpenSpec change
decompose-marquee-labels.
Decompose build_fallback_analysis() (R/spc_analysis.R, ~210-line
boolean cascade) into orchestrator (~98 lines) plus 5 named pure
helpers: .detect_signal_flags(), .allocate_text_budget(),
.select_stability_key(), .select_action_key(),
.evaluate_target_arm(). Pure refactor; fallback narrative output
unchanged. Adding a new cascade arm now becomes a single new test
row + one new key + one new i18n string instead of a multi-place edit.
Implements OpenSpec change decompose-fallback-analysis.
pluralize_da(),
ensure_within_max(), substitute_placeholders(), pick_text(),
pad_to_minimum()) from R/spc_analysis.R into a dedicated
R/utils_text_da.R. Pure relocation; no behavioral change.
R/spc_analysis.R shrinks by ~130 lines. Implements OpenSpec change
extract-utils-text-da.print.summary parameter fully removed from bfh_qic(). The
parameter was deprecated in v0.11.0: calling with print.summary = TRUE
raised an error, while print.summary = FALSE was silently accepted.
Four versions later the deprecation cycle is complete: the parameter
is now removed from the function signature and from all internal
helpers (validate_bfh_qic_inputs(), build_bfh_qic_return()).
Migration: drop the print.summary argument from your call. The
default bfh_qic_result object exposes the SPC summary directly as
result$summary, and return.data = TRUE returns the raw qic data
with summary fields available on result$qic_summary.
Option name bfhcharts.quarto_path renamed to
BFHcharts.quarto_path (TitleCase namespace consistent with the rest
of the package). Option name spc.debug.label_placement similarly
renamed to BFHcharts.debug.label_placement. The old names are no
longer recognised. Migration: replace the legacy name in any
options() call. Both options were internal/dev-only with no
user-facing documentation.
Add named constants for all five package options in R/globals.R:
BFHCHARTS_OPT_QUARTO_PATH, BFHCHARTS_OPT_SUPPRESS_UNIT_AUTO_DETECT,
BFHCHARTS_OPT_DEBUG_LABEL_PLACEMENT (joining existing
BFHCHARTS_OPT_AUDIT_LOG and BFHCHARTS_OPT_ALLOW_GLOBALENV_INJECT).
All getOption() call sites updated to reference the constants for
grep-ability and single-source-of-truth.
Extract resolve_label_size() helper in R/utils_label_helpers.R.
Three sites (apply_spc_labels_to_export(),
build_bfh_qic_config(), recalculate_labels_for_export()) had
identical viewport-size fallback logic; now share the helper.
Remove unused safe_min() helper from R/utils_helpers.R (no callers).
Drop stale 33-line USAGE example comment block from
R/utils_npc_mapping.R (per-function roxygen documents the API).
Remove orphan # message(sprintf( line at R/utils_panel_measurement.R:170
(refactor leftover).
Replace magic literal 6 with PDF_LABEL_SIZE constant at
R/utils_add_right_labels_marquee.R:149 and R/utils_label_helpers.R:254
(constant was already defined in R/globals.R:83).
Add Y_AXIS_UNITS constant in R/chart_types.R; replace 3 hardcoded
duplicates of c("count", "percent", "rate", "time") with the
constant. Adding a new unit now requires a single edit.
Drop stale TODO from tests/testthat/test-bfh_qic_edge_cases.R:193
(explicit empty-data check already lives at
R/utils_bfh_qic_helpers.R:306).
Update CLAUDE.md to drop CHART_TYPES_DA from the documented
internal API list (the constant does not exist in the codebase).
Strengthen tests/testthat/test-public-api-contract.R with a
regression test asserting print.summary does not appear in
bfh_qic() formals or in man/bfh_qic.Rd.
inject_assets from .GlobalEnv now errors instead of warning (H1).
.validate_inject_assets() previously warned when the supplied function
originated from .GlobalEnv or a direct child environment. It now
hard-errors unless options(BFHcharts.allow_globalenv_inject = TRUE) is set.
Accepted namespaces are BFHcharts and biSPCharts by default; companion
packages can pass a custom allowed_namespaces vector to the internal helper.
This prevents privilege-escalation in Shiny deployments where a reactive
closure can silently bind a .GlobalEnv function to inject_assets.
Migration: move your inject_assets callback into a version-controlled
package namespace (e.g. MyOrgAssets::inject_bfh_assets), or set
options(BFHcharts.allow_globalenv_inject = TRUE) in development sessions
where interactive closures are intentional.
bfh_export_pdf() gains restrict_template = FALSE parameter (H2).
When set to TRUE, any non-NULL template_path is rejected with an
error before compilation. Use in deployment contexts where only the packaged
BFHcharts template should be compiled, preventing custom-template injection
from misconfigured pipelines. Default FALSE preserves backward compatibility.M1: Flag-allowlist in .safe_system2_capture(). Replaced the
startsWith(arg, "--") heuristic with an explicit KNOWN_TYPST_FLAGS
allowlist (c("--ignore-system-fonts", "--font-path")). Any double-dash
argument not in the allowlist (e.g. a crafted --rce value) is now
shQuote()-d on POSIX systems, preventing command injection via
flag-shaped strings.
M2: Double-quote rejected in output paths. " was missing from
SHELL_METACHARS_OUTPUT_PATH. It is now blocked by validate_export_path().
Test: output = 'a".pdf' is rejected with "disallowed".
M3: Packaged-font fallback after invalid font_path (silent fallback fixed).
When a user-supplied font_path fails directory validation, the compiler
previously fell through to system fonts even with --ignore-system-fonts set.
A new .detect_packaged_fonts() helper is now called in both the NULL-input
branch and the validation-fail-reset branch, ensuring packaged fonts are
always detected when available.
M18: .is_windows() helper extracted for cross-platform test coverage.
.Platform$OS.type == "windows" check moved into a testable .is_windows()
helper. Tests now use local_mocked_bindings(.is_windows = ...) to verify
both the Windows early-return path (no shQuote) and the POSIX quoting path
without requiring a real Windows OS.
fct_add_spc_labels.R, utils_bfh_qic_helpers.R, spc_analysis.R,
utils_npc_mapping.R, utils_label_helpers.R,
utils_label_formatting.R.call. = FALSE to public-API-boundary stop() calls so the
internal call stack is not leaked to the user.tests/testthat/test-dep-guards.R
asserting every R/*.R file referencing BFHtheme:: also calls
.ensure_bfhtheme(). Catches future drift where new BFHtheme call
sites are added without the corresponding guard.escape_typst_string() over-escapede < og > med backslash i Typst
string-literal-kontekst. Da \< og \> ikke er valide Typst-string-escapes,
bevarede Typst backslashen literalt i PDF-output (fx p \< 0.05 i stedet
for p < 0.05). Paavirkede metadatafelter hospital, department,
details, author og data_definition. Fixet ved at lade < og >
passere uændret - de er almindelige tegn i Typst string literals og kan
ikke terminere strengen.bfh_create_typst_document() til public API. Funktionen var
tidligere kun tilgaengelig via getFromNamespace(). Downstream-pakker
(fx biSPCharts) kan nu kalde den direkte uden at bryde CRAN-konventioner.
bfh_extract_spc_stats() og bfh_merge_metadata() var allerede
eksporteret og er uaendrede. Relateret: biSPCharts #423.bfh_generate_analysis(use_ai = TRUE) kræver nu data_consent = "explicit".
Alle kald med use_ai = TRUE uden eksplicit samtykke fejler nu med en
informativ fejlbesked der refererer GDPR/HIPAA-konteksten. Formålet er at
sikre at klinikdata ikke sendes til et eksternt AI-system uden at kalderen
eksplicit erkender det.
Migration:
# Før:
bfh_generate_analysis(result, use_ai = TRUE)
# Efter:
bfh_generate_analysis(result, use_ai = TRUE, data_consent = "explicit")
use_ai = FALSE (standard) er uændret og påvirkes ikke.
bfh_generate_analysis(): use_rag defaulter nu til FALSE.
Tidligere var use_rag = TRUE hardcoded i kaldet til
BFHllm::bfhllm_spc_suggestion(). Det er nu ændret til FALSE som
privacy-bevarende standard. RAG (retrieval-augmented generation) lagrer
forespørgselsdata i et vektor-store — en separat compliance-overvejelse fra
det engangs-LLM-kald. Kald med use_rag = TRUE bevarer den tidligere adfærd.
Migration for kald der ønsker RAG:
bfh_generate_analysis(result, use_ai = TRUE, data_consent = "explicit",
use_rag = TRUE)
PDF asset-kontrakt dokumenteret og håndhævet (ADR-001, Option A). Den
publicerede pakke garanterer nu eksplicit at bfh_export_pdf() producerer en
gyldig PDF med system-tilgængelige fallback-fonts (Roboto, Arial, Helvetica,
sans-serif) — uden at kræve companion-pakken BFHchartsAssets eller
proprietære Mari-fonts. Font-chain i production-template er uændret
("Mari", "Roboto", "Arial", "Helvetica", "sans-serif"); Mari er stadig
første prioritet når companion-pakken injecter assets via inject_assets.
Brugere der allerede bruger inject_assets = BFHchartsAssets::inject_bfh_assets
er upåvirkede. Brugere der ikke bruger companion-pakken får nu konsistent
fallback-rendering i stedet for runtime-fejl ved manglende Mari. (#TBD)
bfh_compile_typst() auto-detekterer staged fonts/ Funktionen registrerer
automatisk en fonts/-undermappe i det stagede template-tempdir og videregiver
den som --font-path til Typst-compileren — forudsat at font_path-argumentet
ikke er sat eksplicit. Companion-injectede fonts (fx Mari via inject_assets)
opdages dermed uden at kalleren behøver at angive font_path. Eksplicit
font_path-argument har stadig forrang. (#TBD)
Runtime-guard for inject_assets: bfh_export_pdf() og
bfh_create_export_session() advarer nu hvis inject_assets-funktionen
stammer fra .GlobalEnv eller et direkte child-environment. Dette er et
signal om mulig utilsigtet eksponering (fx Shiny-binding af en
top-level-funktion til parameteren). Advarslen kan undertrykkes med
options(BFHcharts.allow_globalenv_inject = TRUE) i udviklingsflows.
Struktureret AI-egress audit-log: Erstattet message("[BFHcharts/AI] ...")
med .emit_audit_event() der producerer et struktureret JSON-objekt. Skrives
til getOption("BFHcharts.audit_log") som JSON-line (append) hvis optionen
er sat; ellers via message() med prefix [BFHcharts/audit]. Indeholder:
timestamp, event-type, package, target-funktion, felter sendt, context-nøgler,
use_rag-værdi, hostname og bruger.
bfh_generate_analysis(): ny data_consent-parameter (se Breaking
changes) og ny use_rag-parameter (se Breaking changes).
Opdateret security-dokumentation: @section Security: i
bfh_export_pdf() og bfh_create_export_session() udvider nu med
eksplicitte acceptable/uacceptable kilder for inject_assets, inkl. RCE-
advarsel. README-sektionen "Branding for Organizational Deployments" har fået
en prominent security-note.
result$summary$sigma_signal rapporterer nu korrekt TRUE for en fase
når et vilkårligt punkt i fasen er et outlier. Tidligere tog
format_qic_summary() sigma.signal-værdien fra den første rad i fasen
frem for at aggregere med any() over alle rader. For faser hvor det
første punkt ikke er et outlier men et eller flere efterfølgende er det,
ville sigma_signal fejlagtigt vise FALSE. runs.signal var allerede
korrekt aggregeret med any() — sigma.signal er nu tilsvarende rettet.
Downstream-pakker (fx biSPCharts) der viser result$summary eller auto-
genereret analysetekst baseret på sigma_signal vil se rettede værdier.
(OpenSpec 2026-05-01-verify-anhoej-summary-vs-qic-data-consistency, ADR-002)inst/adr/ADR-002-anhoej-summary-source.md):
dokumenterer Anhoej-statistik-proveniens, verifikation af konsistens
mellem result$summary og result$qic_data, og baggrund for rettelsen
af sigma.signal-aggregeringen.tests/testthat/test-summary-anhoej-consistency.R):
asserterer at hvert Anhoej-felt i result$summary matcher aggregeringen
af det tilsvarende felt i result$qic_data per fase — for alle chart-typer
og edge cases (enkelt fase, fase med faa punkter, exclude).Test-isolation og graphics-device cleanup. tests/testthat/setup.R
åbner nu en persistent PDF-device via teardown_env() så bfh_qic()'s
interne ggplot_gtable()-kald aldrig trigger R's default Rplots.pdf.
Ny helper-graphics.R eksponerer with_clean_graphics() wrapper til
device-tunge tests. Print/plot-tests i test-bfh_qic_result.R bruger
wrapperen eksplicit.
Withr-konvertering af test-cleanup. if (file.exists(x)) unlink(x)
anti-mønsteret erstattet med withr::local_tempfile() i
test-export_pdf.R (6 steder), test-security-export-pdf.R (4 steder),
test-export_png.R (4 steder), test-export-session.R (1 sted).
Cleanup sker nu garanteret selv ved test-fejl.
Shell-injection test assertion. test-quarto-isolation.R asserter nu
eksplicit at ingen output*-mappe oprettes ved shell-injection-tests
(validering sker før dir.create()).
dev/clean_workdir.R. Nyt R-script der idempotent fjerner kendte
build- og test-artefakter: BFHcharts.Rcheck/, tarballs, doc/, Meta/,
Rplots.pdf (alle niveauer), tests/testthat/_problems/.
.gitignore og .Rbuildignore udvidet med patterns for
tests/testthat/_problems/ og tests/testthat/output; rm -rf .
CI-render-pipeline styrket (strengthen-ci-render-pipeline):
R-CMD-check.yaml (pre-release channel, Typst 0.13+
krævet for --ignore-system-fonts). Render-afhængige tests der hidtil
skippede med skip_if_no_quarto() eksekveres nu i hovedjobbet.pdf-smoke.yaml anvender nu production-template
(inst/templates/typst/bfh-template/bfh-template.typ) fremfor
CI-only test-template. continue-on-error: true på render-step
midlertidigt til fix-pdf-template-asset-contract er merget.git-archive-render.yaml: installerer pakken fra
git archive HEAD-output og renderer smoke-tests. Opdager
render-afhængigheder af untracked filer tidligt.tests/smoke/render_smoke.R understøtter nu
BFHCHARTS_SMOKE_USE_PRODUCTION_TEMPLATE-env-var til eksplicit
production-template-mode på CI.CONTRIBUTING.md oprettet med CI Pipeline-sektion, beskrivelse af
PR-blocking jobs og manuelt trin til branch protection-konfiguration
i GitHub UI.pdf-smoke-statusbadge.Label-placement monolith opdelt i 3-lags arkitektur.
place_two_labels_npc() (520L) er reduceret til en ~90L orkestrator
ved at ekstrahere tre rene hjælpefunktioner:
.validate_placement_inputs(), .resolve_placement_config() og
.compute_placement_strategy(). Den rene strategi-funktion har ingen
device-kald og kan testes uden et grafik-device.
Deterministisk device-håndtering i add_right_labels_marquee().
Tre separate on.exit-blokke for viewport-device er konsolideret til
én. Redundant normal-path cleanup (L613-619) fjernet.
with_temporary_device() tilføjet (utils_panel_measurement.R):
ren wrapper der åbner Cairo PDF-device, kører kode og lukker
deterministisk via on.exit uanset fejl.
clamp01() slettet — aldrig brugt i produktionskode.
height_safety_margin fallback alignet med konfigurationsværdi
(begge nu 1.0, ingen ekstra margin ved korrekte panel-maalinger).
27 nye kontrakt-tests dækker placement-strategi-laget isoleret
(ingen device-krav). Se tests/testthat/test-placement-strategy-contract.R.
Output-stier med spaces/parens/brackets virker nu også i praksis.
v0.12.0 relaxede path-validatoren til at tillade hospital-typiske filnavne
(rapport (final).pdf, Q1 [2026].pdf, Indikator & resultat.pdf), men
bfh_compile_typst() sendte stadig stier ukvotered til system2().
system2(stdout = TRUE, stderr = TRUE) invoker /bin/sh på macOS/Linux
for stream-capture; parens og brackets i argumenter udløste
"syntax error near unexpected token '('" og PDF'en blev aldrig skabt.
Rettes ved ny intern .safe_system2_capture()-wrapper der anvender
shQuote() på path-argumenter (men ikke flag-argumenter som
--ignore-system-fonts). $-tegn i filnavne (fx data_$HOME_test.pdf)
behandles nu som literals; shQuote() single-quote-wrapper forhindrer
shell-variable-expansion. Verificeret på macOS med live Quarto/Typst.
Windows-adfærd for UNC-stier og paths >260 tegn er ikke empirisk testet
i nuværende CI-setup.
ADR-001 oprettet (inst/adr/ADR-001-pdf-asset-policy.md): dokumenterer
valg af Option A (open-fallback default) og konsekvenser for biSPCharts-deploy.
CI smoke-test udvidet: pdf-smoke.yaml kører nu også
test-production-template-renders.R via BFHCHARTS_TEST_RENDER=true for at
validere production-template på hver PR. Testen skipper automatisk når
images/-mappen mangler (known gap, se ADR-001).
README: ny sektion "PDF Asset Policy" dokumenterer pakke-kontrakten, companion-mønsteret og en verificeringskommando.
Ny R/utils_audit.R med .emit_audit_event() og base-R JSON-serialisering
(ingen jsonlite-afhængighed).
svglite flyttet fra Suggests til Imports. bfh_export_pdf()
kalder svglite::svglite() i export_chart_svg() for ggplot→SVG-
konvertering før Typst-rendering, men pakken var kun erklæret som
Suggests. Konsekvens: downstream-pakker (fx biSPCharts) der bruger
bfh_export_pdf() fik ikke svglite installeret automatisk via
pak/renv deployments. På Posit Connect Cloud (eller andre
minimal-deps environments) fejlede PDF-eksport med
The package "svglite" is required to save as SVG. PNG-eksport
(bfh_export_png) var upåvirket fordi grDevices::png() ej kræver
svglite. (#268)PDF-eksport defaulter nu til strict-baseline-mode.
bfh_export_pdf() og bfh_create_export_session() accepterer en ny
parameter strict_baseline (default TRUE). I strict-mode afvises
eksport hvor config$freeze < MIN_BASELINE_N (8) eller hvor en fase
indeholder færre end 8 punkter — fejlen opstår FØR Quarto kaldes og
refererer eksplicit strict_baseline = FALSE som dokumenteret opt-out.
Begrundelse: PDF'er fra eksport-pipelinen lander på QI-leadership-borde
hvor R-warnings aldrig når en menneskelig læser. Anhøj & Olesen (2014)
anbefaler ≥8 baseline-punkter for pålidelig run/crossing-detection.
bfh_qic() selv bevarer warning-only-adfærd (interaktiv default —
analytiker er til stede). Migration: existing batch-pipelines med korte
baselines skal enten passere strict_baseline = FALSE per kald eller via
bfh_create_export_session(strict_baseline = FALSE). Per-kald værdi
overrider session-værdi. (#4 / Codex 2026-04-30)
bfh_qic() håndhæver strengere input-validering på part, freeze,
exclude og metadata$target. Kald der tidligere passerede
validatoren men producerede kryptiske downstream-fejl afvises nu med
klare beskeder før qicharts2 invokeres:
part skal være positive heltal, strengt voksende, unikke, i
[2, nrow(data)]. Tidligere accepteredes 3.5, c(12, 12), c(12, 6)
silently. Hver overtrædelses-type giver sin egen besked
("integer", "unique", "increasing").freeze skal være ét enkelt heltal i [1, nrow(data) - 1].
Non-integer afvises med "integer".exclude skal være positive heltal, unikke, i [1, nrow(data)]
(sortering ikke krævet).metadata$target skal være NULL, ét finit numerisk eller én
character-streng. Multi-element vektorer, Inf, NaN, NA
afvises.data.frame() afvises med klar "empty"-besked før qicharts2.Migration: kontroller at integer-positioner er hele tal (5L ikke 5.5),
at part er sorteret unique, og at metadata$target er en enkelt
scalar-værdi. (#3 / Codex 2026-04-30)
BFHtheme-afhængighed fanges nu ved load + første brug. BFHcharts kalder
BFHtheme:: funktioner på 17 sites (theme, colors, scale_x/y); manglende
eller for-gammel BFHtheme producerede tidligere kryptiske
could not find function "bfh_cols"-fejl midt i plot-rendering. Nu:
.onAttach() udsender en packageStartupMessage() ved library(BFHcharts)
hvis BFHtheme er fraværende eller < 0.5.0..ensure_bfhtheme(min_version = "0.5.0") kaldes ved entry
af alle 13 funktioner der bruger BFHtheme::. Resultatet caches i
package-private env så kun første kald per session betaler
requireNamespace-omkostningen.remotes::install_github()-install-hint.
Ingen public-API ændring; korrekt-installerede brugere ser intet nyt.
(Codex 2026-04-30 #2)Output-stier accepterer nu parentheses, brackets, braces, ampersand,
dollar og single-quote. Hospital-filnavne som rapport (final).pdf,
Q1 [2026].pdf og Indikator & resultat.pdf blev tidligere afvist af
validate_export_path(). Codex code review 2026-04-30 (#10) flaggede
rejection som over-restriktiv. Empirisk verifikation viste dog at R's
system2(... stdout = TRUE, stderr = TRUE) (som BFHcharts bruger til at
capture Quarto-output) faktisk invokerer shell — så shell-pipeline-tegn
(;, |, <, >, backtick) forbliver afvist for at forhindre
command-injection. NUL/LF/CR og ..-traversal afvises også fortsat.
Binary-stier (Quarto-binary etc.) forbliver strikt validerede via
.check_metachars_binary(). (#8 / Codex 2026-04-30 + advisor-justering)
language = "en" producerer nu korrekt engelsk talnotation på y-aksen.
Pakkens dokumenterede engelsk-sprog-support producerede tidligere
1.000,5 (dansk format) selvom labels var oversat — formelt forkert
engelsk talnotation der i grænsetilfælde kunne misforstås (engelsk
1.000 betyder ét, ikke ét tusind). Fix: format_count()-dispatcher
router count-formatering til format_count_english() (decimal .,
thousand ,) for language = "en" og bevarer format_count_danish()
for language = "da". Percent-formatering bruger nu locale-specifikke
separatorer og suffix (12,5 % for da vs 12.5% for en).
X-akse-datoer bruger best-effort locale-swap af LC_TIME for at producere
engelske månedsforkortelser (Jan, Feb ...) hhv. danske (jan,
feb ...) — afhængig af platformens locale-tilgængelighed. Default
language = "da" bevarer eksisterende output for danske brugere.
(#6 / Codex 2026-04-30)
Auto-analyse respekterer nu chart-target uden duplikering i metadata.
bfh_build_analysis_context() læste tidligere kun target fra
metadata$target og ignorerede x$config$target_text /
x$config$target_value. Konsekvens: PDF-eksport med auto_analysis = TRUE
viste target-linjen på chartet men producerede analysetekst uden
målfortolkning ("centerlinjen ligger på 91 %") når caller ikke duplikerede
target i metadata. Fix: ny intern helper .resolve_analysis_target()
implementerer fallback-kæden metadata$target →
config$target_text → config$target_value. Eksisterende kald der
duplikerer target i metadata får uændret adfærd; kald uden duplikering får
nu korrekt målbaseret analyse. (#1 / Codex 2026-04-30)
place_two_labels_npc() collision-cascade dekomponeret til navngivne helpers.
Den 565-linjers funktion havde en NIVEAU 1/2/3-collision-resolution-blok
med uforklaret magic numbers (0.5/0.3/0.15) og dyb nesting. Refactoreret
til 3 pure functions + 1 helper:
.try_niveau_1_gap_reduction() — gap-reduktionsforsøg.try_niveau_2_flip() — label-flip i 3 strategier (A, B, BEGGE).apply_niveau_3_shelf() — sidste-udvej shelf placement.verify_line_gap_npc() — line-gap predicate
Magic numbers navngivet i globals.R (LABEL_PLACEMENT_GAP_REDUCTION_FACTORS,
LABEL_PLACEMENT_TIGHT_LINES_THRESHOLD_FACTOR,
LABEL_PLACEMENT_COINCIDENT_THRESHOLD_FACTOR,
LABEL_PLACEMENT_SHELF_CENTER_THRESHOLD).
Hver helper er individuelt testbar; ny test-suite test-niveau-resolvers.R
pinner kontrakt med 14 tests. Refactoren er byte-equivalent: visual
output og warnings er uændrede (verificeret ved direkte sammenligning af
vdiffr .new.svg-filer mellem inline- og helper-version).
Legacy NPC-only API-signatur (yA_npc=, yB_npc=, label_height_npc=)
bevaret uændret for biSPCharts-kompatibilitet.
(Codex 2026-04-30 #1)R/*.R-kildefiler er nu ASCII-rene. Alle 14 filer med non-ASCII bytes
(124 forekomster) er konverteret: em-dash — → --, operator-symboler
≥/≤ → >=/<=, danske bogstaver translittereret (æ/ø/å → ae/oe/aa)
i implementations-kommentarer, og brugervendte streng-literaler (warning-
beskeder) bruger nu æ-escapes så bytes på disk er ASCII men runtime-
output forbliver dansk UTF-8. Ny test tests/testthat/test-source-ascii.R
håndhæver politikken og rapporterer file:line:char ved fremtidige
overtrædelser. Begrundelse: R CMD check --as-cran advarer om non-ASCII
i R-kilder, blokerer warning-clean releases og r-universe-distribution.
Ingen public-API ændring; ingen semantisk ændring.
(Codex 2026-04-30 #2)
bfh_generate_analysis() med auto_analysis = TRUE producerede forkert
analysetekst ("målet er endnu ikke nået") for p-charts, selvom centerlinjen
reelt opfyldte et procent-mål. Fejlen opstod fordi parse_target_input()
fjernede %-suffixet og returnerede den rå numeriske værdi (fx 90), mens
centerlinjerne for p-charts er på proportionsskala (fx 0.91). Sammenligningen
0.91 >= 90 evaluerede til FALSE. Fix: bfh_build_analysis_context() kalder
nu .normalize_percent_target() og dividerer target-værdien med 100 når
y_axis_unit == "percent" og target-displayet indeholder % eller
target_value > 1. target_display bevares uændret (fx ">= 90%") i
den genererede tekst. Kald med auto_analysis = FALSE (default) er
upåvirkede. (#fix-percent-target-scale-in-analysis)bfh_qic() print.summary-dokumentation afspejler nu v0.11.0-fjernelsen.
@param print.summary beskrev fortsat parameteren som "deprecated, will warn",
selvom runtime hard-errorer siden v0.11.0. Dokumentationen er opdateret til
at angive at kald med print.summary = TRUE giver en fejl, med
migrationsvejledning til det moderne S3-API (result$summary).
Eksempler 20-22 i ?bfh_qic er omskrevet til at bruge bfh_qic_result-objektet
direkte fremfor det fjernede print.summary-argument.
(#update-print-summary-removal-docs)
bfh_qic() @param chart_type og @details Chart Types dokumenterer nu
alle 12 validerede charttyper. mr (Moving Range), pp (Laney-justeret
proportioner) og up (Laney-justeret rater) var accepteret af validatoren men
fraværende i public docs. Alle tre er nu dokumenteret med brugsvejledning,
inkl. hvornår Laney-varianterne (pp/up) er relevante (store denominatorer,
n > 1000 per subgruppe). To nye eksempler tilføjet: pp-chart og mr-chart
parret med I-chart. (#complete-chart-type-public-docs)
Companion-pakke-pattern dokumenteret for proprietær branding.
?bfh_export_pdf, ?bfh_create_export_session og README.md beskriver
nu den anbefalede fremgangsmåde for organisationer, der har brug for
proprietære fonts (Mari, Arial) og hospital-logoer i PDF-eksport:
distribution via en privat companion R-pakke, der plugger ind via
inject_assets-parameteren. Dette holder proprietære assets ude af den
offentlige GPL-3-pakke og ud af consumer-applikationers git-historik, mens
fuld branding understøttes på Posit Connect Cloud, RStudio Connect og Docker.
For BFH/Region Hovedstaden-deployments implementerer BFHchartsAssets
(privat repo) dette mønster. (#add-bfhcharts-assets-companion-pkg)
.github/workflows/pdf-smoke.yaml.disabled er omdobt til pdf-smoke.yaml
og genaktivet som PR-blocking gate paa main og develop. Font-udfordringen
(proprietaer Mari-font kan ikke distribueres i public repo) loeses med
en minimal CI-only Typst-template (tests/smoke/test-template.typ) der
kun bruger DejaVu Sans (installeret via apt-get paa GitHub-hosted runners).
render_smoke.R detekterer CI-env-var og vaelger automatisk test-template
paa CI og production bfh-template lokalt. Visuel korrekthed (Mari-fonts)
haandteres fortsat af vdiffr og manuel review.
(#enable-ci-safe-pdf-smoke-render)print.summary = TRUE er fjernet. Parameteren var depreceret siden
v0.3.0 (7 minor versioner). Kald med print.summary = TRUE fejler nu
med en klar fejlbesked. Migration: brug return.data = TRUE og tilgå
result$qic_summary, eller brug det nye default bfh_qic_result-objekt
og tilgå result$summary direkte. (#modernize-deprecations-and-deps)Advarsel ved for kort baseline. bfh_qic() udsender nu en advarsel
når freeze eller en part-fase har færre end 8 observationer
(MIN_BASELINE_N). Anhøj-reglerne og SPC-litteraturen kræver ca. 8+
punkter for meningsfulde kontrolgrænser — tidligere kørte beregningen
stille videre med statistisk usikre grænser. Ingen ændring i adfærd
for normale serier (n ≥ 8). (#enforce-baseline-minimum-and-cl-warnings)
Advarsel ved custom cl og Anhøj-signaler. Når cl angives manuelt,
beregnes Anhøj løbe- og krydsningssignaler mod den brugerleverede
centrallinje frem for den dataestimerede procesmiddel. bfh_qic() giver
nu eksplicit advarsel om dette, så brugere er klar over fortolknings-
forbeholdet. (#enforce-baseline-minimum-and-cl-warnings)
Validering af Quarto binary-overrides (find_quarto). Stier angivet
via options(bfhcharts.quarto_path) og QUARTO_PATH-miljøvariablen
valideres nu fuldt: shell-metakarakter-tjek (ny binary-variant der tillader
Windows Program Files-parens), path-traversal-tjek, eksistens-tjek og
eksekverbar-bit-tjek (Unix/macOS). Ugyldige overrides afvises med
informativ advarsel og falder tilbage til PATH-opdag. Det gyldige override
har nu prioritet over PATH-fund. Forhindrer potentielt vilkårlig kode-
eksekvering på multi-bruger-systemer med forgiftet .Rprofile.
(#harden-export-pipeline-security)
Kontroltegn strippes i escape_typst_string(). \n, \r, \t
erstattes med mellemrum og NUL-bytes fjernes inden eksisterende \, ",
<, >-escapes. Metadata-felter (fx afdelingsnavn copy-pastet fra Windows
med CRLF) producerer nu gyldigt Typst-output i stedet for syntaksfejl.
(#harden-export-pipeline-security)
AI-egress audit signal. bfh_generate_analysis(use_ai = TRUE) emitter
nu en message() med tag [BFHcharts/AI] umiddelbart inden kald til
BFHllm::bfhllm_spc_suggestion(). Beskeden navngiver de felter der
transmitteres og use_rag-værdien, så R-level logs kan bekræfte om og
hvornår AI-stien blev taget — et compliance/governance-krav i hospital
deployments. Supprimér med
options(BFHcharts.suppress_ai_audit_message = TRUE). (#add-ai-egress-audit-signal)
Fix: outliers_recent_count row-order assumption.
bfh_extract_spc_stats.bfh_qic_result() sorterer nu qic_data efter x
inden recency-vinduet beregnes. Tidligere antog koden at input-rækkerne
allerede lå i kronologisk rækkefølge — omvendt eller scrambled data gav
forkert outliers_recent_count. Rækkefølge er nu ubetydelig; sorted,
reversed og tilfældigt permuteret input giver identiske resultater.
(#fix-outliers-recent-count-row-order)
Fail-early validering i bfh_generate_details() ved ugyldige x-værdier.
min()/max() blev kaldt på qic_data$x uden forudgående tjek for
gyldige værdier, hvilket gav Inf/-Inf i periodefeltet ved tomme
eller alle-NA datasæt (fx cleanup-scenarier i batch-eksport). Funktionen
stopper nu med en informativ bfhcharts_config_error hvis x-kolonnen er
tom, alle-NA eller (for numerisk x) alle-Inf — inden min/max kaldes.
Kald med gyldige data påvirkes ikke. (#validate-export-details-edge-cases)
Uniform truncering af compile-fejl-output til 500 tegn. Begge fejl-
branches i bfh_compile_typst() (non-zero exit og "PDF not created")
afkorter nu output via den fælles helper .truncate_compile_output().
Tidligere lækkede "PDF not created"-branchen ukortede filsystem-stier
i fejlbeskeder. (#harden-export-pipeline-security)
shQuote() fjernet fra argv-vector i bfh_compile_typst(). system2()
med args = character_vector bruger ikke shell; shQuote() tilføjede
literale anførselstegn der brød stier med mellemrum på Unix/macOS
(fx ~/My Files/). Stier med mellemrum kompilerer nu korrekt.
(#harden-export-pipeline-security)
Indsnævr warning-muffler scope i .muffle_expected_warnings(). Det
ubundne "numeric"-substring-mønster mufler nu ikke længere advarsler som
"NAs introduced by coercion to numeric" (malformerede nævnere) eller
"non-numeric argument to binary operator" (typefejl), der er vigtige
datakvalitetssignaler i klinisk SPC-brug. Erstattet med eksplicitte,
forankrede mønstre der kun dækker kendte ufarlige sources:
scale_[xy]_(continuous|date|datetime).* (ggplot2/scales),
font family.*not found in PostScript font database (BFHtheme/grDevices),
og Removed [0-9]+ rows containing (ggplot2 geom-lag). (#tighten-warning-muffling-scope)
Konsolidér dobbelt deprecation-advarsel i build_bfh_qic_return(). Kald
med print.summary = TRUE, return.data = FALSE udsendte tidligere to
advarsler (én generel deprecation + én legacy-format-advarsel). Disse er
samlet til én konsolideret advarsel der indeholder både deprecation-kontekst
og migrationsinstruktion. (#tighten-warning-muffling-scope)
.github/workflows/pdf-smoke.yaml).
Kører 3 repræsentative bfh_export_pdf()-kald (p-chart, i-chart med metadata,
run-chart med target) på hver PR til main og develop. Verificerer at
Quarto/Typst-pipelinen producerer gyldige PDF-filer (> 0 bytes, >= 1 side).
Bruger åbne fallback-fonts (DejaVu/Liberation/Noto/Roboto) via apt-get så
pipelinen virker på public GitHub-runners uden proprietær Mari. Fanger
catastrophic render-regressioner før de lander i main — complement til
ugentlig render-tests.yaml. Manuel follow-up krævet: tilføj
"pdf-smoke (ubuntu-latest)" til branch-protection required-checks.
(#add-pr-blocking-pdf-smoke-render)Fjernet ineffektiv ownership-check i temp-dir-staging. Den døde
Sys.getenv("UID")-baserede ownership-validering i prepare_temp_workspace()
er fjernet — UID er shell-intern og eksporteres typisk ikke til
R-processer (Rscript, RStudio Server, knitr, Shiny, GitHub Actions), så
checken skippede silently uden reel beskyttelse. Faktisk isolation via
tempfile() (per-bruger tempdir()) og Sys.chmod(0700) er uændret.
Tilsvarende forklarende kommentar tilføjet i bfh_create_export_session().
(#cleanup-temp-dir-ownership-check)
vdiffr snapshots re-baseret (9 snapshots). Font-metric drift opstod da
Roboto blev registreret som Helvetica-alias i v0.10.5 (R/zzz.R). SVG-koordinater
ændrede sig minimalt (< 5px) — forventet og intentionelt.
(#add-pr-blocking-pdf-smoke-render)
Sync font-alias-sæt i tests/testthat/setup.R med R/zzz.R. Roboto tilføjet
til c("Mari", "Arial") → c("Mari", "Arial", "Roboto") i setup.R's
grDevices-registrering. Forhindrer metric-divergens mellem production og test.
(#add-pr-blocking-pdf-smoke-render)
skip_if_no_pdf_render_deps() tilføjet til helper-skips.R. Tjekker
BFHcharts:::quarto_available() og pdftools-tilgængelighed samlet.
Til brug i smoke-render og fremtidige PDF-pipeline-tests.
(#add-pr-blocking-pdf-smoke-render)
test-visual-regression.R migreret fra fil-scope til per-test skip.
Fil-scope skip_if_fonts_unavailable() på linje 28 erstattet med
skip_if_no_mari_font() per test. Giver bedre testthat-reporting og
åbner for fremtidige tests der ikke kræver Mari.
(#add-pr-blocking-pdf-smoke-render)
Smoke + boundary tests for g-, t- og mr-chart tilføjet i
tests/testthat/test-chart-types-gtmr.R. Verificerer S3-klasse,
UCL/CL/LCL relationer og grænsetilfælde (nul-tæller-rækker for g-chart,
identiske tider for t-chart). (#expand-test-coverage-gaps)
Bidirektionel i18n-paritetskontrol. test-i18n.R tjekker nu begge
retninger: DA-nøgler manglende i EN og EN-nøgler manglende i DA.
(#expand-test-coverage-gaps)
PDF-indholdsverifikation med pdftools. Render-gated tests i
test-export_pdf.R verificerer nu at genererede PDF-filer har mindst 1
side via pdftools::pdf_info()$pages. (#expand-test-coverage-gaps)
Kørbart eksempel i bfh_qic(). Første @examples-blok er konverteret
fra \dontrun{} til kørbart kode med deterministiske inline-data.
Øvrige eksempler forbliver i \dontrun{}. (#expand-test-coverage-gaps)
Laney p' håndberegnede referenceværdier. To uafhængige fixtures med
kendte UCL/LCL-værdier (beregnet med Laney 2002-formel, verificeret mod
qicharts2) tilføjet til test-statistical-accuracy-extended.R.
(#expand-test-coverage-gaps)
Nye edge-case tests. test-bfh_qic_edge_cases.R dækker nu:
part=c(6,9) kombineret med freeze=6 (regressiontest), tomt data.frame
(fejl forventet), og enkelt-rækket data (returnerer gyldigt objekt).
(#expand-test-coverage-gaps)
Bevar NA i anhoej.signal fra qicharts2. Tidligere blev NA i
anhoej.signal tvunget til FALSE, hvilket maskerede "for kort serie
til evaluering" som "ingen signal". NA bevares nu og repræsenterer
"ikke evaluerbar (for kort serie)". plot_core.R håndterer NA ved
rendering ved at behandle det som FALSE (solid linje) — ingen
visuel ændring for eksisterende charts. (#enforce-baseline-minimum-and-cl-warnings)
Fjern 16 orphan Rd-sider for interne funktioner. Interne funktioner
i utils_typst.R, utils_quarto.R, utils_bfh_qic_helpers.R,
utils_path_policy.R, cache_reset.R og config_objects.R manglede
@noRd-tag. devtools::document() genererede Rd-sider for funktioner
der aldrig var i NAMESPACE. Rettet ved at tilføje @noRd til alle
relevante interne blocks. (#align-public-api-documentation)
Dokumentér $qic_data kolonnekontrakt. new_bfh_qic_result()
har nu en @section qic_data columns: der lister de kanoniske kolonner
fra qicharts2 (x, y, n, cl, ucl, lcl, sigma.signal, runs.signal,
anhoej.signal m.fl.) med semantik og version-bound (qicharts2 >= 0.7.0).
(#align-public-api-documentation)
Tilføj stabilitetserklæring til new_bfh_qic_result(). Ny
@section Stability: dokumenterer at feltnavne (plot, summary,
qic_data, config) er stabile siden v0.10.0 og ikke fjernes uden
deprecation-cyklus. (#align-public-api-documentation)
Fjern @keywords internal fra bfh_qic_result klasse-topic.
Klassen er offentlig (returneres af enhver bfh_qic()-kald).
(#align-public-api-documentation)
README: fjern "Low-Level API for Fine Control" afsnit. spc_plot_config(),
viewport_dims() og bfh_spc_plot() er interne og må ikke
dokumenteres som public API. Afsnittet fjernet. Features-bullet opdateret.
(#align-public-api-documentation)
base_size-loftet er justeret til 48 (fra 100) for at matche
FONT_SCALING_CONFIG$max_size. Eksplicitte base_size-værdier over 48
gav visuelt ødelagte layouts; loftet er nu konsistent med auto-skaleringslogikken.
(#modernize-deprecations-and-deps)
Unit-auto-detektion udsender nu besked. Når width/height angives
uden eksplicit units-parameter, emitterer bfh_qic() og bfh_export_pdf()
en message() der navngiver den detekterede enhed og opfordrer til eksplicit
units-angivelse. Supprimér med
options(BFHcharts.suppress_unit_auto_detect_message = TRUE).
(#modernize-deprecations-and-deps)
bfh_generate_analysis() dokumenterer nu manuel BFHllm-installation.
BFHllm er fjernet fra Remotes: (den er kun i Suggests); Roxygen
@details indeholder nu remotes::install_github("johanreventlow/BFHllm")-
instruktionen til brugere der ønsker AI-analyse (use_ai = TRUE).
(#modernize-deprecations-and-deps)
label_config$centerline_value, $has_frys_column, $has_skift_column
er fjernet som statiske kopier i build_bfh_qic_config(). Disse felter
var duplikater af config$cl, config$freeze og config$part og kunne
desynkronisere ved mutation. export_pdf.R læser nu direkte fra top-niveau
config-felterne. (#modernize-deprecations-and-deps)
BFHllm Remotes-status revideret. Et tidligere forsøg på at fjerne
BFHllm fra Remotes: (begrundet i R CMD check --as-cran advarsler for
Suggests-pakker) brød GitHub Actions CI: r-lib/actions/setup-r-dependencies
installerer Suggests via pak med dependencies = "all", og uden Remotes-pointer
kan pak ikke finde BFHllm (privat GitHub-repo, ej på CRAN). BFHllm er nu
igen i Remotes: for at sikre CI fungerer; den er stadig kun i Suggests,
så manuel install er ikke længere strengt påkrævet for slutbrugere men er
dokumenteret i bfh_generate_analysis() Roxygen.
(#modernize-deprecations-and-deps)
grDevices::postscriptFonts() og grDevices::pdfFonts() ved package
load (ny .onLoad() i R/zzz.R). Tidligere blev registreringen kun
udført i test-setup -- production-kald af bfh_qic() og
ggplot2::ggsave() producerede ~40-50 harmlose PostScript-warnings per
kald (fra grid::C_stringMetric font-metric-lookup). Registreringen er
konservativ: eksisterende Mari/Arial-registreringer (fx via
systemfonts) overskrives ikke. Den interne
.muffle_expected_warnings() helper bevares som defense-in-depth for
datetime/numeric scale warnings. (#202)Filomdoebning: R/create_spc_chart.R -> R/bfh_qic.R. Funktionen
blev omdoebt fra create_spc_chart() til bfh_qic() i v0.2.0, men
filnavnet blev aldrig opdateret. Ingen API-paavirkning -- kun navigation
forbedret. Live docs (README.md, CLAUDE.md, AGENTS.md,
tests/testthat/README.md, pending OpenSpec-changes) opdateret til
at referere det korrekte sti. (#217, #204)
Repository-hygiene: Fjernet geomtextpath fra Suggests (ubrugt --
kun en stale TODO-kommentar i R/plot_core.R refererede pakken). Fjernet
tracked dev-scripts (demo_*.R, pdf_export_forsoeg.R,
test_labels.R, test_date_formatting_debug.R,
09_medicinsikker_*.R) og CLAUDE.md.backup. Strammet .gitignore
med *.backup og generic BFHcharts_*.tar.gz. (#215)
DRY refactor af font-warning handlers i utils_bfh_qic_helpers.R. To
naesten identiske withCallingHandlers-blokke (i render_bfh_plot()
omkring bfh_spc_plot() og i apply_spc_labels_to_export() omkring
add_spc_labels()) er konsolideret i en intern helper
.muffle_expected_warnings(). Helper'en mufler ggplot2 datetime/numeric
scale-warnings og BFHtheme PostScript-font-warnings (Mari ikke i
font-database) -- begge er expected behavior. Genuine warnings
propageres uændret. Fungerer som defense-in-depth efter PR #242
(font-aliases onLoad) der eliminerer font-warnings ved kilden. (#200)
Roxygen-dokumentation eksplicit om trust-grænse for inject_assets og
template_path. Begge parametre i bfh_export_pdf() (og
inject_assets i bfh_create_export_session()) accepterer
caller-supplied kode/templates der kører med fuld proces-privilege.
De er legitim infrastruktur for proprietaere fonts og custom templates,
men en naiv Shiny-integration der videresender user-input vil skabe en
privilege-escalation-vektor. Ny \\section{Security} markerer eksplicit
hvilke parametre der er trusted-code-only og hvordan de skal valideres
mod allow-lister hvis exposed. Ingen kode-aendring -- kun docs. (#218)
Numerisk verifikation udvidet til 7 yderligere chart-typer (#208).
Ny test-fil tests/testthat/test-statistical-accuracy-extended.R med
39 tests der verificerer kontrolgrænse-formler for xbar, s, mr,
t, g, pp og up. Fanger regressioner i BFHcharts' wrapping af
qicharts2 og detekterer breaking changes ved qicharts2-opgraderinger.
A3/B4-konstanter.D4 = 3.267 for n=2).y^(1/3.6)-transformation med I-chart paa transformerede
skala, back-transformeret til original.sqrt(c_bar·(c_bar+1)).qicharts2::qic() direkte siden
prime-formlen er non-triviel at reproducere uden duplikering af
qicharts2 internals.Tests bruger udelukkende deterministiske data (ingen RNG) og haandberegnede expected values for robusthed paa tvaers af R-versioner.
Ny scheduled CI-workflow til live Quarto/Typst render-tests (#210).
R-CMD-check.yaml installerer ikke Quarto (Typst-template hardcoder
proprietaer Mari-font, og Typst fejler exit 1 paa unknown-font warnings).
Dette efterlod PDF/PNG-eksport-pipelinen uden CI-coverage -- regressioner
i template-rendering, font-fallback eller chart-embedding kunne slippe
igennem. Ny .github/workflows/render-tests.yaml kører ugentligt
(mandage 06:00 UTC) plus on-demand og ved aendringer til
export-relaterede filer; matrix over ubuntu-latest + macos-latest;
installerer Quarto 1.5.57 + open fallback-fonts (DejaVu, Liberation,
Noto, Roboto); aktiverer BFHCHARTS_TEST_RENDER=true så render-gate'd
tests kører live; uploader PDF/Typst-artifacts ved fejl for
remote-debugging.
Fire kliniske vignettes (#219). Pakken havde tidligere kun reference-
dokumentation via roxygen — kliniske brugere manglede end-to-end guidance
paa hvilke chart-typer der passer til hvilke spoergsmaal, hvordan
interventioner haandteres, og hvad target-kontrakten dikterer. Fire nye
Rmd-vignettes i vignettes/:
chart-types: Beslutningstrae fra klinisk spoergsmål til
chart_type-valg. Per-type use cases, sample-size guidance,
anti-patterns. Reference: Provost & Murray (2011).phases-and-freeze: Distinguere part (separate centerlinjer per
fase) vs freeze (fastlaast baseline). Klinisk eksempel: pre/post
intervention med frosset baseline. Almindelige fejl + migration-pattern.targets-and-percent: Dokumenterer percent-target-kontrakten fra
v0.9.0 (#203). Migration-eksempel fra silently misvisende
target_value = 2.0 paa percent-akse til korrekt
target_value = 0.02 (proportion) eller multiply = 100.safe-exports: Sikkerheds-praksis for inject_assets og
template_path (#218). Allow-list-pattern til Shiny-applications,
pre-deploy security checklist.Infrastruktur: VignetteBuilder: knitr tilfoejet til DESCRIPTION,
knitr + rmarkdown i Suggests, build artifacts i .gitignore.
Auto-tag-workflow respekterer nu DESCRIPTION's Version-felt direkte.
Tidligere auto-inkrementerede tag-release.yaml PATCH baseret paa
eksisterende tags uafhaengigt af DESCRIPTION, hvilket fik tag og
pakke-version ud af sync (fx blev tag v0.10.3 oprettet paa commit med
Version: 0.10.1, så pak's version-resolver afviste downstream-installs
med Can't install dependency [email protected] (>= 0.10.3)).
Workflow bruger nu v + DESCRIPTION's Version som tag-navn og fejler
hvis tag allerede eksisterer paa anden commit. Konsekvens: udvikler
skal manuelt bumpe DESCRIPTION i hver release-PR for at faa et nyt tag.
(Fixer regression introduceret i tidligere commit der tilfoejede
auto-PATCH-increment.)
Spring v0.10.2 og v0.10.3. Disse tags blev auto-genereret med forkert DESCRIPTION-version (0.10.0 og 0.10.1 hhv.) -- v0.10.4 er første version hvor tag matcher pakke-version igen.
auto for
dynamisk hojde, men det fjernede den visuelle luft mellem analyse-tekst og
PERIODE-linjen ved korte analyser (SPC-PDF lookede mere kramped end
oprindeligt design). Den faste 26.4mm gendanner det oprindelige spacing.
Lange analyser, der overskrider 26.4mm, faldt tidligere udenfor og
haandteres bedst ved at korte analyse-teksten ned -- ikke ved at lade row
flyde. (Reverts #160)bfh_export_pdf() og bfh_compile_typst() ignorerer nu system-fonts som
default (ignore_system_fonts = TRUE). Tidligere faldt Typst tilbage til
system-installerede font-varianter selv når font_path var sat, hvilket
kunne resultere i forkert weight (fx Mari Heavy med metadata
style=Heavy,Regular matchede regular-weight). Det giver nu konsistent
rendering på tværs af dev-maskiner og cloud-deployment.
Migration: Hvis eksisterende kode er afhængig af system-fonts ved
Typst-render, sæt ignore_system_fonts = FALSE eksplicit. (#227)bfh_qic() validerer nu target_value mod y_axis_unit-skalaen.
Når y_axis_unit = "percent" (default multiply = 1), skal target_value
være i [0, 1.5] (proportion). Negative værdier afvises altid.
Den hyppigste fejl: target_value = 2.0 til at betyde "2%" —
brug target_value = 0.02 eller sæt multiply = 100.
Migration:
# Gammel (forkert, plottet target ved 200%):
bfh_qic(..., y_axis_unit = "percent", target_value = 2.0)
# Ny — option A (proportion, default multiply=1):
bfh_qic(..., y_axis_unit = "percent", target_value = 0.02)
# Ny — option B (procent, multiply=100):
bfh_qic(..., y_axis_unit = "percent", target_value = 2.0, multiply = 100)
(#203)
bfh_qic() validerer nu indholdet af denominator-kolonnen n for
ratio-charts (p, pp, u, up). Tidligere blev kun kolonnenavnet
syntakstjekket; rækker med n = 0, n < 0, n = Inf eller y > n
(P-charts) gled igennem og producerede stille misvisende rate-plots
(NaN/Inf-værdier, proportioner > 1). Nu rejses en hård fejl med
rækkenumre, så brugeren kan inspicere kildedataene.
Kontrakt:
p, pp, u, up) kræver n ikke-NULL.n skal være numerisk og endelig (ingen Inf/-Inf).NA værdier af n skal være > 0.p, pp): hver række skal opfylde y <= n.NA i enkelt-rækker af n er tilladt (qicharts2 dropper dem).run, i, mr, c, g, t, xbar, s)
valideres ikke.Migration: Pre-filtrér data inden bfh_qic():
data_clean <- data[!is.na(data$denominator) & data$denominator > 0, ]
bfh_qic(data_clean, ...)
(#205)
bfh_generate_analysis() kræver nu eksplicit use_ai = TRUE for
AI-analyse. Defaulten er ændret fra NULL (auto-detektér BFHllm) til
FALSE (brug altid standardtekster). I healthcare-kontekst er implicit
ekstern databehandling uacceptabel; tidligere kunne BFHllm aktiveres
automatisk når pakken var installeret, uden at brugeren vidste det.
Migration: Kald der ønsker AI-analyse skal sætte use_ai = TRUE
eksplicit. Kald der allerede sætter use_ai = FALSE er uændrede.
Det samme gælder bfh_export_pdf(auto_analysis = TRUE), der nu også
defaulter til use_ai = FALSE (#secure-ai-explicit-opt-in).
language-parameter ("da" eller "en") på
bfh_qic(), bfh_generate_analysis() og bfh_generate_details(). Default er
"da" — alle eksisterende kald er bagudkompatible. Engelsksprogede diagramlabels
("TARGET", "CUR. LEVEL") og analysetekster returneres ved language = "en".
Strings er centraliseret i inst/i18n/da.yaml og inst/i18n/en.yaml.
Intern helper i18n_lookup(key, language) + language-keyed cache
(.i18n_cache) med reset via bfh_reset_caches() (#i18n-chart-strings).bfh_create_export_session() opretter
en genanvendelig eksport-session der kopierer Typst-template-assets én gang og
deler dem på tværs af multiple bfh_export_pdf()-kald. I batch-workflows
(N eksporter fra løkke) eliminerer dette den rekursive template-copy der
dominerer I/O-cost. Brug: session <- bfh_create_export_session(),
send batch_session = session til hvert bfh_export_pdf()-kald, og luk med
close(session). inject_assets- og font_path-argumenter overføres til
session-konstruktøren i stedet for til individuelle kald
(#reuse-typst-template-assets).Visuel regression stabiliseret: vdiffr-snapshots re-baselinede efter BFHtheme
0.5.0 bump (koordinat-skift fra opdateret font-metrics). Testopsætning registrerer
nu Mari og Arial som PostScript/PDF font-aliaser i setup.R, hvilket eliminerer
~1600 harmlose "font family not found in PostScript font database" warnings per
test-kørsel. .new.svg filer tilføjet til .gitignore (#209).
Cache-nøgle reproducerbarhed: Font-cache i utils_add_right_labels_marquee.R
nøglede kun på device-type — ikke på fontfamily. Kald som
.resolve_font_family("Arial") og .resolve_font_family("Helvetica") på
samme device delte cache-entry (første vinder). Nøgle er nu
dev_type + fontfamily for at forhindre stale cache ved fontskift.
Ny intern helper bfh_reset_caches() tømmer alle package-level caches —
bruges automatisk i test-setup via helper-cache.R
(#cache-keying-and-reset).
AST-baseret markdown → Typst parser: markdown_to_typst() bruger nu
CommonMark AST-parsing (commonmark + xml2) i stedet for regex-baseret
konvertering. Alle Typst markup-tegn (#, $, @, _, *, [, ],
<, >, `, ~, ^, \) escapes i plain text-noder, hvilket
forhindrer Typst injection via user-supplied strenge (fx AI-analysetekst).
Understøttede markdown-elementer: bold, italic, inline code, lister,
linjeskift. Potentielle outputforskelle: (1) \n\n (dobbelt newline)
producerer ét Typst-linjeskift i stedet for to — visuelt identisk da
Typst collapser consecutive linjeskift; (2) markdown-links
[tekst](url) renderer nu som synlig tekst alene (ikke bracket-notation);
(3) backtick og * i plain text escapes — var ikke escaped i den gamle
regex-parser (#harden-typst-markdown-parser).
Centraliseret path policy for eksport-funktioner: Duplikeret
sti-valideringslogik i bfh_export_png(), bfh_export_pdf() og
bfh_compile_typst() er samlet i en ny intern helper
validate_export_path() i R/utils_path_policy.R. Alle tre
call-sites anvender nu den samme komplette metacharacter-blacklist
(; | & $ \ ( ) { } < > \n \r) og det samme path-traversal-check. **Adfærdsændringer:** bfh_export_png()afviser nu også<, >, \nog\ri stier (tidligere tilladt);bfh_export_pdf()kræver nu.pdf`-extension på output-stien (tidligere ukontrolleret).
Ingen ændringer i public API-signaturer
(#central-export-path-policy).
spc_plot_config(), viewport_dims(), phase_config() fejler nu
ved ugyldigt input i stedet for at udsende en advarsel og returnere
en coerced/default-værdi. Alle valideringsfejl kaster en condition med
class bfhcharts_config_error. Dette påvirker kun kode der direkte
kalder disse interne constructors — bfh_qic() er upåvirket
(#harden-config-validation).Testbarhed af Quarto-pipeline: bfh_compile_typst() og
quarto_available() accepterer nu .system2 = system2 og
.quarto_path = NULL parametre (dependency injection). Produktionskald
er uændret; tests kan injicere mocks uden live Quarto-installation
(#inject-quarto-system2).
Testsuite stabilisering: Kanoniske skip-helpers tilføjet til
tests/testthat/helper-skips.R: skip_if_no_quarto() og
skip_if_no_mari_font(). Alle render/PDF-tests migreret fra rå
skip_if_not(quarto_available(), ...) til skip_if_not_render_test() +
skip_if_no_quarto() — sikrer at devtools::test() kører rent uden
Quarto installeret og uden render-gate sat (#stabilize-default-test-suite).
Fjernet biSPCharts-specifik kode fra chart_types.R (#119):
CHART_TYPES_DA, CHART_TYPE_DESCRIPTIONS, get_qic_chart_type(),
chart_type_requires_denominator() og get_chart_description() var aldrig
en del af BFHcharts' pipeline og lå ubrugte i pakken. biSPCharts vedligeholder
egne versioner i R/config_chart_types.R. Kun CHART_TYPES_EN er bibeholdt
da den bruges internt til validering af chart-type input.
CI: fuld R CMD check med tests. Fjernede --no-tests workaround fra
R-CMD-check.yaml efter at to pre-existing test-failures blev rettet:
test-smoke.R:10 brugte udfasede BFHtheme farvenavne
(hospital_grey/hospital_dark_grey → grey/dark_grey);
test-export_pdf.R:423 forventede forældet fejlbesked-regex efter
bfh_extract_spc_stats() blev konverteret til S3 generic. CI fanger nu
nye test-regressioner.
Remotes: til DESCRIPTION for BFHtheme og BFHllm. Downstream-
pakker (fx biSPCharts) kunne tidligere ikke installere BFHcharts via pak
uden eksplicit workaround, fordi pak ikke transitivt fandt BFHtheme.
Fra v0.8.1 er transitiv dep-resolution fixet.y_axis_unit = "time" er skiftet fra enkelt-enhed
("30 minutter", "1,5 timer", "2 dage") til komposit-format
("30m", "1t 30m", "2d 13t"). Ændringen løser to konkrete problemer:
(1) Tidligere kunne y-aksen vise 7-cifrede kommatal som "0,6666667 timer"
når brudværdier ikke var hele enheder (issue #138). (2) Det nye format
er mere kompakt og matcher nu data-punkt labels (centrallinje, target)
— pilene fra CL/target rammer præcis samme tekst som y-aksen. Samtidig
placeres ticks på tids-naturlige intervaller (1m, 5m, 15m, 30m, 1t,
2t, 6t, 12t, 1d, 2d, 7d, 30d) via den nye interne time_breaks(),
så ggplot2's default-breaks ikke længere producerer fraktionelle timer
(#138). Downstream-pakker (biSPCharts m.fl.) kan fjerne workarounds der
overlay'er deres eget tidsformat oven på plottet.format_y_value() ignorerer nu y_range-parameteren for time-enhed;
komposit-formatet håndterer selv unit-valg via komponentopdeling.
Parameteren er bibeholdt for bagudkompatibilitet men har ingen effekt.bfh_interpret_spc_signals() er fjernet. Funktionen producerede parallel
Anhøj-tekst via hardcoded sprintf()-kald, men dens output
(context$signal_interpretations) blev aldrig læst af
build_fallback_analysis() i praksis. Al analysetekst genereres nu via
YAML-skabeloner i inst/texts/spc_analysis.yml. bfh_build_analysis_context()
returnerer ikke længere signal_interpretations-feltet. Downstream-kaldere
bør bruge bfh_generate_analysis() for den samlede analysetekst.0,8541667 timer-bug på y-aksen ved tids-data: 51 min formateres
nu korrekt som "51m" i stedet for det 7-cifrede kommatal som
ggplot2's default scale_y_continuous producerede ved fractional-hour-
værdier (#138).59,7 min til "1t" (ikke "60m"), og 1439,7 min til
"1d" (ikke "23t 60m").bfh_generate_details() er nu eksporteret. Funktionen genererer den
formaterede detail-tekst (periode, gennemsnit, seneste, niveau) som vises
over SPC-grafen i PDF-eksporter. Tidligere kun tilgængelig internt — nu
kan downstream-pakker (fx biSPCharts) sætte metadata$details selv,
så preview-veje (via bfh_create_typst_document()) matcher
bfh_export_pdf()-vejen.outliers_recent_count, seneste 6 obs),
mens tabellen fortsat viser totalen (outliers_actual).inst/texts/spc_analysis.yml (outliers_only, runs_outliers,
crossings_outliers, all_signals) og den hardkodede tekst i
bfh_interpret_spc_signals().bfh_extract_spc_stats() er nu en S3-generic med methods for data.frame,
bfh_qic_result og NULL. Kald direkte med et bfh_qic_result-objekt for
at få fyldestgørende outlier-tal — tidligere krævede det den interne funktion
extract_spc_stats_extended(), som nedarvede problemer mellem forskellige
downstream-pakker.result <- bfh_qic(data, x = date, y = value, chart_type = "i")
# Nyt (anbefalet): fyldestgørende stats inkl. outliers
stats <- bfh_extract_spc_stats(result)
# Gammelt: kun runs/crossings fra summary — bevares for bagudkompatibilitet
stats_summary_only <- bfh_extract_spc_stats(result$summary)
PDF-tabellen under "OBS. UDEN FOR KONTROLGRÆNSE" viser nu det korrekte
antal outliers. Tidligere blev outliers_actual begrænset til de seneste
6 observationer, hvilket medførte uoverensstemmelse mellem diagrammet (alle
blå punkter) og tabellen (kun de nyeste outliers). Tabellen viser nu TOTAL
antal outliers i seneste part.
Analyseteksten (bfh_interpret_spc_signals(), bfh_generate_analysis())
nævner fortsat kun outliers indenfor de seneste 6 observationer, så ældre
outliers ikke beskrives som aktuelle problemer. Denne adfærd er nu
eksponeret som et separat stats-felt outliers_recent_count.
extract_spc_stats_extended() er fjernet. Al intern
brug (bfh_export_pdf, bfh_build_analysis_context) er skiftet til
bfh_extract_spc_stats(x) med S3-dispatch på bfh_qic_result.spc_analysis.yml har nu short/standard/detailed varianter for alle tekster,
hvilket giver bedre kontrol over analysetekst-længde (#115)pick_text() vælger nu automatisk den længste variant der passer inden for
tegnbudgettet — erstatter trimning med naturligt variantvalgbfh_interpret_spc_signals() er ikke længere eksporteret. Brug
BFHcharts::: for direkte adgang. Funktionen bruges kun internt af
bfh_generate_analysis() (#115)Mari → Roboto → Arial → Helvetica → sans-serif.
bfh_generate_analysis() - Generates analysis using AI (BFHllm) or standard textsbfh_interpret_spc_signals() - Danish standard texts for Anhøj SPC signals (runs, crossings, outliers)bfh_build_analysis_context() - Collects context from bfh_qic_result for analysisbfh_export_pdf() gains auto_analysis and use_ai parameters for automatic analysis generationExample usage:
# Auto-generate analysis with AI (if BFHllm installed)
bfh_qic(data, x = month, y = infections, chart_type = "i") |>
bfh_export_pdf("report.pdf",
metadata = list(
data_definition = "Antal infektioner pr. 1000 patientdage",
target = 2.5
),
auto_analysis = TRUE
)
# Use standard texts only (no AI)
bfh_generate_analysis(result, use_ai = FALSE)
**bold text** → Typst #strong[bold text]*italic text* → Typst #emph[italic text]\n) → Typst line breaksmarkdown_to_typst() for CommonMark-to-Typst conversion.grob_height_cache, .panel_height_cache, and all related configuration functionsdocs/CACHING_SYSTEM.md to reflect simplified architectureuse_cache parameters from all measurement functionsContextual percent precision for centerline labels: Centerline labels on SPC charts now show one decimal place when the centerline is within 5 percentage points of the target value. This provides better precision where it matters (close to goal) while keeping labels clean when far from target.
Range-aware y-axis precision: Y-axis ticks for percent charts now show decimals when the axis range spans less than 5 percentage points, preventing repeated or indistinguishable tick labels on narrow ranges.
bfh_extract_spc_stats() and bfh_merge_metadata() as public API functions to support downstream packages (like SPCify) without requiring ::: accessor.
bfh_extract_spc_stats() extracts SPC statistics (runs, crossings) from qic summary data framesbfh_merge_metadata() merges user-provided metadata with default values for PDF generationBFHcharts:::function() to BFHcharts::bfh_function()Significant performance optimizations for PDF export functionality, delivering 40-50% faster export times and 75% smaller temporary files.
file.copy(..., recursive = TRUE) for dramatically faster template directory operationsquarto_available() with ~2ms cache hits vs ~50ms system calls| Metric | Before (v0.3.4) | After (v0.3.5) | Improvement | |--------|-----------------|----------------|-------------| | Single PDF export | ~500-800ms | ~300-400ms | 40-50% faster | | Temp file size | ~15-25 MB | ~4-6 MB | 75% smaller | | Quarto check (cached) | ~50ms | ~2ms | 96% faster |
Note: Visual QA confirms 150 DPI provides excellent quality for PDF output. Temporary files are automatically cleaned up after each export.
This release improves internal code organization, error handling, and API clarity.
quarto_available(), bfh_create_typst_document(), bfh_compile_typst()) are no longer exported to users. They remain accessible via BFHcharts::: for advanced use cases. This change simplifies the public API without affecting functionality.ggplot2::ggsave(), writeLines()) now wrapped in tryCatch() with informative error messagesFALSE (fail-safe) instead of TRUEdir.create() to ensure cleanup even if directory creation failsescape_typst_path() and its testsImpact: No breaking changes. Internal API changes only affect advanced users who directly call helper functions with :::.
IMPORTANT: This release addresses critical security vulnerabilities in PDF export functionality. Healthcare organizations using BFHcharts in production environments should update immediately.
.. directory traversal attempts;, |, &, $, etc.) before being passed to system commandsnormalizePath() to prevent TOCTOU attacksbasename() to avoid exposing sensitive full pathsCompliance: These changes strengthen BFHcharts for HIPAA/GDPR compliance requirements in healthcare environments.
Fixed chart path handling regression: bfh_create_typst_document() now correctly handles chart images from any location. Charts are copied to the output directory before Typst generation, fixing the regression where only charts in the same directory worked.
Fixed Quarto version parsing: check_quarto_version() now correctly parses version strings in formats like "Quarto 1.4.557" (prefixed) and "1.4" (two-part). Previously, prefixed version strings would bypass the version guard.
Strengthened template validation: bfh_export_pdf() now properly rejects directories and non-.typ files as template_path. Added validation for file copy success with clear error messages.
Consistent path escaping: All user-provided paths in generated Typst content are now properly escaped, including custom template paths and chart filenames with special characters.
Fixed Windows path handling: Typst import and image paths are now properly escaped for Windows paths and paths containing spaces. Previously, Windows-style backslashes would cause invalid Typst escape sequences.
Fixed date metadata propagation: User-supplied metadata$date is now correctly forwarded to the Typst template. Previously, the PDF always showed today's date from the template, ignoring user-supplied dates.
Enhanced Quarto version checking: quarto_available() now verifies that Quarto version is >= 1.4.0 (required for Typst support). Previously, only binary existence was checked, leading to opaque errors with older Quarto versions.
template_path parameter to bfh_export_pdf() allowing users to specify custom Typst template files instead of using the packaged BFH template.Return type changed: bfh_qic() now returns a bfh_qic_result S3 object instead of a ggplot object
bfh_qic() |> bfh_export_pdf()) and preserves SPC statistics for PDF metadataresult$plot - see Migration Guide belowDeprecated parameter: print.summary parameter is deprecated
bfh_qic_result$summaryprint.summary = TRUE will trigger a deprecation warning but still works (legacy behavior)PNG Export: bfh_export_png() - Export charts to PNG with configurable dimensions
PDF Export: bfh_export_pdf() - Export charts to PDF via Typst templates
Low-level Functions:
bfh_create_typst_document() - Generate Typst documentsbfh_compile_typst() - Compile Typst to PDFquarto_available() - Check Quarto CLI availabilitybfh_qic_result
$plot, $summary, $qic_data, $configis_bfh_qic_result(), get_plot()inst/templates/typst/
If you only display plots in console/viewer, no changes needed:
# Works exactly the same in 0.3.0
bfh_qic(data, x = date, y = value, chart_type = "i")
If you need to customize the plot with ggplot2 layers:
# Before (0.2.0):
plot <- bfh_qic(data, x = date, y = value, chart_type = "i")
plot + labs(caption = "Source: EPJ")
# After (0.3.0):
result <- bfh_qic(data, x = date, y = value, chart_type = "i")
result$plot + labs(caption = "Source: EPJ")
# Before (0.2.0):
result <- bfh_qic(data, x, y, chart_type = "i", print.summary = TRUE)
summary_stats <- result$summary
# After (0.3.0) - Recommended:
result <- bfh_qic(data, x, y, chart_type = "i")
summary_stats <- result$summary # Always available
# After (0.3.0) - Legacy (with deprecation warning):
result <- bfh_qic(data, x, y, chart_type = "i", print.summary = TRUE)
summary_stats <- result$summary # Still works but warns
# Backwards compatible - no changes needed
qic_data <- bfh_qic(data, x, y, chart_type = "i", return.data = TRUE)
# PNG export
bfh_qic(data, x, y, chart_type = "i", chart_title = "Infections") |>
bfh_export_png("infections.png", width_mm = 200, height_mm = 120, dpi = 300)
# PDF export (requires Quarto CLI)
bfh_qic(data, x, y, chart_type = "i", chart_title = "Infections") |>
bfh_export_pdf(
"infections_report.pdf",
metadata = list(
hospital = "BFH",
department = "Kvalitetsafdeling",
analysis = "Signifikant fald observeret",
data_definition = "Antal infektioner per måned"
)
)
BFHcharts::quarto_available()inst/templates/typst/README.mdcreate_spc_chart() has been renamed to bfh_qic()
Update your code by replacing create_spc_chart with bfh_qic:
# Before (0.1.0):
plot <- create_spc_chart(
data = my_data,
x = date,
y = value,
chart_type = "i"
)
# After (0.2.0):
plot <- bfh_qic(
data = my_data,
x = date,
y = value,
chart_type = "i"
)
No other changes required - all parameters work exactly the same.