Files
stack/assets/fhirworx.py
kert c81992184a
Some checks failed
CI / skinny-install (aco) (push) Successful in 54s
CI / skinny-install (api) (push) Successful in 32s
CI / skinny-install (bcda) (push) Successful in 33s
CI / skinny-install (bib) (push) Successful in 30s
CI / skinny-install (bls) (push) Successful in 26s
CI / lint-test (push) Successful in 3m19s
CI / skinny-install (ccw) (push) Successful in 27s
CI / skinny-install (cms) (push) Successful in 32s
CI / skinny-install (cli) (push) Successful in 38s
CI / skinny-install (conf) (push) Successful in 33s
CI / skinny-install (opps) (push) Successful in 43s
Infra CI / notebooks (push) Successful in 7s
Infra CI / zotero (push) Successful in 7s
Infra CI / docs (push) Failing after 10s
Infra CI / api (push) Successful in 13s
Infra CI / mc (push) Successful in 7s
CI / skinny-install (perf) (push) Successful in 31s
CI / skinny-install (pfs) (push) Successful in 30s
CI / skinny-install (rex) (push) Successful in 31s
Deploy / build-scan-report (push) Failing after 1m47s
feat: port fhirworx theme — editorial redesign based on hti5.org
Rip and replace nature/loch with fhirworx — a Federal Register / data
journalism aesthetic inspired by hti5.org.

Typography: Playfair Display for display headings, Source Serif 4 for
reading body, JetBrains Mono reserved for IDs, labels, metadata (docket
codes, commit hashes, query editors, file paths, ports). Stripped
carpet-bombed font-family: mono !important across inject.css and
per-service sheets; kept mono only where load-bearing (th, pre/code,
query editors, span IDs, badge/chip/tag, bucket names, query IDs).

Palette: warm cream (#F7F5F0) background, near-black warm foreground,
deep federal navy (#1C2B3A) primary/sidebar, indigo chart scale +
teal (support) + amber (opposition) semantic accents. PALETTE extended
to 8 colours (+ plum, gray, sepia) for categorical notebook charts.

Rename (no backwards compat):
- assets/nature.py → assets/fhirworx.py
- assets/css/loch.css → assets/css/fhirworx.css
- theme-loch.css → theme-fhirworx.css (Gitea)
- Altair theme registered as "fhirworx" via new alt.theme.register
  decorator API, eliminating the altair 5.5 deprecation warning
- Traefik inject-loch middleware → inject-fhirworx
- Gitea DEFAULT_THEME=fhirworx
- Delete empty theme-throwback.css stub

Rewrote infra/nginx/index.html and infra/gitea/custom/templates/home.tmpl
with editorial masthead (rule-line + Playfair headline + mono identifier
+ serif tagline + rule-line-thin), SERVICES eyebrow + Infrastructure h2,
tiles with .tile-eyebrow / .tile-title / .tile-desc / .tile-port
structure. Stripped letter-spacing: 2-4px tricks and text-transform:
uppercase from non-metadata elements across all per-service sheets.

Propagated import renames: src/conf/connect.py, src/aco/dag.py,
notebooks/acodb_explorer.py, tests/conf/test_connect.py,
tests/test_notebook_layout.py, docs/src/css/custom.css, README.md,
README.md.j2, compose.yml, infra/traefik/dynamic/services.yml.
2026-04-10 14:37:20 -04:00

236 lines
8.5 KiB
Python

"""HTI-5 Federal Register design system — shared palette and theme.
Design: Federal Register Meets Data Journalism.
Inspired by ProPublica, The Marshall Project, and the Federal Register.
Provides:
- ``PALETTE`` — 8-colour chart sequence (indigo scale + teal + amber + plum + gray + sepia)
- ``SCHEMA_COLORS`` — pipeline schema → colour mapping for DAG viz
- ``altair_theme()`` — register-and-enable for Altair charts
- ``mpl_rc()`` — dict of matplotlib rcParams
- ``cytoscape_style()`` — Cytoscape.js stylesheet fragment
Design principles (HTI-5):
* Typography-first: Playfair Display for display, Source Serif 4 for body
* Off-white (#F7F5F0) background, near-black (#1A1A18) text
* Deep federal navy sidebar (#1C2B3A)
* Warm amber accent for opposition, teal for support
* Monospace font (JetBrains Mono) for IDs, labels, metadata
"""
from __future__ import annotations
# ── Chart colour palette ──────────────────────────────────────────────
# Eight-colour categorical sequence for multi-series charts.
# First five match the hti5 --chart-* tokens (indigo scale + teal + amber);
# remaining three extend the editorial palette for categorical variety.
PALETTE = [
"#6B7FD7", # indigo medium (chart-1)
"#4A5CB8", # indigo dark (chart-2)
"#2E3D8F", # indigo deep (chart-3)
"#2E8B6E", # teal / support (chart-4)
"#C8702A", # amber / oppose (chart-5)
"#7448C0", # plum
"#6B6B68", # neutral gray
"#A85800", # sepia / dark amber
]
# Semantic aliases
INDIGO_MED, INDIGO_DARK, INDIGO_DEEP, TEAL, AMBER, PLUM, GRAY, SEPIA = PALETTE
# ── Core palette ──────────────────────────────────────────────────────
# Backgrounds and text following hti5 design tokens
BG_LIGHT = "#F7F5F0" # --background: off-white cream
BG_CARD = "#FAFAF7" # --card: slightly warmer white
BG_DARK = "#1C2B3A" # --primary / sidebar: deep federal navy
BG_CHART = "transparent" # chart area — let container bg show through
AXIS_COLOR = "#D4D0C8" # --border: warm gray
TEXT_COLOR = "#1A1A18" # --foreground: near-black
TITLE_COLOR = "#1A1A18" # --foreground for titles
MUTED_COLOR = "#6B6B68" # --muted-foreground
# ── Typography ────────────────────────────────────────────────────────
FONT_DISPLAY = "Playfair Display, Georgia, serif"
FONT_BODY = "Source Serif 4, Georgia, serif"
FONT_MONO = "JetBrains Mono, Fira Code, monospace"
FONT_FAMILY = FONT_BODY # default for charts
FONT_SIZE_BODY = 11 # pt — readable at screen DPI
FONT_SIZE_AXIS = 11 # pt
FONT_SIZE_TITLE = 13 # pt
FONT_SIZE_LABEL = 12 # pt
LINE_WIDTH = 0.75 # pt — axis/tick stroke
TICK_SIZE = 4 # pt
# ── Figure dimensions (single-column width: 89 mm) ───────────────────
FIG_WIDTH_MM = 89
FIG_HEIGHT_MM = 60
FIG_WIDTH_IN = FIG_WIDTH_MM / 25.4
FIG_HEIGHT_IN = FIG_HEIGHT_MM / 25.4
DPI = 150 # screen-appropriate DPI
# ── Pipeline schema colours (for DAG / lineage viz) ──────────────────
SCHEMA_COLORS: dict[str, str] = {
"core": INDIGO_DEEP,
"input_layer": TEAL,
"cclf": INDIGO_DARK,
"claims_preprocessing": AMBER,
"readmissions": TEAL,
"ahrq_measures": INDIGO_MED,
"pharmacy": "#7448C0", # purple
"quality_measures": "#C0392B", # destructive / red
"hcc_suspecting": "#A85800", # warm amber-brown
"provider_attribution": MUTED_COLOR,
"main": INDIGO_MED,
"data_quality": INDIGO_DARK,
}
EXTERNAL_COLOR = "#9B9B98"
def schema_color(schema: str) -> str:
"""Return the palette colour for a pipeline schema."""
return SCHEMA_COLORS.get(schema, EXTERNAL_COLOR)
# ── Altair theme ──────────────────────────────────────────────────────
def _altair_theme_config() -> dict:
"""Vega-Lite config dict conforming to HTI-5 editorial specs."""
return {
"config": {
"background": BG_LIGHT,
"font": FONT_BODY,
"title": {
"font": FONT_DISPLAY,
"fontSize": FONT_SIZE_TITLE,
"fontWeight": "bold",
"color": TITLE_COLOR,
"anchor": "start",
},
"axis": {
"labelFont": FONT_MONO,
"labelFontSize": FONT_SIZE_AXIS,
"labelColor": MUTED_COLOR,
"titleFont": FONT_BODY,
"titleFontSize": FONT_SIZE_AXIS,
"titleColor": TEXT_COLOR,
"gridColor": AXIS_COLOR,
"gridOpacity": 0.5,
"domainColor": AXIS_COLOR,
"domainWidth": LINE_WIDTH,
"tickColor": AXIS_COLOR,
"tickSize": TICK_SIZE,
"tickWidth": LINE_WIDTH,
},
"legend": {
"labelFont": FONT_MONO,
"labelFontSize": FONT_SIZE_BODY,
"labelColor": TEXT_COLOR,
"titleFont": FONT_BODY,
"titleFontSize": FONT_SIZE_BODY,
"titleColor": TEXT_COLOR,
"symbolSize": 60,
},
"view": {
"stroke": AXIS_COLOR,
"strokeWidth": LINE_WIDTH,
"fill": BG_CARD,
},
"range": {
"category": PALETTE,
},
"mark": {
"color": PALETTE[0],
},
"bar": {
"color": PALETTE[0],
},
"arc": {
"stroke": BG_LIGHT,
"strokeWidth": 1,
},
"line": {
"strokeWidth": 1.5,
},
"point": {
"size": 40,
},
"area": {
"opacity": 0.7,
},
}
}
def altair_theme() -> None:
"""Register and enable the fhirworx Altair chart theme.
Uses the Altair 5.5+ ``alt.theme.register`` decorator API.
"""
import altair as alt
@alt.theme.register("fhirworx", enable=True)
def _fhirworx_theme() -> dict:
return _altair_theme_config()
# ── Matplotlib rcParams ──────────────────────────────────────────────
def mpl_rc() -> dict:
"""Return an rcParams dict for ``matplotlib.rcParams.update()``."""
return {
"font.family": "serif",
"font.serif": ["Source Serif 4", "Georgia", "DejaVu Serif"],
"font.size": FONT_SIZE_BODY,
"axes.titlesize": FONT_SIZE_TITLE,
"axes.titleweight": "bold",
"axes.titlefontproperties": {"family": "serif"},
"axes.labelsize": FONT_SIZE_AXIS,
"axes.linewidth": LINE_WIDTH,
"axes.edgecolor": AXIS_COLOR,
"axes.labelcolor": TEXT_COLOR,
"axes.facecolor": BG_CARD,
"axes.grid": True,
"axes.grid.axis": "y",
"axes.prop_cycle": __import__("cycler").cycler(color=PALETTE),
"grid.color": AXIS_COLOR,
"grid.linewidth": 0.5,
"grid.alpha": 0.6,
"xtick.major.size": TICK_SIZE,
"xtick.major.width": LINE_WIDTH,
"xtick.color": AXIS_COLOR,
"xtick.labelsize": FONT_SIZE_BODY,
"ytick.major.size": TICK_SIZE,
"ytick.major.width": LINE_WIDTH,
"ytick.color": AXIS_COLOR,
"ytick.labelsize": FONT_SIZE_BODY,
"figure.figsize": (FIG_WIDTH_IN, FIG_HEIGHT_IN),
"figure.dpi": DPI,
"figure.facecolor": BG_LIGHT,
"savefig.dpi": DPI,
"savefig.facecolor": BG_LIGHT,
"legend.fontsize": FONT_SIZE_BODY,
"legend.frameon": False,
"text.color": TEXT_COLOR,
}
# ── Cytoscape.js style helpers ───────────────────────────────────────
def cytoscape_node_style() -> dict:
"""Base Cytoscape.js node style properties (HTI-5 editorial style)."""
return {
"font-family": FONT_MONO,
"font-size": "9px",
"text-valign": "center",
"text-halign": "center",
"shape": "round-rectangle",
"background-color": BG_CARD,
"border-color": AXIS_COLOR,
"color": TEXT_COLOR,
}