Output:
Chunk ID: 63f9bf
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3245
Output:
#!/usr/bin/env python3
"""Consume locally queued Dashboard actions outside the network-facing service."""

from __future__ import annotations

import json
import os
import re
import stat
import subprocess
import sys
import sqlite3
import hashlib
from datetime import date, datetime
from pathlib import Path
from typing import Any
from zoneinfo import ZoneInfo

BASE = Path.home() / ".hermes" / "assets" / "Gesundheit"
DEFAULT_DASHBOARD_DB = (BASE / "health_data.db").resolve()
DASHBOARD_DB = (
    Path(os.environ["HEALTH_DASHBOARD_DB"]).resolve()
    if os.environ.get("HEALTH_DASHBOARD_DB")
    else None
)
ACTION_INBOX = Path(
    os.environ.get(
        "HEALTH_DASHBOARD_ACTION_INBOX",
        str(BASE / "runtime" / "dashboard-actions"),
    )
).resolve()
DASHBOARD_FILE = Path(
    os.environ.get(
        "HEALTH_DASHBOARD_FILE",
        str(BASE / "reports" / "health_dashboard_v4.html"),
    )
).resolve()
DASHBOARD_V5_FILE = (
    Path(os.environ["HEALTH_DASHBOARD_V5_FILE"]).resolve()
    if os.environ.get("HEALTH_DASHBOARD_V5_FILE")
    else None
)
SCRIPT_DIR = Path(__file__).resolve().parent
QUICK_ADD = SCRIPT_DIR / "health_symptom_quick_add.py"
DASHBOARD_V4 = SCRIPT_DIR / "health_dashboard_v4.py"
DASHBOARD_V5 = SCRIPT_DIR / "health_dashboard_v5.py"
FIELDS = ("aphthen", "gi", "fatigue", "skin", "eyes", "joints", "vascular")
NAME_RE = re.compile(r"[0-9a-f]{32}\.json")
LOCAL_TIMEZONE = ZoneInfo("Europe/Zurich")
V5_PROFILE_DEFAULT = "default"
V5_PROFILE_HEALTH_RECORD_6E = "health-record-6e"
ALLOWED_V5_PROFILES = frozenset({V5_PROFILE_DEFAULT, V5_PROFILE_HEALTH_RECORD_6E})
MAPPING_DECISIONS = frozenset({"assign", "composite", "ignore"})
MAPPING_CONFIDENCE = frozenset({"low", "medium", "high"})
MAPPING_METHODS = frozenset({"sighi_reference", "ingredient_label", "manual_review", "local_alias"})
HISTAMINE_DEFINITION_VERSION = "sighi_mapping_load_v2"

def parse_v5_profile(raw: str | None) -> str:
    """Return the one allowed regeneration profile or fail before any mutation."""
    value = V5_PROFILE_DEFAULT if raw is None or raw == "" else raw
    if value not in ALLOWED_V5_PROFILES:
        raise RuntimeError("invalid HEALTH_DASHBOARD_V5_PROFILE")
    return value

DASHBOARD_V5_PROFILE = parse_v5_profile(os.environ.get("HEALTH_DASHBOARD_V5_PROFILE"))

def local_today() -> date:
    return datetime.now(LOCAL_TIMEZONE).date()

def validate_symptom_payload(payload: dict[str, Any]) -> dict[str, Any]:
    if set(payload) != {
        "version",
        "action",
        "date",
        "scores",
        "notes",
    }:
        raise ValueError("invalid payload shape")
    if payload["version"] != 1 or payload["action"] != "symptom_checkin":
        raise ValueError("unsupported action")
    try:
        day = date.fromisoformat(payload["date"])
    except (TypeError, ValueError) as exc:
        raise ValueError("invalid date") from exc
    if day.year < 2000 or day > local_today():
        raise ValueError("date outside allowed range")
    scores = payload["scores"]
    if not isinstance(scores, dict) or set(scores) != set(FIELDS):
        raise ValueError("incomplete scores")
    if any(
        type(scores[field]) is not int or scores[field] not in range(4)
        for field in FIELDS
    ):
        raise ValueError("invalid score")
    notes = payload["notes"]
    if not isinstance(notes, str) or len(notes) > 300:
        raise ValueError("invalid notes")
    return {
        "version": 1,
        "action": "symptom_checkin",
        "date": day.isoformat(),
        "scores": {field: scores[field] for field in FIELDS},
        "notes": notes.strip(),
    }

def validate_mapping_payload(payload: dict[str, Any]) -> dict[str, Any]:
    required = {
        "version",
        "action",
        "decision",
        "queue_key",
        "alias",
        "canonical_food",
        "sighi_score",
        "confidence",
        "mapping_method",
        "source_label",
        "source_version",
        "note",
        "ingredient_review_required",
    }
    optional = {"personal_tolerance"}
    if not required <= set(payload) <= required | optional:
        raise ValueError("invalid mapping payload shape")
    if payload["version"] != 1 or payload["action"] != "nutrition_mapping":
        raise ValueError("unsupported action")
    decision = payload["decision"]
    queue_key = payload["queue_key"]
    alias = " ".join(str(payload["alias"] or "").split())
    canonical = " ".join(str(payload["canonical_food"] or "").split())
    score = payload["sighi_score"]
    confidence = payload["confidence"]
    method = payload["mapping_method"]
    source_label = " ".join(str(payload["source_label"] or "").split())
    source_version = " ".join(str(payload["source_version"] or "").split())
    note = " ".join(str(payload["note"] or "").split())
    ingredient_review = payload["ingredient_review_required"]
    tolerance = payload.get("personal_tolerance")
    if not isinstance(queue_key, str) or not re.fullmatch(r"[a-z0-9_]{1,80}", queue_key):
        raise ValueError("invalid queue key")
    if decision not in MAPPING_DECISIONS or confidence not in MAPPING_CONFIDENCE or method not in MAPPING_METHODS:
        raise ValueError("invalid mapping value")
    for field_name, text, maximum in (
        ("alias", alias, 160),
        ("canonical food", canonical, 120),
        ("source label", source_label, 80),
        ("source version", source_version, 40),
        ("note", note, 300),
    ):
        if len(text) > maximum or re.search(r"[\x00-\x1f\x7f]|https?://|/|\\\\|\.hermes", text, re.I):
            raise ValueError(f"invalid {field_name}")
    if not alias or not source_label or not source_version:
        raise ValueError("required mapping documentation missing")
    if score != "unknown" and (type(score) is not int or score not in range(4)):
        raise ValueError("invalid score")
    if type(ingredient_review) is not bool:
        raise ValueError("invalid ingredient review flag")
    if tolerance is not None and (not isinstance(tolerance, str) or len(tolerance) > 160 or re.search(r"[\x00-\x1f\x7f]|https?://|/|\\\\|\.hermes", tolerance, re.I)):
        raise ValueError("invalid personal tolerance")
    if decision == "assign":
        if not canonical or score == "unknown" or ingredient_review:
            raise ValueError("assign requires canonical score without ingredient review")
    elif decision == "composite":
        if score != "unknown" or not ingredient_review:
            raise ValueError("composite must stay unclassified for ingredient review")
    elif decision == "ignore":
        if score != "unknown" or canonical or ingredient_review:
            raise ValueError("ignore must not classify")
    return {
        "version": 1,
        "action": "nutrition_mapping",
        "decision": decision,
        "queue_key": queue_key,
        "alias": alias,
        "canonical_food": canonical,
        "sighi_score": score,
        "confidence": confidence,
        "mapping_method": method,
        "source_label": source_label,
        "source_version": source_version,
        "note": note,
        "ingredient_review_required": ingredient_review,
        "personal_tolerance": tolerance,
    }

def validate_payload(payload: Any) -> dict[str, Any]:
    if not isinstance(payload, dict):
        raise ValueError("invalid payload shape")
    action = payload.get("action")
    if action == "symptom_checkin":
        return validate_symptom_payload(payload)
    if action == "nutrition_mapping":
        return validate_mapping_payload(payload)
    raise ValueError("unsupported action")

def reject_duplicate_object_pairs(pairs: list[tuple[str, object]]) -> dict[str, object]:
    result: dict[str, object] = {}
    for key, value in pairs:
        if key in result:
            raise ValueError("duplicate JSON key")
        result[key] = value
    return result

def ensure_private_inbox() -> None:
    ACTION_INBOX.mkdir(mode=0o700, parents=True, exist_ok=True)
    metadata = ACTION_INBOX.lstat()
    if not stat.S_ISDIR(metadata.st_mode) or metadata.st_uid != os.getuid():
        raise OSError("action inbox is not a private owned directory")
    os.chmod(ACTION_INBOX, 0o700)

def load_action(path: Path) -> dict[str, Any]:
    if not NAME_RE.fullmatch(path.name):
        raise ValueError("invalid filename")
    descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
    try:
        metadata = os.fstat(descriptor)
        if (
            not stat.S_ISREG(metadata.st_mode)
            or metadata.st_uid != os.getuid()
            or stat.S_IMODE(metadata.st_mode) != 0o600
        ):
            raise ValueError("invalid action file permissions")
        if metadata.st_size > 4096:
            raise ValueError("invalid action file")
        raw = os.read(descriptor, 4097)
    finally:
        os.close(descriptor)
    if len(raw) > 4096:
        raise ValueError("action too large")
    return validate_payload(
        json.loads(raw.decode("utf-8"), object_pairs_hook=reject_duplicate_object_pairs)
    )

def apply_symptom_action(day: str, scores: dict[str, int], notes: str) -> None:
    if DASHBOARD_DB is None:
        raise RuntimeError("HEALTH_DASHBOARD_DB is required")
    command = [
        sys.executable,
        str(QUICK_ADD),
        "--db",
        str(DASHBOARD_DB),
        "--date",
        day,
        "--notes",
        notes,
    ]
    for field in FIELDS:
        command.extend((f"--{field}", str(scores[field])))
    subprocess.run(command, check=True, timeout=120, capture_output=True, text=True)
    if DASHBOARD_DB == DEFAULT_DASHBOARD_DB:
        subprocess.run(
            [sys.executable, str(DASHBOARD_V4), "--output", str(DASHBOARD_FILE)],
            check=True,
            timeout=120,
            capture_output=True,
            text=True,
        )

def _slug(value: str) -> str:
    normalized = value.casefold().translate(str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss"}))
    return re.sub(r"[^a-z0-9]+", "_", normalized).strip("_")

def _score_label(max_score: int, unknown: int, load: float) -> str:
    if unknown:
        return "unknown"
    return "classified"

def _public_numeric(value: Any) -> float | None:
    try:
        numeric = float(value)
    except (TypeError, ValueError):
        return None
    return numeric if numeric == numeric and numeric not in {float("inf"), float("-inf")} else None

NUTRIENT_ALLOWLIST = {
    "energy.energy": "kcal",
    "nutrient.protein": "g",
    "nutrient.carb": "g",
    "nutrient.fat": "g",
    "nutrient.fiber": "g",
    "nutrient.sugar": "g",
    "nutrient.saturated_fat": "g",
    "nutrient.salt": "g",
    "nutrient.sodium": "mg",
}

def _nutrient_totals(connection: sqlite3.Connection, item_ids: list[int]) -> str:
    if not item_ids:
        return "{}"
    placeholders = ",".join("?" for _ in item_ids)
    totals: dict[str, float] = {}
    for row in connection.execute(
        f"""SELECT nutrient_key,value,unit FROM nutrition_item_nutrients
            WHERE item_id IN ({placeholders})""",
        item_ids,
    ):
        key = str(row["nutrient_key"] or "")
        unit = str(row["unit"] or "")
        value = _public_numeric(row["value"])
        if key not in NUTRIENT_ALLOWLIST or unit != NUTRIENT_ALLOWLIST[key] or value is None:
            continue
        totals[key] = totals.get(key, 0.0) + value
    return json.dumps({key: round(value, 3) for key, value in sorted(totals.items())}, separators=(",", ":"))

def ensure_nutrition_mapping_schema(connection: sqlite3.Connection) -> None:
    connection.executescript(
        """
        CREATE TABLE IF NOT EXISTS nutrition_mapping_action_log (
            action_id TEXT PRIMARY KEY,
            action_hash TEXT NOT NULL,
            queue_key TEXT NOT NULL,
            decision TEXT,
            alias TEXT,
            canonical_food TEXT,
            sighi_score INTEGER,
            confidence TEXT,
            mapping_method TEXT,
            source_label TEXT,
            source_version TEXT,
            note TEXT,
            ingredient_review_required INTEGER,
            personal_tolerance TEXT,
            definition_version TEXT,
            applied_at TEXT DEFAULT CURRENT_TIMESTAMP
        );
        CREATE TABLE IF NOT EXISTS nutrition_mapping_provenance (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            action_id TEXT NOT NULL,
            normalized_name TEXT NOT NULL,
            decision TEXT,
            alias TEXT,
            canonical_food TEXT,
            sighi_score INTEGER,
            confidence TEXT,
            mapping_method TEXT,
            source_label TEXT,
            source_version TEXT,
            note TEXT,
            ingredient_review_required INTEGER,
            personal_tolerance TEXT,
            definition_version TEXT,
            affected_day TEXT NOT NULL,
            applied_at TEXT DEFAULT CURRENT_TIMESTAMP,
            UNIQUE(action_id, affected_day)


Output:
Chunk ID: 7081e2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2701
Output:
#!/usr/bin/env python3
"""Serve the local Health Dashboard with strict routes and privacy headers."""

from __future__ import annotations

import mimetypes
import json
import os
import re
import secrets
import sqlite3
import stat
import threading
import time
from datetime import date, datetime
from http.cookies import CookieError, SimpleCookie
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, quote, unquote, urlparse
from zoneinfo import ZoneInfo

from dashboard_v5.read_api import (
    APIError,
    connect_read_only,
    dispatch_api,
    _resolve_record_document,
)
from dashboard_v5.document_originals import configured_original_roots, probe_original

BASE = Path.home() / ".hermes" / "assets" / "Gesundheit"
API_DB = (
    Path(os.environ["HEALTH_DASHBOARD_DB"]).resolve()
    if os.environ.get("HEALTH_DASHBOARD_DB")
    else None
)
DB = BASE / "health_data.db"
REPORTS = BASE / "reports"
DASHBOARD_FILE = Path(os.environ["HEALTH_DASHBOARD_FILE"]).resolve()
DASHBOARD_V5_FILE = (
    Path(os.environ["HEALTH_DASHBOARD_V5_FILE"]).resolve()
    if os.environ.get("HEALTH_DASHBOARD_V5_FILE")
    else None
)
ASSET_DIR = Path(
    os.environ.get(
        "HEALTH_DASHBOARD_ASSET_DIR",
        str(BASE / "scripts" / "assets" / "health-assets"),
    )
).resolve()
BIND_HOST = os.environ.get("HEALTH_DASHBOARD_HOST", "127.0.0.1")
BIND_PORT = int(os.environ.get("HEALTH_DASHBOARD_PORT", "8014"))
ALLOWED_HOSTS = {
    value.strip().lower().rstrip(".")
    for value in os.environ.get(
        "HEALTH_DASHBOARD_ALLOWED_HOSTS", "127.0.0.1,localhost"
    ).split(",")
    if value.strip()
}
ROUTE = "/health-dashboard"
V5_ROUTE = "/health-dashboard-v5"
ASSET_ROUTES = {
    "/health-assets/chart.umd.min.js": (
        "chart.umd.min.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5.js": (
        "dashboard-v5.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5-range.js": (
        "dashboard-v5-range.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5-global-search.js": (
        "dashboard-v5-global-search.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5-api-explorer.js": (
        "dashboard-v5-api-explorer.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5-echarts.js": (
        "dashboard-v5-echarts.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5-day-controller.js": (
        "dashboard-v5-day-controller.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5-record.js": (
        "dashboard-v5-record.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5.css": ("dashboard-v5.css", "text/css; charset=utf-8"),
    "/health-assets/echarts-6.1.0.min.js": (
        "echarts-6.1.0.min.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/fullcalendar-6.1.21.min.js": (
        "fullcalendar-6.1.21.min.js",
        "text/javascript; charset=utf-8",
    ),
    "/health-assets/dashboard-v5-echarts-prototype.js": (
        "dashboard-v5-echarts-prototype.js",
        "text/javascript; charset=utf-8",
    ),
}
CHECKIN_ROUTE = "/health-actions/symptom-checkin"
NUTRITION_MAPPING_ROUTE = "/health-actions/nutrition-mapping"
ACTION_INBOX = Path(
    os.environ.get(
        "HEALTH_DASHBOARD_ACTION_INBOX",
        str(BASE / "runtime" / "dashboard-actions"),
    )
).resolve()
TEST_INSTANCE_ID = os.environ.get("HEALTH_DASHBOARD_TEST_INSTANCE_ID", "").strip()
API_TOKEN_FILE = (
    Path(os.environ["HEALTH_DASHBOARD_API_TOKEN_FILE"]).expanduser()
    if os.environ.get("HEALTH_DASHBOARD_API_TOKEN_FILE")
    else None
)
BROWSER_SESSION_TTL_SECONDS = max(
    60, min(int(os.environ.get("HEALTH_DASHBOARD_BROWSER_SESSION_TTL", "900")), 3600)
)
BROWSER_SESSION_COOKIE_SECURE = (
    os.environ.get("HEALTH_DASHBOARD_BROWSER_SESSION_COOKIE_SECURE") == "1"
)
if TEST_INSTANCE_ID:
    if not re.fullmatch(r"[A-Za-z0-9_-]{8,64}", TEST_INSTANCE_ID):
        raise RuntimeError("invalid HEALTH_DASHBOARD_TEST_INSTANCE_ID")
    if not ACTION_INBOX.is_relative_to(Path("/tmp").resolve()):
        raise RuntimeError(
            "synthetic test instance requires an action inbox below /tmp"
        )
SYMPTOM_FIELDS = ("aphthen", "gi", "fatigue", "skin", "eyes", "joints", "vascular")
MAPPING_DECISIONS = {"assign", "composite", "ignore"}
MAPPING_SCORES = {"0", "1", "2", "3", "unknown"}
MAPPING_CONFIDENCE = {"low", "medium", "high"}
MAPPING_METHODS = {"sighi_reference", "ingredient_label", "manual_review", "local_alias"}
CSRF_TTL_SECONDS = max(
    60, min(int(os.environ.get("HEALTH_DASHBOARD_CSRF_TTL", "900")), 3600)
)
QUEUE_RECEIPT_TTL_SECONDS = 120
MAX_API_RESPONSE_BYTES = 512_000
CAPTURE_TIMEZONE = ZoneInfo("Europe/Zurich")
MAX_PENDING_ACTIONS = max(
    1, min(int(os.environ.get("HEALTH_DASHBOARD_MAX_PENDING_ACTIONS", "64")), 256)
)
MAX_ORIGINAL_BYTES = 20 * 1024 * 1024
ORIGINAL_MIME_MAGIC = (
    (b"%PDF-", "application/pdf", ".pdf"),
    (b"\x89PNG\r\n\x1a\n", "image/png", ".png"),
    (b"\xff\xd8\xff", "image/jpeg", ".jpg"),
)
ORIGINAL_ROOTS = configured_original_roots()
_csrf_tokens: dict[str, float] = {}
_csrf_lock = threading.Lock()
_queue_receipts: dict[str, float] = {}
_queue_receipt_lock = threading.Lock()
_browser_sessions: dict[str, float] = {}
_browser_session_lock = threading.Lock()
_action_lock = threading.Lock()

class QueueFullError(OSError):
    """Raised when the bounded private action queue cannot accept more work."""

def load_api_token() -> str:
    if API_TOKEN_FILE is None:
        raise OSError("read-only API authentication is not configured")
    descriptor = os.open(
        API_TOKEN_FILE,
        os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK,
    )
    try:
        metadata = os.fstat(descriptor)
        if (
            not stat.S_ISREG(metadata.st_mode)
            or metadata.st_uid != os.getuid()
            or stat.S_IMODE(metadata.st_mode) != 0o600
            or metadata.st_size > 256
        ):
            raise OSError("read-only API token file is not private")
        raw = os.read(descriptor, 257)
    finally:
        os.close(descriptor)
    token = raw.decode("ascii").strip()
    if not re.fullmatch(r"[A-Za-z0-9_-]{32,128}", token):
        raise OSError("read-only API token is invalid")
    return token

def api_request_is_authenticated(header: str, token: str) -> bool:
    if not header.startswith("Bearer ") or header.count(" ") != 1:
        return False
    supplied = header.removeprefix("Bearer ")
    return len(supplied) == len(token) and secrets.compare_digest(supplied, token)

def issue_browser_session() -> str:
    now = time.monotonic()
    with _browser_session_lock:
        expired = [key for key, expiry in _browser_sessions.items() if expiry <= now]
        for key in expired:
            del _browser_sessions[key]
        session_id = secrets.token_urlsafe(32)
        _browser_sessions[session_id] = now + BROWSER_SESSION_TTL_SECONDS
    return session_id

def browser_session_is_authenticated(cookie_header: str) -> bool:
    try:
        cookie = SimpleCookie(cookie_header)
    except (CookieError, ValueError):
        return False
    morsel = cookie.get("health_api_session")
    if morsel is None:
        return False
    session_id = morsel.value
    if not re.fullmatch(r"[A-Za-z0-9_-]{32,128}", session_id):
        return False
    now = time.monotonic()
    with _browser_session_lock:
        expiry = _browser_sessions.get(session_id)
        if expiry is None or expiry <= now:
            _browser_sessions.pop(session_id, None)
            return False
        return True

def resolve_doc_path(*values: object) -> Path | None:
    for value in values:
        if not value:
            continue
        raw = str(value)
        candidates = [
            Path(os.path.expanduser(raw)),
            BASE / raw,
            Path.home() / raw,
            Path(raw.replace(str(Path.home() / "Gesundheit"), str(BASE))),
        ]
        for candidate in candidates:
            try:
                resolved = candidate.resolve()
                if resolved.is_file() and (
                    resolved.is_relative_to(BASE.resolve())
                    or resolved.is_relative_to(REPORTS.resolve())
                ):
                    return resolved
            except OSError:
                continue
    return None

def document_path(doc_id: int) -> tuple[Path, str] | None:
    connection = sqlite3.connect(f"{DB.resolve().as_uri()}?mode=ro", uri=True)
    connection.execute("PRAGMA query_only=ON")
    connection.row_factory = sqlite3.Row
    try:
        row = connection.execute(
            "SELECT datei_name,dateipfad,local_original_path FROM dokumente WHERE id=?",
            (doc_id,),
        ).fetchone()
        if not row:
            return None
        path = resolve_doc_path(row["local_original_path"], row["dateipfad"])
        if not path:
            return None
        return path, str(row["datei_name"] or path.name)
    finally:
        connection.close()

def _open_regular_beneath(root: Path, candidate: Path) -> int:
    """Open each path component without following symlinks."""
    root = root.absolute()
    candidate = candidate.absolute()
    try:
        relative = candidate.relative_to(root)
    except ValueError as error:
        raise OSError("outside_root") from error
    if not relative.parts or any(part in {"", ".", ".."} for part in relative.parts):
        raise OSError("invalid_path")
    directory = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
    try:
        for part in relative.parts[:-1]:
            child = os.open(
                part,
                os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
                dir_fd=directory,
            )
            os.close(directory)
            directory = child
        return os.open(
            relative.parts[-1],
            os.O_RDONLY | os.O_NOFOLLOW | getattr(os, "O_NONBLOCK", 0),
            dir_fd=directory,
        )
    finally:
        os.close(directory)

def open_verified_original(
    database: Path, opaque: str
) -> tuple[int, int, str, str] | None:
    """Resolve a reviewed document and pin the same descriptor used by HEAD/GET."""
    connection = connect_read_only(database)
    try:
        row = _resolve_record_document(connection, opaque, reviewed=True)
    except APIError:
        return None
    finally:
        connection.close()
    roots = (
        ORIGINAL_ROOTS
        if os.environ.get("HEALTH_DASHBOARD_ORIGINAL_ROOTS")
        else (BASE.absolute(), REPORTS.absolute())
    )
    probe = probe_original(


Output:
Chunk ID: 2732a2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2301
Output:
-- HealthManager canonical schema (no patient data)
-- Generated from the productive SQLite structure on 2026-07-12.
-- This file contains DDL only. Never commit a productive database or data dump.

PRAGMA foreign_keys=ON;

CREATE TABLE apple_health_import_files (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            file_name TEXT,
            file_path TEXT,
            file_hash TEXT UNIQUE,
            record_count INTEGER,
            inserted_count INTEGER,
            skipped_count INTEGER,
            status TEXT,
            error TEXT,
            imported_at TEXT DEFAULT CURRENT_TIMESTAMP,
            retry_count INTEGER NOT NULL DEFAULT 0,
            last_attempt_at TEXT
        );

CREATE TABLE apple_health_records (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            record_type TEXT,
            metric TEXT,
            start_date TEXT,
            end_date TEXT,
            value REAL,
            value_text TEXT,
            unit TEXT,
            source_name TEXT,
            source_version TEXT,
            device TEXT,
            file_name TEXT,
            file_hash TEXT,
            record_hash TEXT UNIQUE,
            raw_json TEXT NOT NULL,
            imported_at TEXT DEFAULT CURRENT_TIMESTAMP
        );

CREATE TABLE arztbesuche (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        datum TEXT NOT NULL,
        arzt TEXT,
        klinik TEXT,
        grund TEXT,
        zusammenfassung TEXT,
        labor_verweis TEXT,
        notizen TEXT,
        created_at TEXT DEFAULT (datetime('now','localtime'))
    );

CREATE TABLE auswertungen (id INTEGER PRIMARY KEY, datum TEXT NOT NULL, typ TEXT NOT NULL, parameter TEXT, ergebnis TEXT, notizen TEXT, created_at TEXT DEFAULT (datetime('now','localtime')));

CREATE TABLE document_insights (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            dokument_id INTEGER,
            batch_id TEXT,
            insight_type TEXT NOT NULL,
            datum TEXT,
            titel TEXT NOT NULL,
            inhalt TEXT,
            severity TEXT DEFAULT 'info',
            source TEXT,
            created_at TEXT DEFAULT (datetime('now','localtime'))
        );

CREATE TABLE dokumente (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            datei_name TEXT NOT NULL,
            dateipfad TEXT NOT NULL,
            daten_typ TEXT NOT NULL CHECK (daten_typ IN ('pdf', 'image')),
            upload_datum TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            groessekbytes INTEGER,
            status TEXT DEFAULT 'neu' CHECK (status IN ('neu', 'eingearbeitet', 'archiviert')),
            verarbeite_datum TIMESTAMP,
            extrahierte_inhalte TEXT, kategorie TEXT, datei_hash TEXT, quelle TEXT, review_status TEXT DEFAULT 'nicht_geprueft', processing_quality TEXT, drive_file_id TEXT, drive_web_url TEXT, local_original_path TEXT, document_date TEXT, institution TEXT,
            FOREIGN KEY (status) REFERENCES dokumente_status
        );

CREATE TABLE dokumente_status (
            status TEXT PRIMARY KEY
        );

CREATE TABLE ernaehrung (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        datum TEXT NOT NULL,
        mahlzeit TEXT,
        beschreibung TEXT NOT NULL,
        wirkung TEXT,
        notizen TEXT,
        created_at TEXT DEFAULT (datetime('now','localtime'))
    );

CREATE TABLE health_daily_summary (
            datum TEXT PRIMARY KEY,
            nutrition_json TEXT,
            vitals_json TEXT,
            symptoms_json TEXT,
            medications_json TEXT,
            notes TEXT,
            generated_at TEXT DEFAULT (datetime('now','localtime'))
        );

CREATE TABLE health_event_periods (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            start_date TEXT NOT NULL,
            end_date TEXT,
            event_type TEXT NOT NULL,
            label TEXT,
            severity TEXT,
            location TEXT,
            source TEXT,
            notes TEXT,
            confidence REAL DEFAULT 1.0,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            updated_at TEXT DEFAULT CURRENT_TIMESTAMP
        );

CREATE TABLE health_events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    date TEXT NOT NULL,
    category TEXT,
    parameter TEXT,
    value TEXT,
    unit TEXT,
    source TEXT,
    notes TEXT,
    created_at TEXT DEFAULT (datetime('now','localtime'))
);

CREATE TABLE health_insights (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            insight_date TEXT NOT NULL,
            insight_type TEXT NOT NULL,
            title TEXT NOT NULL,
            description TEXT,
            evidence_json TEXT,
            confidence TEXT DEFAULT 'niedrig',
            severity TEXT DEFAULT 'info',
            status TEXT DEFAULT 'offen',
            created_at TEXT DEFAULT (datetime('now','localtime'))
        );

CREATE TABLE health_processing_runs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            batch_id TEXT UNIQUE NOT NULL,
            run_type TEXT NOT NULL,
            source TEXT,
            source_ref TEXT,
            started_at TEXT DEFAULT (datetime('now','localtime')),
            finished_at TEXT,
            status TEXT DEFAULT 'running',
            summary TEXT,
            artifacts_json TEXT
        );

CREATE TABLE histamine_food_aliases (
    alias TEXT PRIMARY KEY,
    canonical_food TEXT NOT NULL,
    FOREIGN KEY (canonical_food) REFERENCES histamine_food_rules(canonical_food)
);

CREATE TABLE histamine_food_rules (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    canonical_food TEXT NOT NULL UNIQUE,
    sighi_score INTEGER NOT NULL CHECK(sighi_score BETWEEN 0 AND 3),
    category TEXT,
    tags TEXT,
    notes TEXT,
    confidence TEXT DEFAULT 'medium',
    source TEXT DEFAULT 'local_seed_sighi_inspired',
    updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE korrelationen (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        datum TEXT,
        faktor TEXT,          -- z.B. "Milchprodukte", "Kaffee", "Stress"
        auswirkung TEXT,      -- z.B. "CRP erhöht", "Aphthen"
        stärke TEXT,          -- "stark", "mittel", "schwach"
        notizen TEXT,
        created_at TEXT DEFAULT (datetime('now','localtime'))
    );

CREATE TABLE lab_reference_policy (
    id INTEGER PRIMARY KEY CHECK(id=1),
    canonical_rule TEXT NOT NULL,
    xlsx_role TEXT NOT NULL,
    updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE laborwerte (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            dokument_id INTEGER,
            parameter_name TEXT NOT NULL,
            wert TEXT NOT NULL,
            einheit TEXT,
            reference_min TEXT,
            reference_max TEXT,
            einheiten TEXT,
            bemerking TEXT,
            ermittlung_datum TIMESTAMP DEFAULT CURRENT_TIMESTAMP, abnahme_datum TEXT, befund_datum TEXT, wert_original TEXT, quelle TEXT, validierungsstatus TEXT DEFAULT 'unvalidiert', review_batch_id TEXT, source_type TEXT DEFAULT 'unknown', reference_range_source TEXT DEFAULT 'unknown', verified_against_original INTEGER DEFAULT 0, canonical_document_id INTEGER, provenance_note TEXT,
            FOREIGN KEY (dokument_id) REFERENCES dokumente(id)
        );

CREATE TABLE laborwerte_staging (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            batch_id TEXT NOT NULL,
            dokument_id INTEGER,
            parameter_name TEXT NOT NULL,
            kategorie TEXT,
            wert_extrahiert TEXT,
            wert_korrigiert TEXT,
            wert_final TEXT,
            einheit TEXT,
            referenzbereich TEXT,
            reference_min TEXT,
            reference_max TEXT,
            flag TEXT,
            abnahme_datum TEXT,
            befund_datum TEXT,
            dokumentseite TEXT,
            confidence REAL,
            extraktionsmethode TEXT,
            quelle TEXT,
            kommentar TEXT,
            status TEXT DEFAULT 'zur_pruefung',
            created_at TEXT DEFAULT (datetime('now','localtime')),
            reviewed_at TEXT,
            UNIQUE(batch_id, parameter_name, abnahme_datum, einheit)
        );

CREATE TABLE low_histamine_experiments (
        id INTEGER PRIMARY KEY AUTOINCREMENT, start_date TEXT NOT NULL, end_date TEXT, phase TEXT NOT NULL DEFAULT 'baseline', challenge_food TEXT, hypothesis TEXT, notes TEXT, status TEXT DEFAULT 'active', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP);

CREATE TABLE medication_administrations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    datum TEXT NOT NULL,
    medication_name TEXT NOT NULL,
    dose TEXT,
    route TEXT,
    event_type TEXT,
    scheduled_next_date TEXT,
    notes TEXT,
    source TEXT DEFAULT 'manual',
    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(datum, medication_name, event_type)
);

CREATE TABLE medikamente (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            dokument_id INTEGER,
            medikament_name TEXT NOT NULL,
            dosierung TEXT,
            anwendungsform TEXT,
            erhaltungsform TEXT,
            ermittlung_datum TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (dokument_id) REFERENCES dokumente(id)
        );


The current worker stores `personal_tolerance` directly in mapping provenance and the focused test explicitly expects no personal rows. I’m changing that contract now: provenance/action log will carry separate status/note audit fields, while `personal_food_tolerance` becomes the durable personal table only for assign + canonical + non-unknown status.


sed -n '360,760p' scripts/health/health_dashboard_action_worker.py


sed -n '320,620p' scripts/health/health_dashboard_server.py


rg -n "nutrition_mapping_action_log|nutrition_mapping_provenance|personal_food_tolerance|nutrition_composite_product_review|personal_tolerance" database/schema.sql scripts tests docs/adr/0003-dietary-inflammation-model.md -S


sed -n '1,260p' docs/adr/0003-dietary-inflammation-model.md


Output:
Chunk ID: 6cd31c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3142
Output:
            UNIQUE(action_id, affected_day)
        );
        CREATE TABLE IF NOT EXISTS nutrition_composite_product_review (
            normalized_name TEXT PRIMARY KEY,
            canonical_food TEXT,
            status TEXT NOT NULL DEFAULT 'needs_ingredient_review',
            updated_at TEXT DEFAULT CURRENT_TIMESTAMP
        );
        """
    )
    for table, columns in {
        "nutrition_mapping_action_log": {
            "decision": "TEXT", "alias": "TEXT", "canonical_food": "TEXT", "sighi_score": "INTEGER",
            "confidence": "TEXT", "mapping_method": "TEXT", "source_label": "TEXT", "source_version": "TEXT",
            "note": "TEXT", "ingredient_review_required": "INTEGER", "personal_tolerance": "TEXT",
            "definition_version": "TEXT",
        },
        "nutrition_mapping_provenance": {
            "decision": "TEXT", "alias": "TEXT", "confidence": "TEXT", "mapping_method": "TEXT",
            "source_label": "TEXT", "source_version": "TEXT", "note": "TEXT",
            "ingredient_review_required": "INTEGER", "personal_tolerance": "TEXT",
            "definition_version": "TEXT",
        },
    }.items():
        existing = {row["name"] for row in connection.execute(f"PRAGMA table_info({table})")}
        for name, declaration in columns.items():
            if name not in existing:
                connection.execute(f"ALTER TABLE {table} ADD COLUMN {name} {declaration}")

def recompute_nutrition_day(connection: sqlite3.Connection, day: str) -> None:
    rows = list(
        connection.execute(
            """SELECT i.id,i.meal,i.kcal,i.protein_g,i.carb_g,i.fat_g,h.sighi_score
               FROM nutrition_items i LEFT JOIN nutrition_histamine_scores h ON h.item_id=i.id
               WHERE i.datum=?""",
            (day,),
        )
    )
    connection.execute("DELETE FROM nutrition_meal_summary WHERE datum=?", (day,))
    if not rows:
        connection.execute("DELETE FROM nutrition_daily_summary_v2 WHERE datum=?", (day,))
        return
    totals = {"kcal": 0.0, "protein": 0.0, "carb": 0.0, "fat": 0.0, "items": 0, "unknown": 0, "load": 0.0, "scores": [], "missing": set(), "ids": []}
    by_meal: dict[str, dict[str, Any]] = {}
    for row in rows:
        meal = row["meal"] if row["meal"] in {"breakfast", "lunch", "dinner", "snack"} else "unassigned"
        bucket = by_meal.setdefault(meal, {"kcal": 0.0, "protein": 0.0, "carb": 0.0, "fat": 0.0, "items": 0, "unknown": 0, "load": 0.0, "scores": [], "missing": set(), "ids": []})
        for target in (bucket, totals):
            target["items"] += 1
            target["ids"].append(int(row["id"]))
            for source, dest in (("kcal", "kcal"), ("protein_g", "protein"), ("carb_g", "carb"), ("fat_g", "fat")):
                if row[source] is None:
                    target["missing"].add(dest)
                else:
                    target[dest] += float(row[source])
            if row["sighi_score"] is None:
                target["unknown"] += 1
            else:
                score = int(row["sighi_score"])
                target["scores"].append(score)
                target["load"] += score
    for meal, bucket in by_meal.items():
        max_score = max(bucket["scores"] or [0])
        label = _score_label(max_score, int(bucket["unknown"]), float(bucket["load"]))
        values = {key: None if key in bucket["missing"] else round(float(bucket[key]), 1) for key in ("kcal", "protein", "carb", "fat")}
        connection.execute(
            """INSERT OR REPLACE INTO nutrition_meal_summary
               (datum,meal,kcal,protein_g,carb_g,fat_g,item_count,nutrient_json,histamine_score,histamine_label,updated_at)
               VALUES(?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)""",
            (day, meal, values["kcal"], values["protein"], values["carb"], values["fat"], int(bucket["items"]), _nutrient_totals(connection, bucket["ids"]), round(bucket["load"], 1) if not bucket["unknown"] else None, label),
        )
    max_score = max(totals["scores"] or [0])
    label = _score_label(max_score, int(totals["unknown"]), float(totals["load"]))
    connection.execute(
        """INSERT OR REPLACE INTO nutrition_daily_summary_v2
           (datum,kcal,protein_g,carb_g,fat_g,item_count,nutrient_json,histamine_score,histamine_max,histamine_unknown_count,histamine_label,updated_at)
           VALUES(?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)""",
        (
            day,
            None if "kcal" in totals["missing"] else round(float(totals["kcal"]), 1),
            None if "protein" in totals["missing"] else round(float(totals["protein"]), 1),
            None if "carb" in totals["missing"] else round(float(totals["carb"]), 1),
            None if "fat" in totals["missing"] else round(float(totals["fat"]), 1),
            int(totals["items"]),
            _nutrient_totals(connection, totals["ids"]),
            round(totals["load"], 1) if not totals["unknown"] else None,
            max_score,
            int(totals["unknown"]),
            label,
        ),
    )

def apply_mapping_action(payload: dict[str, Any], action_id: str) -> None:
    if DASHBOARD_DB is None:
        raise RuntimeError("HEALTH_DASHBOARD_DB is required")
    action_hash = hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest()
    connection = sqlite3.connect(DASHBOARD_DB)
    connection.row_factory = sqlite3.Row
    try:
        with connection:
            ensure_nutrition_mapping_schema(connection)
            existing = connection.execute("SELECT action_hash FROM nutrition_mapping_action_log WHERE action_id=?", (action_id,)).fetchone()
            if existing is not None:
                if existing["action_hash"] != action_hash:
                    raise RuntimeError("conflicting idempotency key")
                return
            queue = connection.execute("SELECT normalized_name,example_name FROM nutrition_review_queue WHERE status='open'").fetchall()
            matched = [row for row in queue if _slug(str(row["normalized_name"])) == payload["queue_key"]]
            if len(matched) != 1:
                raise RuntimeError("mapping queue item not found")
            normalized = str(matched[0]["normalized_name"])
            canonical = payload["canonical_food"]
            score = None if payload["sighi_score"] == "unknown" else payload["sighi_score"]
            item_rows = [
                row for row in connection.execute("SELECT id,datum,name,source_product_id FROM nutrition_items")
                if _slug(str(row["name"] or row["source_product_id"] or "")) == payload["queue_key"]
                or _slug(str(row["source_product_id"] or row["name"] or "")) == payload["queue_key"]
            ]
            affected = sorted({row["datum"] for row in item_rows if row["datum"]})
            if len(affected) > 366:
                raise RuntimeError("affected day limit exceeded")
            if payload["decision"] == "composite":
                connection.execute(
                    """INSERT OR REPLACE INTO nutrition_composite_product_review
                       (normalized_name,canonical_food,status,updated_at)
                       VALUES(?,?, 'needs_ingredient_review', CURRENT_TIMESTAMP)""",
                    (normalized, canonical),
                )
            elif payload["decision"] == "assign":
                for row in item_rows:
                    connection.execute(
                        """INSERT OR REPLACE INTO nutrition_histamine_scores
                           (item_id,canonical_food,sighi_score,traffic_light,tags,confidence,reason,scored_at)
                           VALUES(?,?,?,?,?,?,?,CURRENT_TIMESTAMP)""",
                        (row["id"], canonical, score, "classified", payload["mapping_method"], payload["confidence"], payload["note"] or "local validated mapping action"),
                    )
                connection.execute("UPDATE nutrition_review_queue SET status='mapped',updated_at=CURRENT_TIMESTAMP WHERE normalized_name=?", (normalized,))
            else:
                connection.execute("UPDATE nutrition_review_queue SET status='ignored',updated_at=CURRENT_TIMESTAMP WHERE normalized_name=?", (normalized,))
            for day in affected:
                if payload["decision"] == "assign":
                    recompute_nutrition_day(connection, str(day))
                connection.execute(
                    """INSERT OR IGNORE INTO nutrition_mapping_provenance
                       (action_id,normalized_name,decision,alias,canonical_food,sighi_score,confidence,
                        mapping_method,source_label,source_version,note,ingredient_review_required,
                        personal_tolerance,definition_version,affected_day)
                       VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
                    (
                        action_id, normalized, payload["decision"], payload["alias"], canonical or None,
                        score, payload["confidence"], payload["mapping_method"], payload["source_label"],
                        payload["source_version"], payload["note"], int(payload["ingredient_review_required"]),
                        payload.get("personal_tolerance"), HISTAMINE_DEFINITION_VERSION, str(day),
                    ),
                )
            connection.execute(
                """INSERT INTO nutrition_mapping_action_log
                   (action_id,action_hash,queue_key,decision,alias,canonical_food,sighi_score,confidence,
                    mapping_method,source_label,source_version,note,ingredient_review_required,
                    personal_tolerance,definition_version)
                   VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
                (
                    action_id, action_hash, payload["queue_key"], payload["decision"], payload["alias"],
                    canonical or None, score, payload["confidence"], payload["mapping_method"],
                    payload["source_label"], payload["source_version"], payload["note"],
                    int(payload["ingredient_review_required"]), payload.get("personal_tolerance"),
                    HISTAMINE_DEFINITION_VERSION,
                ),
            )
    finally:
        connection.close()

def apply_action(payload: dict[str, Any] | str, action_id: str | dict[str, int], notes: str | None = None) -> None:
    if isinstance(payload, str):
        if not isinstance(action_id, dict) or notes is None:
            raise ValueError("invalid legacy symptom action")
        normalized_payload: dict[str, Any] = {
            "action": "symptom_checkin",
            "date": payload,
            "scores": action_id,
            "notes": notes,
        }
        normalized_action_id = "legacy-symptom-action"
    else:
        normalized_payload = payload
        if not isinstance(action_id, str):
            raise ValueError("invalid action id")
        normalized_action_id = action_id
    if normalized_payload["action"] == "symptom_checkin":
        apply_symptom_action(normalized_payload["date"], normalized_payload["scores"], normalized_payload["notes"])
    elif normalized_payload["action"] == "nutrition_mapping":
        apply_mapping_action(normalized_payload, normalized_action_id)
    else:
        raise ValueError("unsupported action")
    if DASHBOARD_V5_FILE is not None:
        v5_command = [
            sys.executable,
            str(DASHBOARD_V5),
            "--db",
            str(DASHBOARD_DB),
            "--output",
            str(DASHBOARD_V5_FILE),
            "--today",
            local_today().isoformat(),
        ]
        if DASHBOARD_V5_PROFILE == V5_PROFILE_HEALTH_RECORD_6E:
            v5_command.extend(["--health-record-6e", "--explorer-6c", "--calendar-day-6d"])
        subprocess.run(
            v5_command,
            check=True,
            timeout=120,
            capture_output=True,
            text=True,
        )

def main() -> int:
    if DASHBOARD_DB is None:
        print("HEALTH_DASHBOARD_DB is required", file=sys.stderr)
        return 2
    ensure_private_inbox()
    failures = 0
    for path in sorted(ACTION_INBOX.glob("*.json")):
        try:
            payload = load_action(path)
            apply_action(payload, path.stem)
            path.unlink()
        except Exception:
            failures += 1
            try:
                path.replace(path.with_suffix(".failed"))
            except OSError:
                pass
    return 1 if failures else 0

if __name__ == "__main__":
    raise SystemExit(main())


Output:
Chunk ID: 680f93
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2859
Output:
    probe = probe_original(
        row["local_original_path"] or row["dateipfad"],
        reviewed=True,
        roots=roots,
        max_bytes=MAX_ORIGINAL_BYTES,
        opener=_open_regular_beneath,
    )
    if probe.status != "available" or probe.descriptor is None:
        probe.close()
        return None
    return probe.descriptor, probe.size, probe.mime, probe.download_name

def safe_report_path(name: str) -> Path | None:
    clean = Path(unquote(name)).name
    path = (REPORTS / clean).resolve()
    try:
        if path.is_file() and path.is_relative_to(REPORTS.resolve()):
            return path
    except OSError:
        return None
    return None

def canonical_request_host(raw_host: str) -> str | None:
    if not raw_host or any(char in raw_host for char in ("/", "@", "\\")):
        return None
    try:
        parsed = urlparse("//" + raw_host)
        host = parsed.hostname
        _ = parsed.port
    except ValueError:
        return None
    return host.lower().rstrip(".") if host else None

def host_is_allowed(raw_host: str) -> bool:
    host = canonical_request_host(raw_host)
    return host is not None and host in ALLOWED_HOSTS

def origin_matches_request(origin: str, raw_host: str) -> bool:
    try:
        request_authority = urlparse("//" + raw_host)
        supplied = urlparse(origin)
        request_port = request_authority.port
        supplied_port = supplied.port
    except ValueError:
        return False
    if (
        supplied.scheme != "http"
        or not supplied.hostname
        or supplied.username is not None
        or supplied.password is not None
        or supplied.path
        or supplied.params
        or supplied.query
        or supplied.fragment
    ):
        return False
    request_host = request_authority.hostname
    return (
        request_host is not None
        and supplied.hostname.lower().rstrip(".") == request_host.lower().rstrip(".")
        and supplied_port == request_port
    )

def issue_csrf_token(now: float | None = None) -> str:
    issued_at = time.monotonic() if now is None else now
    token = secrets.token_urlsafe(24)
    with _csrf_lock:
        expired = [
            existing
            for existing, timestamp in _csrf_tokens.items()
            if issued_at - timestamp > CSRF_TTL_SECONDS
        ]
        for existing in expired:
            _csrf_tokens.pop(existing, None)
        _csrf_tokens[token] = issued_at
        while len(_csrf_tokens) > 256:
            oldest = min(_csrf_tokens, key=_csrf_tokens.get)  # type: ignore[arg-type]
            _csrf_tokens.pop(oldest, None)
    return token

def consume_csrf_token(token: str, now: float | None = None) -> bool:
    checked_at = time.monotonic() if now is None else now
    with _csrf_lock:
        issued_at = _csrf_tokens.pop(token, None)
    return issued_at is not None and 0 <= checked_at - issued_at <= CSRF_TTL_SECONDS

def issue_queue_receipt(now: float | None = None) -> str:
    issued_at = time.monotonic() if now is None else now
    token = secrets.token_urlsafe(18)
    with _queue_receipt_lock:
        _queue_receipts[token] = issued_at
        expired = [
            key
            for key, timestamp in _queue_receipts.items()
            if issued_at - timestamp > QUEUE_RECEIPT_TTL_SECONDS
        ]
        for key in expired:
            _queue_receipts.pop(key, None)
        while len(_queue_receipts) > 256:
            oldest = min(_queue_receipts, key=_queue_receipts.get)  # type: ignore[arg-type]
            _queue_receipts.pop(oldest, None)
    return token

def consume_queue_receipt(token: str, now: float | None = None) -> bool:
    checked_at = time.monotonic() if now is None else now
    with _queue_receipt_lock:
        issued_at = _queue_receipts.pop(token, None)
    return (
        issued_at is not None
        and 0 <= checked_at - issued_at <= QUEUE_RECEIPT_TTL_SECONDS
    )

def dashboard_html_with_nonce(
    path: Path,
    *,
    capture_queued: bool = False,
) -> tuple[bytes, str, str, str]:
    nonce = secrets.token_urlsafe(18)
    csrf_token = issue_csrf_token()
    browser_session_csrf_token = issue_csrf_token()
    text = path.read_text(encoding="utf-8")
    capture_day = local_today()
    text = text.replace("__CSRF_TOKEN__", csrf_token)
    text = text.replace("__BROWSER_SESSION_CSRF__", browser_session_csrf_token)
    text = text.replace("__CAPTURE_QUEUED__", "true" if capture_queued else "false")
    text = text.replace("__CAPTURE_DATE__", capture_day.isoformat())
    text = text.replace("__CAPTURE_DATE_LABEL__", capture_day.strftime("%d.%m.%Y"))
    text = text.replace("<script", f"<script nonce='{nonce}'")
    text = text.replace("<style", f"<style nonce='{nonce}'")
    return text.encode("utf-8"), nonce, csrf_token, browser_session_csrf_token

def local_today() -> date:
    return datetime.now(CAPTURE_TIMEZONE).date()

def validate_symptom_form(
    form: dict[str, list[str]],
) -> tuple[str, dict[str, int], str]:
    day = (form.get("date") or [""])[0]
    try:
        parsed_day = date.fromisoformat(day)
    except ValueError as exc:
        raise ValueError("invalid date") from exc
    if parsed_day.year < 2000 or parsed_day > local_today():
        raise ValueError("date outside allowed range")
    scores: dict[str, int] = {}
    for field in SYMPTOM_FIELDS:
        raw = (form.get(field) or [""])[0]
        if raw not in {"0", "1", "2", "3"}:
            raise ValueError(f"invalid or missing score: {field}")
        scores[field] = int(raw)
    notes = (form.get("notes") or [""])[0].strip()
    if len(notes) > 300:
        raise ValueError("notes too long")
    return parsed_day.isoformat(), scores, notes

def validate_symptom_submission(
    form: dict[str, list[str]],
) -> tuple[str, dict[str, int], str, str]:
    required = {"csrf_token", "date", "notes", *SYMPTOM_FIELDS}
    allowed = required | {"return_to"}
    keys = frozenset(form)
    if keys not in {frozenset(required), frozenset(allowed)}:
        raise ValueError("invalid submission shape")
    if any(len(values) != 1 for values in form.values()):
        raise ValueError("every field must have a single value")
    return_to = form.get("return_to", ["v4"])[0]
    if return_to not in {"v4", "v5"}:
        raise ValueError("invalid return target")
    day, scores, notes = validate_symptom_form(form)
    return day, scores, notes, return_to

def validate_mapping_submission(form: dict[str, list[str]]) -> tuple[dict[str, object], str]:
    required = {
        "csrf_token",
        "decision",
        "queue_key",
        "alias",
        "canonical_food",
        "sighi_score",
        "confidence",
        "mapping_method",
        "source_label",
        "source_version",
        "note",
        "ingredient_review_required",
        "return_to",
    }
    allowed = required | {"personal_tolerance"}
    if not required <= set(form) <= allowed or any(len(values) != 1 for values in form.values()):
        raise ValueError("invalid mapping shape")
    return_to = form["return_to"][0]
    if return_to != "v5":
        raise ValueError("invalid return target")
    decision = form["decision"][0]
    queue_key = form["queue_key"][0].strip()
    alias = " ".join(form["alias"][0].split())
    canonical_food = " ".join(form["canonical_food"][0].split())
    score = form["sighi_score"][0]
    confidence = form["confidence"][0]
    method = form["mapping_method"][0]
    source_label = " ".join(form["source_label"][0].split())
    source_version = " ".join(form["source_version"][0].split())
    note = " ".join(form["note"][0].split())
    review = form["ingredient_review_required"][0]
    tolerance = " ".join(form.get("personal_tolerance", [""])[0].split())
    if not re.fullmatch(r"[a-z0-9_]{1,80}", queue_key):
        raise ValueError("invalid queue key")
    for value, maximum in ((alias, 160), (canonical_food, 120), (source_label, 80), (source_version, 40), (note, 300), (tolerance, 160)):
        if len(value) > maximum or re.search(r"[\x00-\x1f\x7f]|https?://|/|\\\\|\.hermes", value, re.I):
            raise ValueError("invalid mapping text")
    if not alias or not source_label or not source_version:
        raise ValueError("mapping documentation required")
    if decision not in MAPPING_DECISIONS or score not in MAPPING_SCORES or confidence not in MAPPING_CONFIDENCE or method not in MAPPING_METHODS or review not in {"true", "false"}:
        raise ValueError("invalid mapping value")
    ingredient_review_required = review == "true"
    if decision == "assign" and (score == "unknown" or not canonical_food or ingredient_review_required):
        raise ValueError("assign requires canonical score")
    if decision == "composite" and (score != "unknown" or not ingredient_review_required):
        raise ValueError("composite remains unclassified")
    if decision == "ignore" and (score != "unknown" or canonical_food or ingredient_review_required):
        raise ValueError("ignore must not classify")
    return {
        "version": 1,
        "action": "nutrition_mapping",
        "decision": decision,
        "queue_key": queue_key,
        "alias": alias,
        "canonical_food": canonical_food,
        "sighi_score": "unknown" if score == "unknown" else int(score),
        "confidence": confidence,
        "mapping_method": method,
        "source_label": source_label,
        "source_version": source_version,
        "note": note,
        "ingredient_review_required": ingredient_review_required,
        **({"personal_tolerance": tolerance} if tolerance else {}),
    }, return_to

def ensure_private_inbox() -> None:
    ACTION_INBOX.mkdir(mode=0o700, parents=True, exist_ok=True)
    metadata = ACTION_INBOX.lstat()
    if not stat.S_ISDIR(metadata.st_mode) or metadata.st_uid != os.getuid():
        raise OSError("action inbox is not a private owned directory")
    os.chmod(ACTION_INBOX, 0o700)

def write_action_payload(payload: dict[str, object]) -> None:
    """Atomically enqueue a validated action; the network server never writes the DB."""
    with _action_lock:
        ensure_private_inbox()
        if sum(1 for _ in ACTION_INBOX.glob("*.json")) >= MAX_PENDING_ACTIONS:
            raise QueueFullError("private action queue is full")
        token = secrets.token_hex(16)
        temporary = ACTION_INBOX / f".{token}.tmp"
        destination = ACTION_INBOX / f"{token}.json"
        packed = json.dumps(
            payload,
            ensure_ascii=False,
            separators=(",", ":"),
        ).encode("utf-8")
        descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
        try:
            with os.fdopen(descriptor, "wb") as handle:
                handle.write(packed)
                handle.flush()
                os.fsync(handle.fileno())
            temporary.replace(destination)
            directory = os.open(ACTION_INBOX, os.O_RDONLY | os.O_DIRECTORY)
            try:
                os.fsync(directory)
            finally:
                os.close(directory)
        except Exception:
            temporary.unlink(missing_ok=True)
            raise

def write_symptom_checkin(day: str, scores: dict[str, int], notes: str) -> None:
    write_action_payload(
        {
            "version": 1,
            "action": "symptom_checkin",
            "date": day,
            "scores": scores,
            "notes": notes,
        }
    )


Output:
Chunk ID: c6f27c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1405
Output:
database/schema.sql:424:CREATE TABLE personal_food_tolerance (
database/schema.sql:432:CREATE TABLE nutrition_mapping_action_log (
database/schema.sql:446:    personal_tolerance TEXT,
database/schema.sql:451:CREATE TABLE nutrition_mapping_provenance (
database/schema.sql:467:    personal_tolerance TEXT,
database/schema.sql:474:CREATE TABLE nutrition_composite_product_review (
scripts/health/health_dashboard_action_worker.py:127:    optional = {"personal_tolerance"}
scripts/health/health_dashboard_action_worker.py:143:    tolerance = payload.get("personal_tolerance")
scripts/health/health_dashboard_action_worker.py:188:        "personal_tolerance": tolerance,
scripts/health/health_dashboard_action_worker.py:324:        CREATE TABLE IF NOT EXISTS nutrition_mapping_action_log (
scripts/health/health_dashboard_action_worker.py:338:            personal_tolerance TEXT,
scripts/health/health_dashboard_action_worker.py:342:        CREATE TABLE IF NOT EXISTS nutrition_mapping_provenance (
scripts/health/health_dashboard_action_worker.py:356:            personal_tolerance TEXT,
scripts/health/health_dashboard_action_worker.py:362:        CREATE TABLE IF NOT EXISTS nutrition_composite_product_review (
scripts/health/health_dashboard_action_worker.py:371:        "nutrition_mapping_action_log": {
scripts/health/health_dashboard_action_worker.py:374:            "note": "TEXT", "ingredient_review_required": "INTEGER", "personal_tolerance": "TEXT",
scripts/health/health_dashboard_action_worker.py:377:        "nutrition_mapping_provenance": {
scripts/health/health_dashboard_action_worker.py:380:            "ingredient_review_required": "INTEGER", "personal_tolerance": "TEXT",
scripts/health/health_dashboard_action_worker.py:463:            existing = connection.execute("SELECT action_hash FROM nutrition_mapping_action_log WHERE action_id=?", (action_id,)).fetchone()
scripts/health/health_dashboard_action_worker.py:485:                    """INSERT OR REPLACE INTO nutrition_composite_product_review
scripts/health/health_dashboard_action_worker.py:505:                    """INSERT OR IGNORE INTO nutrition_mapping_provenance
scripts/health/health_dashboard_action_worker.py:508:                        personal_tolerance,definition_version,affected_day)
scripts/health/health_dashboard_action_worker.py:514:                        payload.get("personal_tolerance"), HISTAMINE_DEFINITION_VERSION, str(day),
scripts/health/health_dashboard_action_worker.py:518:                """INSERT INTO nutrition_mapping_action_log
scripts/health/health_dashboard_action_worker.py:521:                    personal_tolerance,definition_version)
scripts/health/health_dashboard_action_worker.py:527:                    int(payload["ingredient_review_required"]), payload.get("personal_tolerance"),
scripts/health/health_dashboard_v4.py:394:        personal_tolerances = list(c.execute("SELECT canonical_food,personal_status,evidence_level,notes,updated_at FROM personal_food_tolerance ORDER BY CASE personal_status WHEN 'problematic' THEN 1 WHEN 'unclear' THEN 2 WHEN 'safe' THEN 3 ELSE 4 END, canonical_food"))
scripts/health/health_dashboard_v4.py:658:        for r in personal_tolerances
scripts/health/health_dashboard_server.py:520:    allowed = required | {"personal_tolerance"}
scripts/health/health_dashboard_server.py:537:    tolerance = " ".join(form.get("personal_tolerance", [""])[0].split())
scripts/health/health_dashboard_server.py:568:        **({"personal_tolerance": tolerance} if tolerance else {}),
tests/test_dashboard_v5_sprint6g_nutrition.py:120:        "personal_tolerance": "documented local note",
tests/test_dashboard_v5_sprint6g_nutrition.py:137:        assert connection.execute("SELECT COUNT(*) FROM nutrition_mapping_action_log").fetchone()[0] == 1
tests/test_dashboard_v5_sprint6g_nutrition.py:138:        assert connection.execute("SELECT COUNT(*) FROM nutrition_mapping_provenance WHERE affected_day='2026-06-14'").fetchone()[0] == 1
tests/test_dashboard_v5_sprint6g_nutrition.py:141:        provenance = connection.execute("SELECT decision,alias,confidence,mapping_method,source_label,source_version,note,ingredient_review_required,personal_tolerance,definition_version FROM nutrition_mapping_provenance").fetchone()
tests/test_dashboard_v5_sprint6g_nutrition.py:151:            "personal_tolerance": "documented local note",
tests/test_dashboard_v5_sprint6g_nutrition.py:154:        assert connection.execute("SELECT COUNT(*) FROM personal_food_tolerance").fetchone()[0] == 0
tests/test_dashboard_v5_sprint6g_nutrition.py:174:        columns = {row["name"] for row in connection.execute("PRAGMA table_info(nutrition_mapping_provenance)")}
tests/test_dashboard_v5_sprint6g_nutrition.py:208:        assert connection.execute("SELECT status FROM nutrition_composite_product_review").fetchone()[0] == "needs_ingredient_review"
tests/test_dashboard_v5_sprint6g_nutrition.py:209:        assert connection.execute("SELECT COUNT(*) FROM nutrition_mapping_action_log").fetchone()[0] == 2
tests/test_dashboard_v5_sprint6g_nutrition.py:234:        "personal_tolerance": ["neutral note"],
tests/test_dashboard_v5_sprint6g_nutrition.py:239:    assert payload["personal_tolerance"] == "neutral note"
scripts/health/health_dashboard_v3.py:293:        personal_tolerances = list(c.execute("SELECT canonical_food,personal_status,evidence_level,notes,updated_at FROM personal_food_tolerance ORDER BY CASE personal_status WHEN 'problematic' THEN 1 WHEN 'unclear' THEN 2 WHEN 'safe' THEN 3 ELSE 4 END, canonical_food"))
scripts/health/health_dashboard_v3.py:544:        for r in personal_tolerances


Output:
Chunk ID: e366be
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 854
Output:
# ADR 0003: Dietary Inflammation Model

Date: 2026-07-16

## Status

Accepted for Dashboard v5 Sprint 6G-A.

## Context

Nutrition data combines imported meal records, nutrient totals, SIGHi-style histamine mappings, symptom dimensions, and optional context such as medication phases or documented events. These inputs are incomplete on many days and may contain unknown food mappings.

Technical inventory from the Sprint 6G-A fixture/audit, abstracted without product names:

- nutrition item records: present, with meal, date, item label, imported macros, and source-local product identifiers;
- item nutrient rows: present for selected macro and nutrient keys, with mixed unit coverage;
- histamine mapping rows: present for mapped and unmapped item states;
- review queue rows: present for unmapped normalized product slugs;
- daily and meal summaries: present, but historically allowed zero defaults for missing macro/index fields;
- DII reference-data tables: absent;
- DII food-parameter normalization/version tables: absent;
- reference licensing evidence for the full DII parameter database: absent.

Input coverage/unit findings:

- known and displayable nutrient units are limited to kcal, g, and mg for allowlisted keys;
- unknown-unit micronutrients are omitted from read APIs;
- missing imported macros are unknown and must not be normalized to zero;
- currently displayable inflammation-adjacent components are fiber, sugar, saturated fat, and sodium where known;
- required DII parameters are not covered with a complete, licensed, versioned reference table.

The Dietary Inflammatory Index methodology required for a real DII implementation is the peer-reviewed Shivappa et al. 2014 method: nutrient/food-parameter intake is standardized against a global comparative database, converted to centered percentiles, multiplied by literature-derived inflammatory effect scores, and summed across available parameters. That methodology requires stable parameter names, units, global means/standard deviations, effect scores, coverage thresholds, and versioning. Those inputs are not available in this project state.

## Decision

Dashboard v5 exposes transparent components only:

- documented macros and allowlisted nutrients,
- versioned histamine mapping coverage,
- unknown item counts,
- explicitly complete or incomplete day semantics,
- side-by-side ECharts views for nutrition and Aphthae when both are documented.

The dashboard does not compute a dietary inflammation index when required inputs are insufficient. Unknown or unmapped foods make the histamine component incomplete rather than zero. Composite products require ingredient review before a definitive mapping is applied.

Personal tolerance labels remain separate from shared food mapping definitions.

Missing-parameter handling:

- unobserved DII parameters are not imputed as neutral;
- known values are shown only as individual documented components;
- any future DII calculation must store the methodology id, source version, parameter coverage, unit normalization map, and unavailable-parameter policy with each computed value.

Conclusion: Entzündungsindex derzeit nicht belastbar berechenbar.

## Consequences

The UI may show fewer derived values, but every displayed component has visible coverage semantics. Charts use neutral hypothesis wording and make no diagnosis, treatment, warning, reassurance, or causal claim.


I also need the exact UI form fields, so I’m locating the mapping controls before editing the server/worker. The database schema already has the personal table, but its status values are the older vocabulary and the mapping audit columns need the new split fields.


rg -n "personal_tolerance|nutrition-mapping|mapping-form|canonical_food|sighi_score|source_version|ingredient_review" scripts/health/assets/health-assets scripts/health/dashboard_v5 -S


sed -n '230,310p' tests/fixtures/dashboard_v5_fixture.py


rg -n "CREATE TABLE|ALTER TABLE|schema.sql|executescript|backup|integrity_check" scripts/health/health_system_migrate.py tests/test_health_system_migrate_sprint2.py tests/test_dashboard_v5_sprint6g_nutrition.py -S


Output:
Chunk ID: e0437b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 678
Output:
    example_name TEXT NOT NULL,
    occurrence_count INTEGER DEFAULT 0,
    first_seen TEXT,
    last_seen TEXT,
    suggested_canonical_food TEXT,
    suggested_score INTEGER,
    reason TEXT,
    status TEXT DEFAULT 'open' CHECK(status IN ('open','mapped','ignored')),
    updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE nutrition_treatment_phase_summary (
        phase TEXT PRIMARY KEY,
        start_date TEXT,
        end_date TEXT,
        nutrition_days INTEGER DEFAULT 0,
        avg_histamine_score REAL,
        avg_symptom_score REAL,
        avg_kcal REAL,
        avg_protein_g REAL,
        notes TEXT,
        computed_at TEXT DEFAULT CURRENT_TIMESTAMP
    );

CREATE TABLE personal_food_tolerance (
    canonical_food TEXT PRIMARY KEY,
    personal_status TEXT NOT NULL DEFAULT 'unknown',
    evidence_level TEXT DEFAULT 'low' CHECK(evidence_level IN ('low','medium','high')),
    notes TEXT,
    updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE nutrition_mapping_action_log (
    action_id TEXT PRIMARY KEY,
    action_hash TEXT NOT NULL,
    queue_key TEXT NOT NULL,
    decision TEXT,
    alias TEXT,
    canonical_food TEXT,
    sighi_score INTEGER,
    confidence TEXT,
    mapping_method TEXT,
    source_label TEXT,
    source_version TEXT,
    note TEXT,
    ingredient_review_required INTEGER,
    personal_tolerance TEXT,
    definition_version TEXT,
    applied_at TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE nutrition_mapping_provenance (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    action_id TEXT NOT NULL,
    normalized_name TEXT NOT NULL,
    decision TEXT,
    alias TEXT,
    canonical_food TEXT,
    sighi_score INTEGER,
    traffic_light TEXT,
    mapping_scope TEXT,
    confidence TEXT,
    mapping_method TEXT,
    source_label TEXT,
    source_version TEXT,
    note TEXT,
    ingredient_review_required INTEGER,
    personal_tolerance TEXT,
    definition_version TEXT,
    affected_day TEXT NOT NULL,
    applied_at TEXT DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(action_id, affected_day)
);

CREATE TABLE nutrition_composite_product_review (
    normalized_name TEXT PRIMARY KEY,
    canonical_food TEXT,
    status TEXT NOT NULL DEFAULT 'needs_ingredient_review' CHECK(status IN ('needs_ingredient_review','reviewed')),
    updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE multimodal_correlation_results (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    predictor TEXT NOT NULL,
    target TEXT NOT NULL,
    lag_days INTEGER NOT NULL,
    medication_phase TEXT NOT NULL,
    n INTEGER NOT NULL,
    eligible_target_days INTEGER NOT NULL,
    expected_target_days INTEGER NOT NULL DEFAULT 0,
    target_coverage REAL NOT NULL DEFAULT 0,


Output:
Chunk ID: 58201c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 645
Output:
scripts/health/dashboard_v5/data_provider.py:260:        for row in connection.execute("SELECT normalized_name,example_name,occurrence_count,suggested_canonical_food,suggested_score FROM nutrition_review_queue WHERE status='open' ORDER BY occurrence_count DESC,last_seen DESC LIMIT 25"):
scripts/health/dashboard_v5/data_provider.py:264:                suggested = public_text(row["suggested_canonical_food"], 120, allow_empty=True)
scripts/health/dashboard_v5/data_provider.py:271:                    "suggested_canonical_food": suggested or "",
scripts/health/dashboard_v5/contracts.py:255:            "name", "occurrences", "reason", "queue_key", "suggested_canonical_food",
scripts/health/dashboard_v5/contracts.py:268:        if "suggested_canonical_food" in row:
scripts/health/dashboard_v5/contracts.py:269:            _bounded_text(row["suggested_canonical_food"], f"{path}.suggested_canonical_food", 120, allow_empty=True)
scripts/health/dashboard_v5/read_api.py:1396:                      h.canonical_food,h.sighi_score,h.traffic_light,h.confidence
scripts/health/dashboard_v5/read_api.py:1405:            canonical = safe_metadata_text(item["canonical_food"], 120, allow_empty=True)
scripts/health/dashboard_v5/read_api.py:1421:                    "canonical_food": canonical or None,
scripts/health/dashboard_v5/read_api.py:1422:                    "score": int(item["sighi_score"]) if item["sighi_score"] in (0, 1, 2, 3) else None,
scripts/health/dashboard_v5/read_api.py:1423:                    "status": "mapped" if item["sighi_score"] in (0, 1, 2, 3) else "unmapped",
scripts/health/dashboard_v5/read_api.py:1467:                  suggested_canonical_food,suggested_score,reason,status
scripts/health/dashboard_v5/read_api.py:1475:        suggested = safe_metadata_text(row["suggested_canonical_food"], 120, allow_empty=True)
scripts/health/dashboard_v5/read_api.py:1485:            "suggested_canonical_food": suggested,
scripts/health/assets/health-assets/dashboard-v5.js:431:        form.action = "/health-actions/nutrition-mapping";
scripts/health/assets/health-assets/dashboard-v5.js:438:          ["source_version", "unknown"],
scripts/health/assets/health-assets/dashboard-v5.js:454:        canonical.name = "canonical_food";
scripts/health/assets/health-assets/dashboard-v5.js:456:        canonical.value = item.suggested_canonical_food || "";
scripts/health/assets/health-assets/dashboard-v5.js:458:        score.name = "sighi_score";
scripts/health/assets/health-assets/dashboard-v5.js:486:        reviewRequired.name = "ingredient_review_required";


Output:
Chunk ID: 198ca9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1149
Output:
                (1, "fixture", "complete", _iso_day(current), "lunch", "SYNTHETIC_COMPLETE_FOOD_MAPPING", "Synthetic Brand", 100, "g", 0, 0, 0, 0, "fixture-food-complete", fixture_timestamp, fixture_timestamp),
                (2, "fixture", "incomplete", _iso_day(current), "dinner", "SYNTHETIC_INCOMPLETE_FOOD_MAPPING_WITH_A_VERY_LONG_NAME_THAT_MUST_WRAP", None, None, "g", None, None, None, None, "fixture-food-incomplete", fixture_timestamp, fixture_timestamp),
            ],
        )
        connection.executemany(
            """INSERT INTO nutrition_histamine_scores
               (item_id,canonical_food,sighi_score,traffic_light,tags,confidence,reason,scored_at)
               VALUES(?,?,?,?,?,?,?,?)""",
            [
                (1, "synthetic cucumber", 0, "classified", "fixture", "high", "complete synthetic mapping", fixture_timestamp),
                (2, None, None, "unknown", None, "low", "intentionally unmapped fixture row", fixture_timestamp),
            ],
        )
        connection.execute(
            """INSERT INTO nutrition_daily_summary_v2
               (datum,kcal,protein_g,carb_g,fat_g,item_count,histamine_score,
                histamine_max,histamine_unknown_count,histamine_label,nutrient_json,updated_at)
               VALUES(?,?,?,?,?,?,?,?,?,?,?,?)""",
            (_iso_day(current), None, None, None, None, 2, None, 0, 1, "unknown", json.dumps({"nutrient.protein": None, "nutrient.fiber": 4.2}), _iso_noon(current)),
        )
        connection.executemany(
            """INSERT INTO nutrition_item_nutrients(item_id,nutrient_key,value,unit,raw_value)
               VALUES(?,?,?,?,?)""",
            [
                (1, "nutrient.fiber", 4.2, "g", "4.2"),
                (1, "nutrient.sugar", 2.0, "g", "2.0"),
                (1, "nutrient.vitamin_c", 20.0, "unknown", "20"),
                (2, "nutrient.sodium", 10.0, "mg", "10"),
            ],
        )
        connection.execute(
            """INSERT INTO nutrition_meal_summary
               (datum,meal,kcal,protein_g,carb_g,fat_g,item_count,nutrient_json,histamine_score,histamine_label,updated_at)
               VALUES(?,?,?,?,?,?,?,?,?,?,?)""",
            (_iso_day(current), "lunch", 0, 0, 0, 0, 1, "{}", 0, "classified", _iso_noon(current)),
        )
        connection.execute(
            """INSERT INTO nutrition_review_queue
               (normalized_name,example_name,occurrence_count,first_seen,last_seen,suggested_canonical_food,suggested_score,reason,status,updated_at)
               VALUES(?,?,?,?,?,?,?,?,?,?)""",
            ("synthetic_incomplete_food_mapping_with_a_very_long_name_that_must_wrap", "SYNTHETIC_INCOMPLETE_FOOD_MAPPING_WITH_A_VERY_LONG_NAME_THAT_MUST_WRAP", 1, _iso_day(current), _iso_day(current), "synthetic reviewed food", 1, "synthetic missing mapping", "open", fixture_timestamp),
        )
        symptom_rows = []
        for day_index, day in enumerate(complete_symptom_days):
            for dimension_index, dimension in enumerate(SYMPTOM_DIMENSIONS):
                symptom_rows.append((_iso_day(day), dimension, str((day_index + dimension_index) % 4), "daily_quick_score", "synthetic complete day", fixture_timestamp))
        for dimension in SYMPTOM_DIMENSIONS[:-1]:
            symptom_rows.append((_iso_day(incomplete_symptom_day), dimension, "1", "daily_quick_score", "synthetic incomplete day", fixture_timestamp))
        connection.executemany(
            "INSERT INTO symptom_log(datum,symptom,schwergrad,kontext,notizen,created_at) VALUES(?,?,?,?,?,?)",
            symptom_rows,
        )
        connection.commit()
        fk_errors = connection.execute("PRAGMA foreign_key_check").fetchall()
        if fk_errors:
            raise RuntimeError(f"fixture violates foreign keys: {fk_errors}")
    except Exception:
        connection.close()
        target.unlink(missing_ok=True)
        raise
    finally:
        try:
            connection.close()
        except Exception:
            pass
    target.chmod(0o600)

    return {
        "anchor_date": _iso_day(anchor_date),
        "current_source_date": _iso_day(current),
        "stale_source_date": _iso_day(stale),
        "future_source_date": _iso_day(future),
        "complete_symptom_days": [_iso_day(day) for day in complete_symptom_days],
        "incomplete_symptom_day": _iso_day(incomplete_symptom_day),
        "no_data_day": _iso_day(no_data_day),
        "synthetic_only": True,
    }

def validate_fixture(db_path: str | Path) -> dict[str, object]:
    """Return structural acceptance checks without importing application code."""


Output:
Chunk ID: e3f2fa
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 573
Output:
tests/test_dashboard_v5_sprint6g_nutrition.py:176:        assert connection.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
tests/test_health_system_migrate_sprint2.py:19:    connection.executescript(
tests/test_health_system_migrate_sprint2.py:21:        CREATE TABLE laborwerte (id INTEGER PRIMARY KEY);
tests/test_health_system_migrate_sprint2.py:22:        CREATE TABLE dokumente (id INTEGER PRIMARY KEY);
tests/test_health_system_migrate_sprint2.py:23:        CREATE TABLE symptom_log (
tests/test_health_system_migrate_sprint2.py:33:    monkeypatch.setattr(module, "BACKUP_DIR", tmp_path / "backups")
tests/test_health_system_migrate_sprint2.py:55:        assert connection.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
tests/test_health_system_migrate_sprint2.py:63:    connection.executescript(
tests/test_health_system_migrate_sprint2.py:65:        CREATE TABLE multimodal_correlation_results (
tests/test_health_system_migrate_sprint2.py:113:        assert connection.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
scripts/health/health_system_migrate.py:17:BACKUP_DIR = BASE / "backups"
scripts/health/health_system_migrate.py:26:        conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {ddl}")
scripts/health/health_system_migrate.py:34:    backup = BACKUP_DIR / f"health_data_before_review_workflow_{stamp}.db"
scripts/health/health_system_migrate.py:35:    shutil.copy2(DB, backup)
scripts/health/health_system_migrate.py:53:    conn.executescript(
scripts/health/health_system_migrate.py:55:        CREATE TABLE IF NOT EXISTS health_processing_runs (
scripts/health/health_system_migrate.py:68:        CREATE TABLE IF NOT EXISTS laborwerte_staging (
scripts/health/health_system_migrate.py:95:        CREATE TABLE IF NOT EXISTS document_insights (
scripts/health/health_system_migrate.py:108:        CREATE TABLE IF NOT EXISTS health_daily_summary (
scripts/health/health_system_migrate.py:118:        CREATE TABLE IF NOT EXISTS health_insights (
scripts/health/health_system_migrate.py:131:        CREATE TABLE IF NOT EXISTS report_runs (
scripts/health/health_system_migrate.py:143:        CREATE TABLE IF NOT EXISTS multimodal_correlation_results (
scripts/health/health_system_migrate.py:190:    print(f"Migration OK. Backup: {backup}")


The migration restore test belongs naturally in the Sprint 6G focused test file using the synthetic fixture and full `schema.sql`, so it avoids touching production paths. I’m adding it there with raw table equality around a SQLite backup/restore cycle.


sed -n '420,510p' scripts/health/assets/health-assets/dashboard-v5.js


sed -n '1,240p' tests/test_health_system_migrate_sprint2.py


sed -n '1,230p' scripts/health/health_system_migrate.py


rg -n "mypy|ruff|pyright|eslint|static|python -m py_compile|pytest" README.md package.json pyproject.toml setup.cfg tox.ini . -g '!scripts/health/assets/health-assets/echarts-6.1.0.min.js' -S


Output:
Chunk ID: 3752ed
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1040
Output:
    const review = q("#mapping-review");
    if (review) {
      review.replaceChildren();
      const rows = (bundle.nutrition && bundle.nutrition.mapping_review) || [];
      if (!rows.length) review.append(node("p", "v5-empty", "Keine offenen Mapping-Prüfungen"));
      rows.forEach((item) => {
        const row = node("article", "v5-card v5-card--quiet");
        row.append(node("strong", "", item.name));
        row.append(node("div", "v5-meta", `${item.occurrences} Dokumentation(en) · Status: nicht zugeordnet`));
        const form = node("form", "mapping-action-form");
        form.method = "post";
        form.action = "/health-actions/nutrition-mapping";
        [
          ["csrf_token", q("#view-nutrition")?.dataset.mappingCsrf || ""],
          ["return_to", "v5"],
          ["queue_key", item.queue_key || ""],
          ["alias", item.example_name || item.name || ""],
          ["source_label", "SIGHi"],
          ["source_version", "unknown"],
        ].forEach(([name, value]) => {
          const input = node("input");
          input.type = "hidden";
          input.name = name;
          input.value = String(value);
          form.append(input);
        });
        const decision = node("select");
        decision.name = "decision";
        [["assign", "Zuordnen"], ["composite", "Zutatenprüfung"], ["ignore", "Ignorieren"]].forEach(([value, label]) => {
          const option = node("option", "", label);
          option.value = value;
          decision.append(option);
        });
        const canonical = node("input");
        canonical.name = "canonical_food";
        canonical.maxLength = 120;
        canonical.value = item.suggested_canonical_food || "";
        const score = node("select");
        score.name = "sighi_score";
        ["unknown", "0", "1", "2", "3"].forEach((value) => {
          const option = node("option", "", value === "unknown" ? "SIGHi unbekannt" : `SIGHi ${value}`);
          option.value = value;
          if (String(item.suggested_score ?? "unknown") === value) option.selected = true;
          score.append(option);
        });
        const confidence = node("select");
        confidence.name = "confidence";
        [["low", "niedrig"], ["medium", "mittel"], ["high", "hoch"]].forEach(([value, label]) => {
          const option = node("option", "", label);
          option.value = value;
          if (value === "medium") option.selected = true;
          confidence.append(option);
        });
        const method = node("select");
        method.name = "mapping_method";
        [["sighi_reference", "SIGHi-Referenz"], ["ingredient_label", "Zutatenliste"], ["manual_review", "manuelle Prüfung"], ["local_alias", "lokaler Alias"]].forEach(([value, label]) => {
          const option = node("option", "", label);
          option.value = value;
          method.append(option);
        });
        const note = node("input");
        note.name = "note";
        note.maxLength = 300;
        note.value = item.reason || "";
        const reviewRequired = node("input");
        reviewRequired.type = "hidden";
        reviewRequired.name = "ingredient_review_required";
        reviewRequired.value = "false";
        decision.addEventListener("change", () => {
          const composite = decision.value === "composite";
          const ignore = decision.value === "ignore";
          reviewRequired.value = composite ? "true" : "false";
          score.value = composite || ignore ? "unknown" : score.value;
          canonical.value = ignore ? "" : canonical.value;
          canonical.readOnly = ignore;
        });
        [
          ["Entscheidung", decision],
          ["Kanonisches Lebensmittel", canonical],
          ["SIGHi", score],
          ["Vertrauen", confidence],
          ["Methode", method],
          ["Notiz", note],
        ].forEach(([labelText, control]) => {
          const label = node("label", "mapping-field", labelText);
          label.append(control);
          form.append(label);
        });
        form.append(reviewRequired);
        const submit = node("button", "", "Aktion vormerken");
        submit.type = "submit";


Output:
Chunk ID: e98d06
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1230
Output:
import importlib.util
import sqlite3
from pathlib import Path

import pytest

SCRIPT = Path(__file__).resolve().parents[1] / "scripts/health/health_system_migrate.py"

@pytest.fixture
def migration(tmp_path, monkeypatch):
    spec = importlib.util.spec_from_file_location("health_system_migrate_sprint2", SCRIPT)
    assert spec and spec.loader
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)

    db = tmp_path / "health.db"
    connection = sqlite3.connect(db)
    connection.executescript(
        """
        CREATE TABLE laborwerte (id INTEGER PRIMARY KEY);
        CREATE TABLE dokumente (id INTEGER PRIMARY KEY);
        CREATE TABLE symptom_log (
            id INTEGER PRIMARY KEY,
            datum TEXT NOT NULL,
            symptom TEXT NOT NULL,
            kontext TEXT
        );
        """
    )
    connection.close()
    monkeypatch.setattr(module, "DB", db)
    monkeypatch.setattr(module, "BACKUP_DIR", tmp_path / "backups")
    return module, db

def test_sprint2_migration_is_idempotent_and_additive(migration):
    module, db = migration

    module.main()
    module.main()

    connection = sqlite3.connect(db)
    try:
        lab_columns = {row[1] for row in connection.execute("PRAGMA table_info(laborwerte)")}
        result_columns = {
            row[1]
            for row in connection.execute("PRAGMA table_info(multimodal_correlation_results)")
        }
        assert {"verified_against_original", "reference_range_source"} <= lab_columns
        assert {"expected_target_days", "target_coverage", "quality_flags"} <= result_columns
        assert connection.execute(
            "SELECT 1 FROM sqlite_master WHERE type='index' AND name='idx_symptom_log_daily_dimension'"
        ).fetchone()
        assert connection.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
    finally:
        connection.close()

def test_existing_old_result_schema_is_upgraded_without_losing_rows(migration):
    module, db = migration
    connection = sqlite3.connect(db)
    connection.executescript(
        """
        CREATE TABLE multimodal_correlation_results (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            predictor TEXT NOT NULL,
            target TEXT NOT NULL,
            lag_days INTEGER NOT NULL,
            medication_phase TEXT NOT NULL,
            n INTEGER NOT NULL,
            eligible_target_days INTEGER NOT NULL,
            missing_pairs INTEGER NOT NULL,
            rho REAL,
            p_value REAL,
            q_value REAL,
            status TEXT NOT NULL,
            method TEXT NOT NULL,
            quality_flags TEXT NOT NULL,
            interpretation TEXT NOT NULL,
            computed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE(predictor,target,lag_days,medication_phase)
        );
        INSERT INTO multimodal_correlation_results
            (predictor,target,lag_days,medication_phase,n,eligible_target_days,
             missing_pairs,status,method,quality_flags,interpretation)
        VALUES
            ('nutrition:histamine_score','symptom_total',0,'unknown',0,1,
             1,'insufficient_n','old-safe-method','[]','old aggregate row');
        """
    )
    connection.close()

    module.main()
    module.main()

    connection = sqlite3.connect(db)
    try:
        columns = {
            row[1]: row
            for row in connection.execute("PRAGMA table_info(multimodal_correlation_results)")
        }
        assert columns["expected_target_days"][4] == "0"
        assert columns["target_coverage"][4] == "0"
        row = connection.execute(
            """SELECT predictor,target,n,expected_target_days,target_coverage
               FROM multimodal_correlation_results"""
        ).fetchone()
        assert row == ("nutrition:histamine_score", "symptom_total", 0, 0, 0.0)
        assert connection.execute(
            "SELECT COUNT(*) FROM multimodal_correlation_results"
        ).fetchone()[0] == 1
        assert connection.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
    finally:
        connection.close()

def test_migration_does_not_create_unique_index_over_existing_duplicates(migration):
    module, db = migration
    connection = sqlite3.connect(db)
    connection.executemany(
        "INSERT INTO symptom_log(id,datum,symptom,kontext) VALUES(?,?,?,?)",
        [
            (1, "2026-01-01", "GI/Darm", "daily_quick_score"),
            (2, "2026-01-01", "GI/Darm", "daily_quick_score"),
        ],
    )
    connection.commit()
    connection.close()

    module.main()

    connection = sqlite3.connect(db)
    try:
        assert connection.execute(
            "SELECT 1 FROM sqlite_master WHERE type='index' AND name='idx_symptom_log_daily_dimension'"
        ).fetchone() is None
        assert connection.execute("SELECT COUNT(*) FROM symptom_log").fetchone()[0] == 2
    finally:
        connection.close()


Output:
Chunk ID: c8d4a7
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1830
Output:
#!/usr/bin/env python3
"""Migration for the health management system.

Adds explicit review/staging/insight/report tables without destroying existing data.
Run repeatedly; it is idempotent.
"""
from __future__ import annotations

import os
import shutil
import sqlite3
from datetime import datetime
from pathlib import Path

BASE = Path(os.path.expanduser("~/.hermes/assets/Gesundheit"))
DB = BASE / "health_data.db"
BACKUP_DIR = BASE / "backups"

def column_exists(conn: sqlite3.Connection, table: str, column: str) -> bool:
    return any(row[1] == column for row in conn.execute(f"PRAGMA table_info({table})"))

def add_column(conn: sqlite3.Connection, table: str, column: str, ddl: str) -> None:
    if not column_exists(conn, table, column):
        conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {ddl}")

def main() -> None:
    if not DB.exists():
        raise SystemExit(f"DB not found: {DB}")
    BACKUP_DIR.mkdir(parents=True, exist_ok=True)
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup = BACKUP_DIR / f"health_data_before_review_workflow_{stamp}.db"
    shutil.copy2(DB, backup)

    conn = sqlite3.connect(DB)
    conn.execute("PRAGMA foreign_keys=ON")

    # Add non-breaking columns to existing core tables.
    add_column(conn, "laborwerte", "abnahme_datum", "TEXT")
    add_column(conn, "laborwerte", "befund_datum", "TEXT")
    add_column(conn, "laborwerte", "wert_original", "TEXT")
    add_column(conn, "laborwerte", "quelle", "TEXT")
    add_column(conn, "laborwerte", "validierungsstatus", "TEXT DEFAULT 'unvalidiert'")
    add_column(conn, "laborwerte", "verified_against_original", "INTEGER NOT NULL DEFAULT 0")
    add_column(conn, "laborwerte", "reference_range_source", "TEXT")
    add_column(conn, "laborwerte", "review_batch_id", "TEXT")
    add_column(conn, "dokumente", "quelle", "TEXT")
    add_column(conn, "dokumente", "review_status", "TEXT DEFAULT 'nicht_geprueft'")
    add_column(conn, "dokumente", "processing_quality", "TEXT")

    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS health_processing_runs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            batch_id TEXT UNIQUE NOT NULL,
            run_type TEXT NOT NULL,
            source TEXT,
            source_ref TEXT,
            started_at TEXT DEFAULT (datetime('now','localtime')),
            finished_at TEXT,
            status TEXT DEFAULT 'running',
            summary TEXT,
            artifacts_json TEXT
        );

        CREATE TABLE IF NOT EXISTS laborwerte_staging (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            batch_id TEXT NOT NULL,
            dokument_id INTEGER,
            parameter_name TEXT NOT NULL,
            kategorie TEXT,
            wert_extrahiert TEXT,
            wert_korrigiert TEXT,
            wert_final TEXT,
            einheit TEXT,
            referenzbereich TEXT,
            reference_min TEXT,
            reference_max TEXT,
            flag TEXT,
            abnahme_datum TEXT,
            befund_datum TEXT,
            dokumentseite TEXT,
            confidence REAL,
            extraktionsmethode TEXT,
            quelle TEXT,
            kommentar TEXT,
            status TEXT DEFAULT 'zur_pruefung',
            created_at TEXT DEFAULT (datetime('now','localtime')),
            reviewed_at TEXT,
            UNIQUE(batch_id, parameter_name, abnahme_datum, einheit)
        );

        CREATE TABLE IF NOT EXISTS document_insights (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            dokument_id INTEGER,
            batch_id TEXT,
            insight_type TEXT NOT NULL,
            datum TEXT,
            titel TEXT NOT NULL,
            inhalt TEXT,
            severity TEXT DEFAULT 'info',
            source TEXT,
            created_at TEXT DEFAULT (datetime('now','localtime'))
        );

        CREATE TABLE IF NOT EXISTS health_daily_summary (
            datum TEXT PRIMARY KEY,
            nutrition_json TEXT,
            vitals_json TEXT,
            symptoms_json TEXT,
            medications_json TEXT,
            notes TEXT,
            generated_at TEXT DEFAULT (datetime('now','localtime'))
        );

        CREATE TABLE IF NOT EXISTS health_insights (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            insight_date TEXT NOT NULL,
            insight_type TEXT NOT NULL,
            title TEXT NOT NULL,
            description TEXT,
            evidence_json TEXT,
            confidence TEXT DEFAULT 'niedrig',
            severity TEXT DEFAULT 'info',
            status TEXT DEFAULT 'offen',
            created_at TEXT DEFAULT (datetime('now','localtime'))
        );

        CREATE TABLE IF NOT EXISTS report_runs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            report_type TEXT NOT NULL,
            period_start TEXT,
            period_end TEXT,
            output_path TEXT,
            drive_file_id TEXT,
            status TEXT DEFAULT 'created',
            summary TEXT,
            created_at TEXT DEFAULT (datetime('now','localtime'))
        );

        CREATE TABLE IF NOT EXISTS multimodal_correlation_results (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            predictor TEXT NOT NULL,
            target TEXT NOT NULL,
            lag_days INTEGER NOT NULL,
            medication_phase TEXT NOT NULL,
            n INTEGER NOT NULL,
            eligible_target_days INTEGER NOT NULL,
            expected_target_days INTEGER NOT NULL DEFAULT 0,
            target_coverage REAL NOT NULL DEFAULT 0,
            missing_pairs INTEGER NOT NULL,
            rho REAL,
            p_value REAL,
            q_value REAL,
            status TEXT NOT NULL,
            method TEXT NOT NULL,
            quality_flags TEXT NOT NULL,
            interpretation TEXT NOT NULL,
            computed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE(predictor,target,lag_days,medication_phase)
        );

        CREATE INDEX IF NOT EXISTS idx_multimodal_correlations_status
            ON multimodal_correlation_results(status,target,medication_phase);
        CREATE INDEX IF NOT EXISTS idx_labor_staging_status ON laborwerte_staging(status);
        CREATE INDEX IF NOT EXISTS idx_labor_staging_batch ON laborwerte_staging(batch_id);
        CREATE INDEX IF NOT EXISTS idx_laborwerte_abnahme ON laborwerte(abnahme_datum);
        CREATE INDEX IF NOT EXISTS idx_health_insights_date ON health_insights(insight_date);
        CREATE INDEX IF NOT EXISTS idx_document_insights_doc ON document_insights(dokument_id);
        """
    )

    add_column(conn, "multimodal_correlation_results", "expected_target_days", "INTEGER NOT NULL DEFAULT 0")
    add_column(conn, "multimodal_correlation_results", "target_coverage", "REAL NOT NULL DEFAULT 0")
    duplicate_quick_rows = conn.execute(
        """SELECT COUNT(*) FROM (
            SELECT datum,symptom,kontext FROM symptom_log
            GROUP BY datum,symptom,kontext HAVING COUNT(*) > 1
        )"""
    ).fetchone()[0]
    if duplicate_quick_rows == 0:
        conn.execute(
            "CREATE UNIQUE INDEX IF NOT EXISTS idx_symptom_log_daily_dimension ON symptom_log(datum,symptom,kontext)"
        )

    conn.commit()
    conn.close()
    print(f"Migration OK. Backup: {backup}")

if __name__ == "__main__":
    main()