""" summarizer.py — glue logic: load → build context → call LLM → collect results Public API ---------- - summarize_grants(current: list[dict], past_winners: list[dict] | None = None, *, limit: int | None = None, include_context: bool = False) -> list[dict] Returns a list of dicts with keys: grant_id, title, summary_md, context(optional), source_path(optional) This file intentionally stays light; exporting and CLI live elsewhere. """ from __future__ import annotations from typing import Any, Dict, Iterable, List, Optional from pathlib import Path import logging from .context_builder import build_context from .llm_client import LLMClient logger = logging.getLogger(__name__) # ----------------------------- Core API --------------------------------------- def summarize_grants( current: List[Dict[str, Any]], past_winners: Optional[List[Dict[str, Any]]] = None, *, limit: Optional[int] = None, include_context: bool = False, client: Optional[LLMClient] = None, ) -> List[Dict[str, Any]]: """Summarize a batch of grants using an LLM. Parameters ---------- current : list of grant dicts (from data_loader.load_current_grants) past_winners : optional list of past winner dicts (may be empty) limit : if provided, process at most this many grants include_context : whether to include the raw context text in the result client : optional pre-initialized LLMClient """ client = client or LLMClient() # For this MVP, we pass the *same* past_winners list to every grant. # Later you can add filtering by theme if you want. results: List[Dict[str, Any]] = [] items = current[: limit or len(current)] for i, g in enumerate(items, 1): grant_id = g.get("id") or g.get("title") or g.get("name") or f"grant_{i}" title = g.get("title") or g.get("name") or g.get("competition_title") or "(untitled)" try: ctx = build_context(g, past_winners) summary = client.summarize(ctx) row = { "grant_id": grant_id, "title": title, "summary_md": summary, } if include_context: row["context"] = ctx if g.get("_path"): row["source_path"] = g["_path"] results.append(row) logger.info("Summarized: %s", title) except Exception as e: # keep going even if one fails logger.exception("Failed to summarize %s: %s", title, e) results.append({ "grant_id": grant_id, "title": title, "summary_md": f"Summary failed: {e}", }) return results # ----------------------------- Ad-hoc test ------------------------------------ if __name__ == "__main__": # Tiny smoke test using fake data logging.basicConfig(level=logging.INFO) fake_current = [ { "id": "demo-1", "title": "AI in Manufacturing", "sections": { "summary_raw": "Funding for AI-driven manufacturing improvements.", "scope_raw": "Projects should demonstrate measurable productivity gains.", }, "deadline": "2025-12-17", "funding_amount": "up to £1M", } ] fake_history = [ { "project_title": "Smart Factory Vision", "lead_org": "Acme Robotics", "award_amount": "£450,000", "competition": "Manufacturing AI 2023", "abstract": "Computer vision for automated QA on production lines.", } ] out = summarize_grants(fake_current, fake_history, limit=1, include_context=True) from pprint import pprint pprint(out)