← Files ClaraARCHIVED FILE

scripts/decision_narrative.py

3.25 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

"""Store model-authored decision prose against current case and approval identity."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from case_store import atomic_text

__all__ = ["NARRATIVE_FILENAME", "commit_narrative", "load_narrative"]
NARRATIVE_FILENAME = "decision_narrative.json"


def _validate(payload: dict[str, Any], approved_claim_ids: set[str]) -> list[str]:
    if not isinstance(payload, dict):
        raise ValueError("Decision narrative must be a JSON object")
    review = payload.get("review", {})
    if not isinstance(review, dict):
        raise ValueError("Decision narrative review must be an object")
    if (
        review.get("status") not in {"model_reviewed", "human_reviewed"}
        or not str(review.get("reviewed_by", "")).strip()
    ):
        raise ValueError("Decision narrative requires a recorded model or human review")
    paragraphs = payload.get("paragraphs")
    if not isinstance(paragraphs, list) or not paragraphs:
        raise ValueError("Decision narrative requires model-authored paragraphs")
    result = []
    for paragraph in paragraphs:
        if not isinstance(paragraph, dict):
            raise ValueError("Each narrative paragraph must be an object")
        text = paragraph.get("text", "")
        claims = paragraph.get("claim_ids", [])
        if (
            not isinstance(text, str)
            or not text.strip()
            or not isinstance(claims, list)
            or not claims
        ):
            raise ValueError(
                "Each narrative paragraph needs text and declared claim IDs"
            )
        if not all(isinstance(claim, str) for claim in claims):
            raise ValueError("Narrative claim IDs must be strings")
        if not set(claims).issubset(approved_claim_ids):
            raise ValueError(
                "Decision narrative references unapproved or unknown claims"
            )
        result.append(text.strip())
    return result


def commit_narrative(
    case_dir: Path,
    authored: Path,
    *,
    basis: dict[str, Any],
    approved_claim_ids: set[str],
) -> Path:
    """Bind authored prose, not register order, to an exact reviewed case basis."""
    payload = json.loads(authored.read_text(encoding="utf-8"))
    _validate(payload, approved_claim_ids)
    payload["schema_version"] = 1
    payload["basis"] = basis
    payload["boundary"] = (
        "Paragraph meaning and completeness were reviewed by the declared reviewer; code checks identity and approval references only."
    )
    path = case_dir / NARRATIVE_FILENAME
    atomic_text(path, json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
    return path


def load_narrative(
    case_dir: Path, *, basis: dict[str, Any] | None, approved_claim_ids: set[str]
) -> list[str]:
    """Return current authored paragraphs; never fabricate a missing recommendation."""
    path = case_dir / NARRATIVE_FILENAME
    if not path.exists():
        return []
    payload = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(payload, dict) or basis is None or payload.get("basis") != basis:
        raise ValueError(
            "Decision narrative is stale; review the current workpaper and commit updated prose"
        )
    return _validate(payload, approved_claim_ids)

SHA-256: d596dcb192db69e8ca32e511f99aba371328260ddd916e77c7b7d250b781daf2