""" run.py ====== CLI entry point. Demonstrates the full pipeline and prints a QA summary. Usage: # Extraction + validation only (no API key needed): python run.py path/to/Master_Sheet.xlsx --no-llm # Full run with narratives (requires GOOGLE_API_KEY): python run.py path/to/Master_Sheet.xlsx --developer "Green Edge Consortium" The mini-grid capacities are an external input (developer-supplied, not in the survey data). Pass them with --solar-pv / --battery / --annual-consumption, or fill them in afterwards on the returned object before rendering. """ from __future__ import annotations import argparse from pue_report_agent import extract_master_sheet, validate, recompute from pue_report_agent.schema import EnergySystemSpec def main() -> None: ap = argparse.ArgumentParser(description="PUE report builder") ap.add_argument("master_sheet") ap.add_argument("--developer", default="the mini-grid developer") ap.add_argument("--no-llm", action="store_true", help="skip narrative generation (no API key required)") ap.add_argument("--solar-pv", default="") ap.add_argument("--battery", default="") ap.add_argument("--annual-consumption", default="") ap.add_argument("--date", default="", help="report date for the title page") ap.add_argument("--docx-out", default="", help="render a template-faithful .docx to this path") ap.add_argument("--json-out", default="", help="write the full PUEReportData JSON to this path") ap.add_argument("--developer-logo", default="", help="optional path to developer logo image " "(placed on the left of the page header)") ap.add_argument("--dreef-logo", default="", help="optional path to DREEF logo image " "(placed on the right of the page header)") args = ap.parse_args() # 1. Deterministic extraction data = extract_master_sheet(args.master_sheet) # 2. Recompute every total from line items (never trust a sheet total) data = recompute(data) # 3. External developer-supplied capacity overrides (optional). Merge into # the already-extracted minigrid so we don't discard the distribution # metrics / inverter that extraction pulled from Key Findings. if args.solar_pv: data.minigrid.solar_pv = args.solar_pv if args.battery: data.minigrid.battery_storage = args.battery if args.annual_consumption: data.minigrid.annual_consumption = args.annual_consumption # 4. Validation / reconciliation data = validate(data) # 5. Narratives (optional). All narratives use Gemini. if not args.no_llm: from pue_report_agent import generate_narratives if generate_narratives is None: print("[warn] langchain not installed; skipping narratives.") else: data = generate_narratives(data) # 6. QA summary _print_summary(data) # 7. Render the template-faithful Word document if args.docx_out: from pue_report_agent.render import render render(data, args.docx_out, developer=args.developer, report_date=args.date, developer_logo_path=args.developer_logo or None, dreef_logo_path=args.dreef_logo or None) print(f"\nWrote {args.docx_out}") if args.json_out: with open(args.json_out, "w") as fh: fh.write(data.model_dump_json(indent=2)) print(f"Wrote {args.json_out}") def _print_summary(data) -> None: c = data.community print("=" * 64) print(f" {c.name} ({c.lga}, {c.state})") print("=" * 64) print(f" Interviewed: {data.interviews.total} " f"(farmers {data.interviews.farmers}, processors " f"{data.interviews.agroprocessors}, SME {data.interviews.sme}, " f"mobility {data.interviews.mobility})") print(f" Farmers: {data.farmers.male}M / {data.farmers.female}F, " f"{len(data.farmers.crops)} crops") print(f" Processors: {data.processors.male}M / {data.processors.female}F, " f"{len(data.processors.machinery)} machines on record") print(f" SME types: {len(data.sme.types)} Mobility: {data.mobility.total}") print(f" Pilot energy: {data.equipment_pilot.projection_total_kwh} kWh " f"ScaleUp: {data.equipment_scaleup.projection_total_kwh} kWh") print(f" Budget grand total: ₦{(data.budget.grand_total or 0):,.0f}") print(f" Financial models: {[m.model_name for m in data.financial_models]}") print() if data.warnings: print(f" ⚠ {len(data.warnings)} item(s) need review:") for w in data.warnings: print(f" • {w}") else: print(" ✓ no warnings") if __name__ == "__main__": main()