Spaces:
Running
Running
| """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 | |
| ] | |