← Files Investment BankingARCHIVED FILE
skills/dcf-model-builder/scripts/run_pipeline_runtime
11.2 KB · Oct 2, 2026 · 00:27 UTC
#!/usr/bin/env python3
"""Run the deterministic DCF pipeline."""
from __future__ import annotations
import argparse
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_ROOT = SCRIPT_DIR.parent
PLUGIN_ROOT = SCRIPT_DIR.parents[2]
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
if str(PLUGIN_ROOT) not in sys.path:
sys.path.insert(0, str(PLUGIN_ROOT))
from skill_core import ( # noqa: E402
build_p0_handoff,
build_workbook_sheets,
compute_checks,
determine_model_status,
load_json,
normalize_plan,
render_report,
run_scenario,
run_sensitivities,
validate_plan_structure,
write_json,
write_xlsx,
)
from shared.model_artifacts import write_model_manifest # noqa: E402
from shared.model_citations import write_model_citations_from_sheets # noqa: E402
def _blocked_run_log(
errors: list[str], output_dir: Path, include_report_md: bool = True
) -> dict[str, Any]:
paths = {
"workbook": str(output_dir / "model.xlsx"),
"plan": str(output_dir / "plan.json"),
"run_log": str(output_dir / "run_log.json"),
"manifest": str(output_dir / "manifest.json"),
}
if include_report_md:
paths["report"] = str(output_dir / "report.md")
return {
"model_status": "not-decision-ready",
"workbook_mode": "deterministic_export",
"source_basis": [],
"hard_failures": errors,
"warnings": [],
"assumptions": {},
"checks": {"validation_errors": errors},
"p0_handoff": {
"selected_valuation_range": {},
"scenarios": {},
"wacc_and_terminal_assumptions": {},
"key_value_drivers": [],
"major_caveats": errors,
"model_status": "not-decision-ready",
"paths": paths,
},
}
def _file_record(path: Path, role: str, description: str) -> dict[str, Any]:
return {
"path": str(path),
"role": role,
"description": description,
"exists": True if path.name == "manifest.json" else path.exists(),
}
def write_output_manifest(
output_dir: Path,
model_status: str,
human_deliverables: list[tuple[Path, str]],
agent_artifacts: list[tuple[Path, str]],
hard_failures: list[Any],
warnings: list[Any],
) -> dict[str, Any]:
workbook_path = human_deliverables[0][0] if human_deliverables else None
return write_model_manifest(
output_dir,
"dcf-model-builder",
"deterministic_export",
workbook_path,
model_status,
agent_artifacts,
hard_failures,
warnings,
)
manifest_path = output_dir / "manifest.json"
all_agent_artifacts = [*agent_artifacts, (manifest_path, "agent-facing output manifest")]
manifest = {
"manifest_version": "1.0",
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"skill": "dcf-model-builder",
"artifact_mode": "deterministic_export",
"model_status": model_status,
"output_dir": str(output_dir),
"primary_human_deliverable": str(human_deliverables[0][0]) if human_deliverables else None,
"human_deliverables": [
_file_record(path, "human_deliverable", description)
for path, description in human_deliverables
],
"agent_artifacts": [
_file_record(path, "agent_artifact", description)
for path, description in all_agent_artifacts
],
"hard_failure_count": len(hard_failures),
"warning_count": len(warnings),
"discipline_note": "Use the human deliverable as the main output; manifest/run_log/plan files are agent-facing support artifacts.",
}
write_json(manifest_path, manifest)
return manifest
def write_blocked_outputs(
errors: list[str], output_dir: Path, include_report_md: bool
) -> dict[str, Any]:
run_log = _blocked_run_log(errors, output_dir, include_report_md=include_report_md)
run_log["output_manifest"] = str(output_dir / "manifest.json")
write_json(output_dir / "run_log.json", run_log)
write_output_manifest(
output_dir,
"not-decision-ready",
[],
[(output_dir / "run_log.json", "blocked run log with validation/read errors")],
errors,
[],
)
return run_log
def main() -> int:
parser = argparse.ArgumentParser(description="Run DCF deterministic export pipeline")
parser.add_argument("plan_path", help="Path to plan.json")
parser.add_argument(
"--output-dir",
default=None,
help="Output directory for model.xlsx plus support artifacts. Defaults to ./output.",
)
parser.add_argument(
"--print-report",
action="store_true",
help="Print the text report between clear markers without writing a Markdown file",
)
parser.add_argument(
"--write-report-md",
action="store_true",
help="Write legacy report.md in addition to the workbook. Off by default.",
)
parser.add_argument(
"--no-report-md",
action="store_true",
help="Deprecated compatibility flag; Markdown report files are already off by default.",
)
args = parser.parse_args()
write_report_md = bool(args.write_report_md and not args.no_report_md)
output_dir = Path(args.output_dir) if args.output_dir else Path.cwd() / "output"
output_dir.mkdir(parents=True, exist_ok=True)
try:
raw_plan = load_json(args.plan_path)
except FileNotFoundError:
errors = [f"plan file not found: {args.plan_path}"]
write_blocked_outputs(errors, output_dir, include_report_md=write_report_md)
print("BLOCKED DCF PIPELINE")
for e in errors:
print(f"- {e}")
return 1
except json.JSONDecodeError as exc:
errors = [f"invalid JSON: line {exc.lineno}, column {exc.colno}: {exc.msg}"]
write_blocked_outputs(errors, output_dir, include_report_md=write_report_md)
print("BLOCKED DCF PIPELINE")
for e in errors:
print(f"- {e}")
return 1
except Exception as exc:
errors = [f"could not read plan: {exc}"]
write_blocked_outputs(errors, output_dir, include_report_md=write_report_md)
print("BLOCKED DCF PIPELINE")
for e in errors:
print(f"- {e}")
return 1
validation_errors = validate_plan_structure(raw_plan)
if validation_errors:
write_blocked_outputs(validation_errors, output_dir, include_report_md=write_report_md)
print("BLOCKED DCF PIPELINE")
for e in validation_errors:
print(f"- {e}")
return 1
plan = normalize_plan(raw_plan, SKILL_ROOT)
write_json(output_dir / "plan.json", plan)
scenario_results: dict[str, dict[str, Any]] = {}
execution_errors: list[str] = []
for scenario_name in ["base", "downside", "upside"]:
try:
scenario_results[scenario_name] = run_scenario(plan, scenario_name)
except Exception as exc:
execution_errors.append(f"scenario {scenario_name} failed: {exc}")
try:
sensitivity_result = run_sensitivities(plan)
except Exception as exc:
sensitivity_result = {
"rows": [],
"directionality": {
"passed": False,
"details": [{"check": "sensitivity run", "passed": False, "detail": str(exc)}],
},
}
execution_errors.append(f"sensitivities failed: {exc}")
checks = compute_checks(plan, scenario_results, sensitivity_result)
if execution_errors:
checks.setdefault("hard_failures", [])
checks["hard_failures"].extend(execution_errors)
# Deduplicate while preserving order.
checks["hard_failures"] = list(dict.fromkeys(checks["hard_failures"]))
hard_failures = checks.get("hard_failures", [])
warnings = checks.get("warnings", [])
model_status = determine_model_status(plan, hard_failures, warnings)
assumptions = {
"company": plan.get("meta", {}).get("company"),
"model_type": plan.get("meta", {}).get("model_type"),
"valuation_date": plan.get("meta", {}).get("valuation_date"),
"horizon_years": plan.get("timeline", {}).get("horizon_years"),
"terminal_method": plan.get("terminal_value", {}).get("method"),
"cash_flow_basis": plan.get("forecast", {}).get("cash_flow_basis"),
}
p0_handoff = build_p0_handoff(
plan,
scenario_results,
sensitivity_result,
model_status,
warnings,
output_dir,
include_report_md=write_report_md,
)
run_log = {
"model_status": model_status,
"workbook_mode": "deterministic_export",
"source_basis": plan.get("source_basis", []),
"hard_failures": hard_failures,
"warnings": warnings,
"assumptions": assumptions,
"checks": checks,
"p0_handoff": p0_handoff,
"output_manifest": str(output_dir / "manifest.json"),
"output_paths": {
"model_xlsx": str(output_dir / "model.xlsx"),
"plan_json": str(output_dir / "plan.json"),
"run_log_json": str(output_dir / "run_log.json"),
"manifest_json": str(output_dir / "manifest.json"),
**({"report_md": str(output_dir / "report.md")} if write_report_md else {}),
},
}
workbook_sheets = build_workbook_sheets(
plan, scenario_results, sensitivity_result, checks, run_log
)
write_xlsx(output_dir / "model.xlsx", workbook_sheets)
model_citations = write_model_citations_from_sheets(
output_dir / "model_citations.json", output_dir / "model.xlsx", workbook_sheets
)
run_log["model_citations_path"] = str(output_dir / "model_citations.json")
run_log["model_citation_count"] = len(model_citations)
run_log["output_paths"]["model_citations_json"] = str(output_dir / "model_citations.json")
write_json(output_dir / "run_log.json", run_log)
report = render_report(plan, scenario_results, checks, run_log)
if write_report_md:
(output_dir / "report.md").write_text(report, encoding="utf-8")
human_deliverables = [(output_dir / "model.xlsx", "deterministic DCF workbook")]
if write_report_md:
human_deliverables.append((output_dir / "report.md", "legacy Markdown DCF report"))
write_output_manifest(
output_dir,
model_status,
human_deliverables,
[
(output_dir / "plan.json", "normalized plan actually used"),
(
output_dir / "model_citations.json",
"workbook cell/range citation ledger for model outputs",
),
(
output_dir / "run_log.json",
"machine-readable run log, checks, warnings, and downstream handoff",
),
],
hard_failures,
warnings,
)
if args.print_report:
print("---DCF_MODEL_REPORT_START---")
print(report.rstrip())
print("---DCF_MODEL_REPORT_END---")
else:
print(f"DCF pipeline complete. model_status={model_status}. Output: {output_dir}")
return 0 if not hard_failures else 2
if __name__ == "__main__":
raise SystemExit(main())
SHA-256: 8141f70bf0ef2954d1ecb2e431bb3e7f76b644d62cc299196f595014a9f80ef9