grantforge-api / backend /core /grants /versioning.py
GrantForge Bot
Deploy sha-93ab5d396350b3e6db35d3bd315ec00724aa6c32 — source build (no GHCR)
5ecb84c
Raw
History Blame Contribute Delete
4.28 kB
"""Wersjonowanie zmian regulaminów i terminów (Warstwa 1)."""
from __future__ import annotations
import os
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from core.grants.models import GrantVersion, HumanVerificationItem
TRACKED_FIELDS = ("deadline", "regulation_url", "precise_regulation_url", "status", "url", "official_page_url")
VERIFICATION_FIELDS = frozenset({"deadline", "regulation_url", "precise_regulation_url", "status"})
def _norm(val: Any) -> str:
if val is None:
return ""
return str(val).strip()
def detect_field_changes(
source_id: str,
old_item: Dict[str, Any],
new_item: Dict[str, Any],
) -> List[Dict[str, Any]]:
changes: List[Dict[str, Any]] = []
for field in TRACKED_FIELDS:
old_v = _norm(old_item.get(field))
new_v = _norm(new_item.get(field))
if old_v == new_v:
continue
if not old_v and not new_v:
continue
summary = _summarize_change(field, old_v, new_v)
changes.append(
{
"grant_source_id": source_id,
"field_name": field,
"old_value": old_v,
"new_value": new_v,
"change_summary": summary,
"requires_verification": field in VERIFICATION_FIELDS,
}
)
return changes
def _summarize_change(field: str, old_v: str, new_v: str) -> str:
labels = {
"deadline": "Termin naboru",
"regulation_url": "Regulamin",
"precise_regulation_url": "Regulamin (precyzyjny)",
"status": "Status",
"url": "Strona programu",
"official_page_url": "Oficjalna strona",
}
label = labels.get(field, field)
if not old_v:
return f"{label}: ustawiono → {new_v[:120]}"
if not new_v:
return f"{label}: usunięto (było: {old_v[:80]})"
return f"{label}: {old_v[:60]}{new_v[:60]}"
def record_changes(
db: Session,
changes: List[Dict[str, Any]],
*,
source: str = "import",
) -> Dict[str, int]:
"""Zapisuje GrantVersion + opcjonalnie HumanVerificationQueue."""
if os.environ.get("HUMAN_VERIF_QUEUE", "true").lower() == "false":
enqueue_hvq = False
else:
enqueue_hvq = True
versions = 0
queued = 0
for ch in changes:
db.add(
GrantVersion(
grant_source_id=ch["grant_source_id"],
field_name=ch["field_name"],
old_value=ch.get("old_value"),
new_value=ch.get("new_value"),
change_summary=ch.get("change_summary"),
source=source,
requires_verification=ch.get("requires_verification", False),
)
)
versions += 1
if enqueue_hvq and ch.get("requires_verification"):
db.add(
HumanVerificationItem(
grant_source_id=ch["grant_source_id"],
change_type="field_update",
field_name=ch["field_name"],
old_value=ch.get("old_value"),
new_value=ch.get("new_value"),
summary=ch.get("change_summary"),
priority=8 if ch["field_name"] == "deadline" else 6,
)
)
queued += 1
return {"versions_recorded": versions, "verification_queued": queued}
def get_recent_versions(
db: Session,
*,
grant_source_id: Optional[str] = None,
limit: int = 50,
) -> List[Dict[str, Any]]:
q = db.query(GrantVersion).order_by(GrantVersion.detected_at.desc())
if grant_source_id:
q = q.filter(GrantVersion.grant_source_id == grant_source_id)
rows = q.limit(limit).all()
return [
{
"id": r.id,
"grant_source_id": r.grant_source_id,
"field_name": r.field_name,
"old_value": r.old_value,
"new_value": r.new_value,
"change_summary": r.change_summary,
"detected_at": r.detected_at.isoformat() if r.detected_at else None,
"source": r.source,
"requires_verification": r.requires_verification,
}
for r in rows
]