grant-radar / src /analyzer /utils /citations.py
Riley Coleman
feat: add PDF & supporting materials integration to context
bfcc872
Raw
History Blame Contribute Delete
5.65 kB
"""
Citation and sourcing utilities to reduce hallucinations.
When presenting grant information, we cite the source data:
- Grant ID and title
- Specific fields (funding_max, deadline, etc.)
- Field source (from grant record or extracted)
This helps users verify information and trust the system.
"""
from typing import Dict, Any, List, Optional
from datetime import datetime
def cite_grant_fact(grant: Dict[str, Any], field: str, value: Any) -> str:
"""
Create a citation for a fact about a grant.
Args:
grant: The grant record dict
field: Field name (e.g., "funding_max", "close_date")
value: The value to cite
Returns:
Formatted citation like "£50,000 [Source: Grant ID #2315]"
"""
grant_id = grant.get("id") or grant.get("competition_id") or "unknown"
# Special formatting for currency
if field in ("funding_max", "funding_min", "total_pot") and isinstance(value, (int, float)):
return f"£{value:,.0f} [Source: Grant ID #{grant_id}]"
# Special formatting for dates
if field in ("close_date", "open_date", "deadline") and value:
return f"{value} [Source: Grant ID #{grant_id}]"
# Default formatting
if value is None:
return "Not specified"
return f"{value} [Source: Grant ID #{grant_id}]"
def format_grant_with_citations(grant: Dict[str, Any]) -> Dict[str, Any]:
"""
Format a grant record with citations on key fields.
Args:
grant: The grant record
Returns:
Dict with cited versions of key fields
"""
grant_id = grant.get("id") or grant.get("competition_id") or "unknown"
title = grant.get("title", "(untitled)")
cited = {
"id": grant_id,
"title": title,
"url": grant.get("url", ""),
}
# Cite monetary fields
if grant.get("funding_max"):
cited["funding_max"] = cite_grant_fact(grant, "funding_max", grant["funding_max"])
if grant.get("funding_min"):
cited["funding_min"] = cite_grant_fact(grant, "funding_min", grant["funding_min"])
if grant.get("total_pot"):
cited["total_pot"] = cite_grant_fact(grant, "total_pot", grant["total_pot"])
# Cite dates
if grant.get("close_date"):
cited["close_date"] = cite_grant_fact(grant, "close_date", grant["close_date"])
if grant.get("open_date"):
cited["open_date"] = cite_grant_fact(grant, "open_date", grant["open_date"])
return cited
def build_citation_summary(grant: Dict[str, Any]) -> str:
"""
Build a markdown summary of grant with citations.
Args:
grant: The grant record
Returns:
Markdown formatted summary with citations
"""
grant_id = grant.get("id") or grant.get("competition_id") or "unknown"
title = grant.get("title", "(untitled)")
url = grant.get("url", "")
lines = [
f"## {title}",
f"**Grant ID:** #{grant_id}",
"",
]
if url:
lines.append(f"**Official Link:** {url}")
lines.append("")
# Funding details with citations
if grant.get("funding_max") or grant.get("funding_min"):
min_fund = grant.get("funding_min")
max_fund = grant.get("funding_max")
if min_fund and max_fund:
funding_str = f"£{min_fund:,.0f} – £{max_fund:,.0f}"
elif max_fund:
funding_str = f"up to £{max_fund:,.0f}"
elif min_fund:
funding_str = f"from £{min_fund:,.0f}"
else:
funding_str = "See official page"
lines.append(f"**Funding per project:** {funding_str} [Source: Grant ID #{grant_id}]")
if grant.get("total_pot"):
lines.append(f"**Total available:** £{grant['total_pot']:,.0f} [Source: Grant ID #{grant_id}]")
lines.append("")
# Dates with citations
if grant.get("open_date") or grant.get("close_date"):
close = grant.get("close_date") or grant.get("deadline")
if close:
lines.append(f"**Deadline:** {close} [Source: Grant ID #{grant_id}]")
if grant.get("open_date"):
lines.append(f"**Opens:** {grant['open_date']} [Source: Grant ID #{grant_id}]")
lines.append("")
# Duration with citation
if grant.get("duration_min") or grant.get("duration_max"):
min_dur = grant.get("duration_min")
max_dur = grant.get("duration_max")
if min_dur and max_dur:
duration_str = f"{min_dur}{max_dur} months"
elif max_dur:
duration_str = f"up to {max_dur} months"
elif min_dur:
duration_str = f"from {min_dur} months"
else:
duration_str = "Variable"
lines.append(f"**Project duration:** {duration_str} [Source: Grant ID #{grant_id}]")
return "\n".join(lines)
def validate_fact_availability(grant: Dict[str, Any], fact_type: str) -> bool:
"""
Check if a fact exists in the grant data before citing it.
Args:
grant: The grant record
fact_type: Type of fact ("funding", "deadline", "duration", "scope", "eligibility")
Returns:
True if the fact is available to cite
Raises assertion/warning if fact is missing
"""
fact_fields = {
"funding": ["funding_max", "funding_min", "total_pot"],
"deadline": ["close_date", "deadline"],
"duration": ["duration_min", "duration_max"],
"scope": ["scope", "scope_raw", "sections"],
"eligibility": ["eligibility", "eligibility_raw"],
}
required_fields = fact_fields.get(fact_type, [])
for field in required_fields:
if grant.get(field):
return True
return False