Implement FIN-WP-0001 ledger, evaluate, and evidence lane (T03–T05)
Add SQLite-backed cost ledger with import/summary CLI, cron-friendly evaluate command with federation emit, and dogfood evidence artefact generation from fixture CSVs.
This commit is contained in:
parent
d6f88cd70a
commit
277c2d05ca
12 changed files with 760 additions and 7 deletions
|
|
@ -14,9 +14,21 @@ from fin_hub.ingest.anthropic import parse_anthropic_billing_csv
|
|||
from fin_hub.ingest.cloud import parse_cloud_cost_csv
|
||||
from fin_hub.ingest.hosteurope import parse_hosteurope_csv
|
||||
from fin_hub.services.alerts import evaluate_budget_alerts
|
||||
from fin_hub.services.evaluate import evaluate_runway
|
||||
from fin_hub.services.evidence import write_runway_evidence
|
||||
from fin_hub.services.ledger import (
|
||||
default_ledger_path,
|
||||
import_csv,
|
||||
ledger_stats_json,
|
||||
set_opening_balance,
|
||||
)
|
||||
from fin_hub.services.runway import compute_runway
|
||||
|
||||
|
||||
def _ledger_path(args: argparse.Namespace) -> Path:
|
||||
return Path(args.ledger) if args.ledger else default_ledger_path()
|
||||
|
||||
|
||||
def _cmd_import_cloud(args: argparse.Namespace) -> int:
|
||||
rows = parse_cloud_cost_csv(Path(args.path))
|
||||
print(json.dumps([row.__dict__ for row in rows], indent=2, default=str))
|
||||
|
|
@ -82,6 +94,61 @@ def _cmd_ops_costs(args: argparse.Namespace) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def _cmd_ledger_import(args: argparse.Namespace) -> int:
|
||||
result = import_csv(
|
||||
Path(args.path),
|
||||
args.type,
|
||||
ledger_path=_ledger_path(args),
|
||||
force=args.force,
|
||||
)
|
||||
print(json.dumps(result.as_dict(), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_ledger_summary(args: argparse.Namespace) -> int:
|
||||
print(ledger_stats_json(ledger_path=_ledger_path(args)))
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_ledger_set_balance(args: argparse.Namespace) -> int:
|
||||
set_opening_balance(_ledger_path(args), args.balance, currency=args.currency)
|
||||
print(
|
||||
json.dumps(
|
||||
{"opening_balance": args.balance, "currency": args.currency},
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_evaluate(args: argparse.Namespace) -> int:
|
||||
report = evaluate_runway(
|
||||
ledger_path=_ledger_path(args),
|
||||
opening_balance=args.balance,
|
||||
alert_threshold_months=args.threshold,
|
||||
currency=args.currency,
|
||||
allocated=args.allocated,
|
||||
spent=args.spent,
|
||||
emit=args.emit,
|
||||
api_base=args.api_base,
|
||||
)
|
||||
print(json.dumps(report, indent=2, default=str))
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_evidence(args: argparse.Namespace) -> int:
|
||||
output = write_runway_evidence(
|
||||
ledger_path=_ledger_path(args),
|
||||
output_dir=Path(args.output_dir) if args.output_dir else None,
|
||||
opening_balance=args.balance,
|
||||
seed_fixtures=args.seed_fixtures,
|
||||
alert_threshold_months=args.threshold,
|
||||
currency=args.currency,
|
||||
)
|
||||
print(json.dumps({"written": str(output.resolve())}, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Fin Hub operator CLI")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
|
@ -113,6 +180,53 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
ops.add_argument("path")
|
||||
ops.set_defaults(func=_cmd_ops_costs)
|
||||
|
||||
ledger = sub.add_parser("ledger", help="Persistent SQLite cost ledger")
|
||||
ledger_sub = ledger.add_subparsers(dest="ledger_command", required=True)
|
||||
|
||||
ledger_import = ledger_sub.add_parser("import", help="Import a cost CSV into the ledger")
|
||||
ledger_import.add_argument("type", choices=["cloud", "anthropic", "hosteurope"])
|
||||
ledger_import.add_argument("path")
|
||||
ledger_import.add_argument("--ledger", help="Ledger database path (default: .fin-hub/ledger.db)")
|
||||
ledger_import.add_argument("--force", action="store_true", help="Re-import even if file unchanged")
|
||||
ledger_import.set_defaults(func=_cmd_ledger_import)
|
||||
|
||||
ledger_summary = ledger_sub.add_parser("summary", help="Monthly rollup summary from ledger")
|
||||
ledger_summary.add_argument("--ledger", help="Ledger database path (default: .fin-hub/ledger.db)")
|
||||
ledger_summary.set_defaults(func=_cmd_ledger_summary)
|
||||
|
||||
ledger_balance = ledger_sub.add_parser("set-balance", help="Store opening cash balance in ledger meta")
|
||||
ledger_balance.add_argument("balance", type=float)
|
||||
ledger_balance.add_argument("--currency", default="EUR")
|
||||
ledger_balance.add_argument("--ledger", help="Ledger database path (default: .fin-hub/ledger.db)")
|
||||
ledger_balance.set_defaults(func=_cmd_ledger_set_balance)
|
||||
|
||||
evaluate = sub.add_parser(
|
||||
"evaluate",
|
||||
help="Evaluate runway from ledger burns (cron/systemd friendly)",
|
||||
)
|
||||
evaluate.add_argument("--ledger", help="Ledger database path (default: .fin-hub/ledger.db)")
|
||||
evaluate.add_argument("--balance", type=float, help="Override stored opening balance")
|
||||
evaluate.add_argument("--threshold", type=float, default=3.0)
|
||||
evaluate.add_argument("--currency", default="EUR")
|
||||
evaluate.add_argument("--allocated", type=float)
|
||||
evaluate.add_argument("--spent", type=float)
|
||||
evaluate.add_argument("--emit", action="store_true", help="Emit fin→dev and fin→canon signals")
|
||||
evaluate.add_argument("--api-base")
|
||||
evaluate.set_defaults(func=_cmd_evaluate)
|
||||
|
||||
evidence = sub.add_parser("evidence", help="Write runway dogfood evidence artefact")
|
||||
evidence.add_argument("--ledger", help="Ledger database path (default: .fin-hub/ledger.db)")
|
||||
evidence.add_argument("--balance", type=float, default=12000.0)
|
||||
evidence.add_argument("--threshold", type=float, default=3.0)
|
||||
evidence.add_argument("--currency", default="EUR")
|
||||
evidence.add_argument("--output-dir", help="Output directory (default: docs/evidence)")
|
||||
evidence.add_argument(
|
||||
"--seed-fixtures",
|
||||
action="store_true",
|
||||
help="Import tests/fixtures CSVs before generating the report",
|
||||
)
|
||||
evidence.set_defaults(func=_cmd_evidence)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue