feat: add persistent monthly settlement drafts and approved valuation selection
This commit is contained in:
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user