← Files Investment BankingARCHIVED FILE
shared/model_citations.py
11.2 KB · Oct 2, 2026 · 00:27 UTC
from __future__ import annotations
import json
import re
import xml.etree.ElementTree as ET
import zipfile
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Mapping, Sequence
REQUIRED_CITATION_FIELDS = {
"citation_id",
"workbook_path",
"sheet",
"cell_or_range",
"metric_name",
"value",
"formula",
"source_ids",
"assumption_flag",
"tie_out_status",
}
VALID_TIE_OUT_STATUSES = {"tied", "not_tied", "needs_review", "model_generated", "not_applicable"}
def _slug(value: Any) -> str:
raw = str(value or "").strip().lower()
raw = re.sub(r"[^a-z0-9]+", "-", raw).strip("-")
return raw or "citation"
def _col_letter(index_1: int) -> str:
out = ""
n = int(index_1)
while n > 0:
n, rem = divmod(n - 1, 26)
out = chr(ord("A") + rem) + out
return out or "A"
def _cell(row_1: int, col_1: int) -> str:
return f"{_col_letter(col_1)}{row_1}"
def _stringify_value(value: Any) -> str:
if value is None:
return ""
if isinstance(value, (dict, list)):
return json.dumps(value, sort_keys=True)
return str(value)
def _source_ids(
row: Mapping[str, Any], default_source_ids: Sequence[str] | None = None
) -> list[str]:
value = row.get("source_ids") or row.get("source_id") or row.get("source")
if isinstance(value, list):
out = [str(item) for item in value if str(item)]
elif value:
out = [str(value)]
else:
out = [str(item) for item in (default_source_ids or ["model-output"]) if str(item)]
return out or ["model-output"]
def citation_item(
citation_id: str,
workbook_path: str | Path,
sheet: str,
cell_or_range: str,
metric_name: str,
value: Any = "",
formula: str = "",
source_ids: Sequence[str] | None = None,
assumption_flag: bool = False,
tie_out_status: str = "model_generated",
**extra: Any,
) -> dict[str, Any]:
sources = [str(item) for item in (source_ids or ["model-output"]) if str(item)] or [
"model-output"
]
cid = str(
citation_id or f"model-output:{_slug(sheet)}:{_slug(metric_name)}:{_slug(cell_or_range)}"
)
cell_value = str(cell_or_range or "A1")
item = {
"citation_id": cid,
"id": cid,
"source_id": cid,
"parent_source_id": sources[0],
"title": str(metric_name or cid),
"short_label": f"Model: {sheet}!{cell_value}",
"type": "model_cell",
"quality": "model_output",
"workbook_path": str(workbook_path),
"sheet": str(sheet),
"cell_or_range": cell_value,
"cell": cell_value.split(":", 1)[0],
"range": cell_value,
"metric_name": str(metric_name or cid),
"value": _stringify_value(value),
"formula": str(formula or ""),
"source_ids": sources,
"assumption_flag": bool(assumption_flag),
"tie_out_status": tie_out_status
if tie_out_status in VALID_TIE_OUT_STATUSES
else "needs_review",
"generated_at": datetime.now(timezone.utc).isoformat(),
"aliases": [str(metric_name or cid)],
"notes": "Generated workbook-level citation for dashboard/source-gate use.",
}
item.update(extra)
return item
def _table_to_records(table: Any) -> tuple[list[str], list[Mapping[str, Any]]]:
if not table:
return [], []
if isinstance(table, list) and table and isinstance(table[0], Mapping):
headers: list[str] = []
for row in table:
for key in row.keys():
if str(key) not in headers:
headers.append(str(key))
return headers, [row for row in table if isinstance(row, Mapping)]
if isinstance(table, list) and table and isinstance(table[0], (list, tuple)):
headers = [str(item) for item in table[0]]
records = []
for row in table[1:]:
if not isinstance(row, (list, tuple)):
continue
records.append(
{
headers[idx] if idx < len(headers) else f"col_{idx + 1}": row[idx]
for idx in range(len(row))
}
)
return headers, records
return [], []
def _value_header(headers: Sequence[str]) -> str | None:
preferred = ["value", "Value", "metric_value", "amount", "output", "result"]
for key in preferred:
if key in headers:
return key
return headers[-1] if headers else None
def _metric_name(row: Mapping[str, Any], fallback: str) -> str:
for key in ("metric_name", "metric", "line_item", "item", "field", "name", "label", "section"):
if row.get(key) not in (None, ""):
return str(row.get(key))
return fallback
def build_model_citations_from_sheets(
workbook_path: str | Path,
sheets: Mapping[str, Any],
default_source_ids: Sequence[str] | None = None,
max_per_sheet: int = 80,
) -> list[dict[str, Any]]:
citations: list[dict[str, Any]] = []
workbook = str(workbook_path)
seen: set[str] = set()
for sheet_name, table in sheets.items():
headers, records = _table_to_records(table)
if not records:
continue
value_header = _value_header(headers)
value_col_idx = (
(list(headers).index(value_header) + 1)
if value_header in headers
else max(1, len(headers))
)
emitted = 0
for idx, row in enumerate(records, start=2):
metric = _metric_name(row, f"{sheet_name} row {idx - 1}")
if not metric.strip():
continue
value = row.get(value_header, "") if value_header else ""
if value in (None, "") and emitted >= 5:
continue
cell_ref = _cell(idx, value_col_idx)
cid = f"model-output:{_slug(sheet_name)}:{_slug(metric)}:{_slug(row.get('scenario', ''))}:{_slug(row.get('period_label', row.get('period', '')))}:{cell_ref.lower()}"
if cid in seen:
continue
seen.add(cid)
assumption_flag = "assumption" in sheet_name.lower() or "assumption" in metric.lower()
citations.append(
citation_item(
cid,
workbook,
str(sheet_name),
cell_ref,
metric,
value,
str(row.get("formula", "")),
_source_ids(row, default_source_ids),
assumption_flag,
str(row.get("tie_out_status", "model_generated")),
scenario=row.get("scenario", ""),
statement=row.get("statement", ""),
section=row.get("section", ""),
line_item=row.get("line_item", metric),
period_label=row.get("period_label", row.get("period", "")),
units=row.get("units", ""),
)
)
emitted += 1
if emitted >= max_per_sheet:
break
return citations
def workbook_sheet_names(workbook_path: str | Path) -> list[str]:
with zipfile.ZipFile(workbook_path) as archive:
workbook_xml = archive.read("xl/workbook.xml")
root = ET.fromstring(workbook_xml)
ns = {"main": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
sheets = root.find("main:sheets", ns)
if sheets is None:
return []
return [sheet.attrib.get("name", "Sheet") for sheet in sheets.findall("main:sheet", ns)]
def build_model_citations_for_workbook_outline(
workbook_path: str | Path, max_sheets: int = 12
) -> list[dict[str, Any]]:
citations = []
for sheet in workbook_sheet_names(workbook_path)[:max_sheets]:
metric = f"{sheet} workbook section"
citations.append(
citation_item(
f"model-output:{_slug(sheet)}:a1",
workbook_path,
sheet,
"A1",
metric,
"See workbook section",
"",
["model-output"],
"assumption" in sheet.lower(),
"needs_review",
)
)
return citations
def write_model_citations(path: str | Path, citations: Sequence[Mapping[str, Any]]) -> Path:
target = Path(path)
target.parent.mkdir(parents=True, exist_ok=True)
validate_model_citations(citations)
target.write_text(json.dumps(list(citations), indent=2) + "\n", encoding="utf-8")
return target
def write_model_citations_from_sheets(
path: str | Path,
workbook_path: str | Path,
sheets: Mapping[str, Any],
default_source_ids: Sequence[str] | None = None,
) -> list[dict[str, Any]]:
citations = build_model_citations_from_sheets(
workbook_path, sheets, default_source_ids=default_source_ids
)
if not citations:
citations = build_model_citations_for_workbook_outline(workbook_path)
write_model_citations(path, citations)
return citations
def write_model_citations_for_workbook(
path: str | Path, workbook_path: str | Path
) -> list[dict[str, Any]]:
citations = build_model_citations_for_workbook_outline(workbook_path)
write_model_citations(path, citations)
return citations
def load_model_citations(path: str | Path) -> list[dict[str, Any]]:
payload = json.loads(Path(path).read_text(encoding="utf-8"))
if isinstance(payload, dict):
payload = payload.get("model_citations") or payload.get("workbook_citations") or []
return [item for item in payload if isinstance(item, dict)] if isinstance(payload, list) else []
def validate_model_citations(
payload: Sequence[Mapping[str, Any]] | Mapping[str, Any], strict: bool = False
) -> list[str]:
citations: Sequence[Mapping[str, Any]]
if isinstance(payload, Mapping):
citations = payload.get("model_citations") or payload.get("workbook_citations") or [] # type: ignore[assignment]
else:
citations = payload
errors: list[str] = []
if not isinstance(citations, Sequence) or isinstance(citations, (str, bytes)) or not citations:
return ["model citations must be a non-empty array"]
for idx, citation in enumerate(citations):
prefix = f"citations[{idx}]"
if not isinstance(citation, Mapping):
errors.append(f"{prefix} must be an object")
continue
missing = sorted(field for field in REQUIRED_CITATION_FIELDS if field not in citation)
if missing:
errors.append(f"{prefix} missing required fields: {', '.join(missing)}")
if citation.get("tie_out_status") not in VALID_TIE_OUT_STATUSES:
errors.append(f"{prefix}.tie_out_status invalid: {citation.get('tie_out_status')}")
source_ids = citation.get("source_ids")
if not isinstance(source_ids, list) or not source_ids:
errors.append(f"{prefix}.source_ids must be a non-empty array")
if strict:
for field in ("citation_id", "workbook_path", "sheet", "cell_or_range", "metric_name"):
if str(citation.get(field, "")).strip().lower() in {
"",
"unknown",
"not_provided",
"n/a",
}:
errors.append(f"{prefix}.{field} cannot be placeholder in strict mode")
return errors
SHA-256: b8d113a9a2751d4af427537935750fc39d19fb05d866f5b1db97f2d8141057e5