grant-radar / src /analyzer /summarizer.py
Riley Coleman
Add working grant analyst demo setup
59ebe66
Raw
History Blame Contribute Delete
3.79 kB
"""
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)