feat: add persistent monthly settlement drafts and approved valuation selection

This commit is contained in:
2026-10-10 02:49:34 +00:00
parent 68212e773b
commit 92f44661c3
37 changed files with 2902 additions and 29 deletions
+41 -4
View File
@@ -67,6 +67,18 @@ def _public_evidence(item: dict[str, Any], strict: bool = False) -> dict[str, An
"valuation_id", "valuation_kind", "active", "last_synced_at", "last_balance", "valuation_amount", "reason",
"unverified_candidates", "candidate", "candidates", "state_hash", "blocking", "snapshot_run_id", "is_correction",
"correction_of_run_id", "correction_reason",
# Draft-close provenance is intentionally an exact allowlist. Nested
# objects are recursively filtered by these same keys.
"date_difference_days", "direction", "original_recorded_at", "selection_mode",
"date_difference_direction", "recorded_at", "representative_liability_definition",
"allow_missing_exclusions", "allow_carried_before", "allow_manual_substitute",
"reason", "selection_reason", "selected_by", "selected_at", "item_version", "source_ref",
"source_attributes", "approval", "approval_reference", "approved_by",
"approved_at", "approval_reason", "draft_id", "draft_version",
"review_fingerprint", "contains_substitutions", "contains_carried",
"has_approved_missing", "carried_forward", "source_type", "source_id",
"amount", "currency", "valuation_id", "valuation_date", "age_days",
"verification_status", "evidence_kind", "carried_forward",
}
if isinstance(item, dict):
return {key: _public_evidence(value, strict) for key, value in item.items() if key in allowed or (not strict and isinstance(value, (dict, list)))}
@@ -304,6 +316,9 @@ def _snapshot_rows_for_run(conn: sqlite3.Connection, run_id: str) -> tuple[list[
integrity_error = any(value is None for value in evidence.values())
metadata = {
"snapshot_run_id": run["id"],
"draft_id": run["draft_id"] if "draft_id" in run.keys() else "",
"draft_version": run["draft_version"] if "draft_version" in run.keys() else 0,
"review_fingerprint": run["review_fingerprint"] if "review_fingerprint" in run.keys() else "",
"is_correction": bool(run["correction_of_run_id"]),
"correction_of_run_id": run["correction_of_run_id"] or "",
"correction_reason": run["correction_reason"] or "",
@@ -408,10 +423,18 @@ def _snapshot_items(conn: sqlite3.Connection, basis_date: str) -> list[dict[str,
def _closed_run_metadata(metadata: dict[str, Any]) -> dict[str, Any]:
"""Expose immutable run evidence without source notes or memos."""
applied = [_public_evidence(item, strict=True) for item in metadata.get("applied_evidence", [])]
excluded = [_public_evidence(item, strict=True) for item in metadata.get("excluded_evidence", [])]
carried = [_public_evidence(item, strict=True) for item in metadata.get("carried_evidence", [])]
contains_substitutions = any(item.get("selection_mode") == "manual_substitute" for item in applied)
contains_carried = bool(carried) or any(item.get("selection_mode") == "carried_before" or item.get("carried_forward") for item in applied)
has_approved_missing = any(item.get("reason") == "approved_missing" for item in excluded)
# Evidence is immutable: do not inject absent draft keys into legacy
# exclusions/carried rows. Draft finalization persists its own lineage.
warnings = []
exclusions = [_public_evidence(item, strict=True) for item in metadata.get("excluded_evidence", [])]
candidate_warnings: dict[tuple[str, str], dict[str, Any]] = {}
for item in exclusions:
for item in excluded:
if item.get("candidate"):
key = (item.get("source_type", ""), item.get("source_id", ""))
warning_shape = {"code": "unverified_candidate", "source_type": key[0], "source_id": key[1], "candidates": []}
@@ -424,9 +447,16 @@ def _closed_run_metadata(metadata: dict[str, Any]) -> dict[str, Any]:
warnings.extend(candidate_warnings.values())
return {
"warnings": warnings,
"excluded_items": exclusions,
"carried_items": [_public_evidence(item, strict=True) for item in metadata.get("carried_evidence", [])],
"excluded_items": excluded,
"applied_evidence": applied,
"carried_items": carried,
"contains_substitutions": contains_substitutions,
"contains_carried": contains_carried,
"has_approved_missing": has_approved_missing,
"snapshot_run_id": metadata.get("snapshot_run_id", ""),
"draft_id": metadata.get("draft_id", ""),
"draft_version": metadata.get("draft_version", 0),
"review_fingerprint": metadata.get("review_fingerprint", ""),
"close_status": metadata.get("close_status", ""),
"selection_policy": metadata.get("selection_policy", ""),
"approval": {} if metadata.get("legacy_closed") else {k: metadata.get(k, "") for k in ("approved_by", "approved_at", "approval_reason", "approval_reference")},
@@ -1104,6 +1134,13 @@ def get_net_worth_timeseries(start_date: str = "", end_date: str = "", months: i
"source": "asset_snapshots",
"item_count": len(items),
"snapshot_run_id": metadata.get("snapshot_run_id", ""),
"draft_id": run_public.get("draft_id", ""),
"draft_version": run_public.get("draft_version", 0),
"review_fingerprint": run_public.get("review_fingerprint", ""),
"contains_substitutions": run_public.get("contains_substitutions", False),
"contains_carried": run_public.get("contains_carried", False),
"has_approved_missing": run_public.get("has_approved_missing", False),
"applied_evidence": run_public.get("applied_evidence", []),
"close_status": metadata.get("close_status", ""),
"selection_policy": metadata.get("selection_policy", ""),
"warnings": run_public["warnings"],