""" Instrument schema: what a grant instrument actually funds and requires. Source priority: 1) Gold profile (known instrument family) 2) Heuristic extract from regulation / announcement text 3) Conservative generic (never invents SMART multi-modules) """ from __future__ import annotations import re from typing import Any, Dict, List, Optional, Sequence from core.projects.instrument_profile import ( EUROGRANTY_GENERATION_RULES, KREDYT_EKO_GENERATION_RULES, KREDYT_TECH_GENERATION_RULES, STEP_GENERATION_RULES, get_instrument_profile, resolve_program_type, ) from core.grants.advisor_brief_extract import ( build_advisor_brief_from_text, extract_key_rules_from_text, ) SCHEMA_VERSION = 1 # --------------------------------------------------------------------------- # Gold profiles (curated — extend over time; extract fills gaps) # --------------------------------------------------------------------------- _GOLD: Dict[str, Dict[str, Any]] = { "EUROGRANTY": { "family": "EUROGRANTY", "instrument_kind": "preparatory_lump_sum", "label": "Granty na Eurogranty (FENG.02.12)", "funds_rnd": False, "funds_infrastructure": False, "funds_implementation": False, "budget_form": "lump_sum", "required_sections": [ "Informacje o projekcie i target Eurograntu (program UE, call, WP, termin)", "Wnioskodawca i kwalifikowalność (MŚP, de minimis, rok obrotowy, PKD)", "Opis działań przygotowawczych (partnerzy, pisanie wniosku, tłumaczenia, analizy)", "Rola w Eurograncie i partnerstwo (samodzielny / koordynator / członek)", "Zespół przygotowujący wniosek i kompetencje (powiązanie z realnym PKD firmy)", "Budżet ryczałtowy (jedna kwota wg roli + analizy specjalistyczne)", "Harmonogram powiązany z terminem konkursu UE", "Wskaźniki: złożenie 1 wniosku Eurogrant + wynik oceny >50%", "Zasady horyzontalne, DNSH, KIS, zrównoważony rozwój", "Załączniki i oświadczenia", ], "allowed_costs": [ "poszukiwanie partnerów i spotkania", "przygotowanie wniosku o Eurogrant", "tłumaczenia", "analizy specjalistyczne (uzasadnione)", "wynagrodzenia / podróże / doradztwo przygotowawcze", ], "forbidden_costs": [ "prace B+R / rozwój produktu", "infrastruktura B+R", "wdrożenie innowacji produkcyjnej", "moduły SMART (Cyfryzacja, Zazielenienie, Kompetencje)", "budowa fabryki / platformy jako przedmiot projektu", ], "obligatory_criteria": [ "kwalifikowalność wnioskodawcy (MŚP / org. badawcza, rok obrotowy ≥12 mies.)", "kwalifikowalność i zasadność projektu (konkretny target Eurograntu)", "budżet = dopuszczalna kwota ryczałtowa", "wskaźniki zgodne z formularzem (złożenie wniosku + wynik >50%)", ], "indicators": [ "złożenie 1 wniosku o Eurogrant po dacie wniosku do PARP", "wynik oceny Eurograntu >50% punktów/etapów", ], "required_company_fields": [ "nip", "company_name", "pkd_codes", "msp_status", "de_minimis", "closed_financial_year", "revenue_3y", "employment_fte", ], "required_project_fields": [ "eurogrant_programme", "eurogrant_call", "eurogrant_role", "specialist_analysis_yes_no", "lump_sum_amount_pln", ], "allow_smart_modules": False, "generation_rules": EUROGRANTY_GENERATION_RULES, }, "SMART": { "family": "SMART", "instrument_kind": "innovation_modules", "label": "Ścieżka SMART / FENG innowacje", "funds_rnd": True, "funds_infrastructure": True, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Informacje ogólne o projekcie", "Wnioskodawca i powiązania", "Zespół zarządzający i projektowy", "Analiza zapotrzebowania i rynku", "Innowacyjność (TRL) i znaczenie projektu", "Moduł B+R: Plan prac", "Moduł Wdrożenie Innowacji", "Zrównoważony rozwój i zasada DNSH", "Montaż finansowy i koszty", "Wskaźniki KPI i analiza ryzyka", "Załączniki i Oświadczenia", ], "allowed_costs": ["koszty kwalifikowalne wg regulaminu SMART/FENG"], "forbidden_costs": [], "obligatory_criteria": [ "innowacyjność i TRL", "kwalifikowalność wnioskodawcy", "DNSH / zasady horyzontalne", ], "indicators": ["wskaźniki produktu i rezultatu wg regulaminu"], "required_company_fields": [ "nip", "company_name", "pkd_codes", "msp_status", ], "required_project_fields": [ "project_description", "innovation_description", ], "allow_smart_modules": True, "generation_rules": "", }, "ARIMR": { "family": "ARIMR", "instrument_kind": "agricultural_investment", "label": "ARiMR / inwestycje rolnicze", "funds_rnd": False, "funds_infrastructure": True, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Opis inwestycji", "Dobrostan zwierząt / modernizacja", "Analiza opłacalności", "Środowisko i warunki", "Budżet i wkład własny", "Załączniki", ], "allowed_costs": ["inwestycje kwalifikowalne wg naboru ARiMR"], "forbidden_costs": ["prace B+R przemysłowe spoza zakresu naboru"], "obligatory_criteria": ["kwalifikowalność gospodarstwa / wnioskodawcy"], "indicators": [], "required_company_fields": ["nip", "company_name"], "required_project_fields": ["project_description"], "allow_smart_modules": False, "generation_rules": ( "Instrument rolniczy ARiMR — nie generuj modułów SMART B+R / Cyfryzacja." ), }, "ZUS_BHP": { "family": "ZUS_BHP", "instrument_kind": "workplace_safety", "label": "ZUS dofinansowanie BHP", "funds_rnd": False, "funds_infrastructure": False, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Opis projektu BHP", "Zakres działań i kosztów", "Harmonogram", "Efekty i wskaźniki BHP", "Załączniki", ], "allowed_costs": ["koszty kwalifikowalne BHP wg regulaminu ZUS"], "forbidden_costs": ["moduły SMART / Eurogranty"], "obligatory_criteria": ["kwalifikowalność płatnika składek"], "indicators": [], "required_company_fields": ["nip", "company_name"], "required_project_fields": ["project_description"], "allow_smart_modules": False, "generation_rules": "Instrument ZUS BHP — nie generuj struktury SMART ani Eurograntów.", "legal_references": [], }, "BGK": { "family": "BGK", "instrument_kind": "financial_instrument", "label": "BGK — instrumenty finansowe / gwarancje / preferencyjne finansowanie", "funds_rnd": False, "funds_infrastructure": False, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Informacje o wniosku i produkcie BGK", "Wnioskodawca i zdolność formalna", "Cel i zakres finansowania", "Zabezpieczenia / warunki produktu (jeśli dotyczy)", "Plan spłaty / wykorzystania środków", "Załączniki i oświadczenia", ], "allowed_costs": ["zgodnie z regulaminem konkretnego produktu BGK"], "forbidden_costs": [ "moduły SMART B+R", "budżet ryczałtowy Eurograntów (inny instrument)", ], "obligatory_criteria": [ "kwalifikowalność wnioskodawcy wg produktu BGK", "zgodność celu z regulaminem produktu", ], "indicators": [], "required_company_fields": ["nip", "company_name", "msp_status"], "required_project_fields": ["project_description"], "allow_smart_modules": False, "generation_rules": ( "Instrument BGK (gwarancja/pożyczka/preferencyjne finansowanie) — " "NIE generuj modułów SMART ani struktury Grantów na Eurogranty. " "Opieraj się na regulaminie konkretnego produktu." ), "legal_references": [], }, "NFOSIGW": { "family": "NFOSIGW", "instrument_kind": "environmental_investment", "label": "NFOŚiGW — ochrona środowiska / efektywność energetyczna", "funds_rnd": False, "funds_infrastructure": True, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Opis przedsięwzięcia i efekt ekologiczny", "Wnioskodawca i kwalifikowalność", "Zakres rzeczowy i techniczny", "Efekt środowiskowy / energetyczny i wskaźniki", "Budżet i źródła finansowania", "Harmonogram", "DNSH / zasady horyzontalne (jeśli wymagane)", "Załączniki", ], "allowed_costs": ["koszty kwalifikowalne wg naboru NFOŚiGW"], "forbidden_costs": ["moduły SMART niezwiązane z naborem", "koszty niekwalifikowalne z regulaminu"], "obligatory_criteria": [ "kwalifikowalność przedsięwzięcia środowiskowego", "wymierny efekt ekologiczny/energetyczny", ], "indicators": ["wskaźniki efektu ekologicznego / OZE / EE wg naboru"], "required_company_fields": ["nip", "company_name"], "required_project_fields": ["project_description"], "allow_smart_modules": False, "generation_rules": ( "Instrument NFOŚiGW — skup się na efekcie środowiskowym/energetycznym i kosztach " "kwalifikowalnych naboru. NIE wstawiaj domyślnych modułów SMART B+R." ), "legal_references": [], }, "REGIONAL": { "family": "REGIONAL", "instrument_kind": "regional_fes", "label": "Programy regionalne / FE / RPO", "funds_rnd": False, "funds_infrastructure": True, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Informacje o projekcie i naborze regionalnym", "Wnioskodawca i kwalifikowalność (region, status)", "Opis projektu i uzasadnienie", "Zgodność z celami RPO / FE i kryteriami naboru", "Budżet i wkład własny", "Harmonogram", "Wskaźniki produktu i rezultatu", "Zasady horyzontalne / DNSH (jeśli dotyczy)", "Załączniki", ], "allowed_costs": ["koszty kwalifikowalne wg regulaminu naboru regionalnego"], "forbidden_costs": ["koszty poza katalogiem naboru", "ślepe kopiowanie modułów SMART"], "obligatory_criteria": [ "lokalizacja / kwalifikowalność terytorialna", "zgodność z typem projektu w naborze", ], "indicators": ["wskaźniki z karty naboru / regulaminu"], "required_company_fields": ["nip", "company_name", "msp_status"], "required_project_fields": ["project_description"], "allow_smart_modules": False, "generation_rules": ( "Nabór regionalny (FE/RPO) — sekcje i koszty WYŁĄCZNIE z regulaminu tego naboru. " "NIE zakładaj struktury Ścieżki SMART ani Eurograntów." ), "legal_references": [], }, # STEP (Strategic Technologies for Europe Platform) / dual-use — mid-term priority. # Must NOT inherit Ścieżka SMART multi-module structure (B+R / Cyfryzacja / Zazielenienie…). "STEP": { "family": "STEP", "instrument_kind": "critical_tech_dual_use", "label": "STEP / dual-use — technologie krytyczne", "funds_rnd": True, "funds_infrastructure": True, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Informacje o projekcie i naborze STEP", "Wnioskodawca i kwalifikowalność", "Opis technologii krytycznej / dual-use (jeśli dotyczy)", "Innowacyjność i wkład w strategiczną niezależność UE", "Zakres rzeczowy, plan prac i wdrożenie", "Budżet i montaż finansowy (koszty kwalifikowalne)", "Harmonogram", "Wskaźniki produktu i rezultatu", "DNSH / zasady horyzontalne / bezpieczeństwo (dual-use)", "Załączniki i oświadczenia", ], "allowed_costs": [ "koszty kwalifikowalne wg regulaminu naboru STEP / dual-use", "prace B+R i wdrożenie technologii krytycznej (jeśli nabór dopuszcza)", "infrastruktura niezbędna do realizacji projektu STEP", ], "forbidden_costs": [ "moduły SMART (Cyfryzacja, Zazielenienie, Kompetencje, Internacjonalizacja) " "jako domyślna struktura wniosku", "ślepe kopiowanie szablonu Ścieżki SMART FENG.01", "koszty poza katalogiem naboru STEP", ], "obligatory_criteria": [ "zgodność z zakresem STEP (technologie cyfrowe / biotech / czyste technologie)", "wkład w strategiczną niezależność UE lub dual-use (jeśli dotyczy)", "kwalifikowalność wnioskodawcy wg naboru", ], "indicators": ["wskaźniki produktu i rezultatu wg karty naboru STEP"], "required_company_fields": [ "nip", "company_name", "pkd_codes", "msp_status", ], "required_project_fields": [ "project_description", "critical_technology_area", "innovation_description", ], "allow_smart_modules": False, "generation_rules": STEP_GENERATION_RULES, "legal_references": [], }, # Kredyt Ekologiczny (BGK / continuous) — debt + ecological premium; not SMART grant modules. "KREDYT_EKO": { "family": "KREDYT_EKO", "instrument_kind": "green_credit", "label": "Kredyt Ekologiczny — finansowanie dłużne proekologiczne", "funds_rnd": False, "funds_infrastructure": True, "funds_implementation": True, "budget_form": "loan", "required_sections": [ "Informacje o kredycie / programie (BGK Kredyt Ekologiczny)", "Wnioskodawca i zdolność kredytowa", "Cel ekologiczny inwestycji i kwalifikowalność", "Zakres rzeczowy i koszty kwalifikowalne", "Montaż finansowy (kredyt + premia / wkład własny)", "Harmonogram realizacji i spłaty", "Efekty środowiskowe / wskaźniki EE / OZE", "Zabezpieczenia i warunki bankowe", "Załączniki i oświadczenia", ], "allowed_costs": [ "inwestycje proekologiczne wg katalogu programu Kredyt Ekologiczny", "modernizacja energetyczna / OZE (jeśli program dopuszcza)", "koszty kwalifikowalne powiązane z celem ekologicznym", ], "forbidden_costs": [ "moduły SMART (B+R, Cyfryzacja, Zazielenienie, Kompetencje) jako struktura wniosku grantowego", "ślepe kopiowanie szablonu Ścieżki SMART FENG.01", "koszty niekwalifikowalne wg regulaminu kredytu", "traktowanie produktu jak czystej dotacji inwestycyjnej bez komponentu dłużnego", ], "obligatory_criteria": [ "cel proekologiczny zgodny z programem", "zdolność kredytowa / zabezpieczenia", "zgodność kosztów z katalogiem Kredytu Ekologicznego", ], "indicators": ["efekty środowiskowe / oszczędność energii / OZE wg programu"], "required_company_fields": ["nip", "company_name", "msp_status"], "required_project_fields": [ "project_description", "ecological_objective", "loan_amount_pln", ], "allow_smart_modules": False, "generation_rules": KREDYT_EKO_GENERATION_RULES, "legal_references": [], }, # Kredyt Technologiczny (BGK / continuous) — debt + tech premium; not SMART multi-modules. "KREDYT_TECH": { "family": "KREDYT_TECH", "instrument_kind": "tech_credit", "label": "Kredyt Technologiczny — finansowanie dłużne na innowacje technologiczne", "funds_rnd": False, "funds_infrastructure": True, "funds_implementation": True, "budget_form": "loan", "required_sections": [ "Informacje o kredycie / programie (BGK Kredyt Technologiczny)", "Wnioskodawca i zdolność kredytowa", "Opis technologii / innowacji będącej przedmiotem finansowania", "Zakres rzeczowy i koszty kwalifikowalne", "Montaż finansowy (kredyt + premia technologiczna / wkład własny)", "Harmonogram realizacji i spłaty", "Efekty wdrożenia / wskaźniki technologiczne", "Zabezpieczenia i warunki bankowe", "Załączniki i oświadczenia", ], "allowed_costs": [ "zakup i wdrożenie nowej technologii wg katalogu Kredytu Technologicznego", "koszty kwalifikowalne powiązane z innowacją technologiczną (jeśli program dopuszcza)", "szkolenia operatorów powiązane z zakupioną technologią (jeśli dopuszczone)", ], "forbidden_costs": [ "moduły SMART (B+R, Cyfryzacja, Zazielenienie, Kompetencje) jako domyślna struktura wniosku", "ślepe kopiowanie szablonu Ścieżki SMART FENG.01", "koszty poza katalogiem regulaminu kredytu", "traktowanie produktu jak pure grant B+R bez komponentu dłużnego", ], "obligatory_criteria": [ "przedmiot = wdrożenie nowej technologii zgodne z programem", "zdolność kredytowa / zabezpieczenia", "zgodność kosztów z katalogiem Kredytu Technologicznego", ], "indicators": ["wskaźniki wdrożenia technologii / premii technologicznej wg programu"], "required_company_fields": ["nip", "company_name", "msp_status", "pkd_codes"], "required_project_fields": [ "project_description", "technology_description", "loan_amount_pln", ], "allow_smart_modules": False, "generation_rules": KREDYT_TECH_GENERATION_RULES, "legal_references": [], }, # FST — Just Transition Fund regional track (higher intensity); not SMART multi-module. "FST": { "family": "FST", "instrument_kind": "just_transition_regional", "label": "Fundusz Sprawiedliwej Transformacji (FST) — nabór regionalny", "funds_rnd": True, "funds_infrastructure": True, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Informacje o naborze FST / operatorze regionalnym", "Wnioskodawca i kwalifikowalność terytorialna FST", "Opis projektu i wkład w transformację regionu", "Zakres rzeczowy i plan prac", "Budżet i koszty kwalifikowalne", "Harmonogram", "Wskaźniki produktu i rezultatu FST", "DNSH / zasady horyzontalne", "Załączniki", ], "allowed_costs": ["koszty kwalifikowalne wg regulaminu naboru FST"], "forbidden_costs": [ "domyślne moduły SMART jako struktura wniosku", "koszty poza katalogiem naboru FST", ], "obligatory_criteria": [ "lokalizacja w obszarze FST", "zgodność z celami sprawiedliwej transformacji", ], "indicators": ["wskaźniki FST wg karty naboru"], "required_company_fields": ["nip", "company_name", "msp_status", "region"], "required_project_fields": ["project_description"], "allow_smart_modules": False, "generation_rules": ( "Nabór FST — sekcje i koszty WYŁĄCZNIE z regulaminu operatora regionalnego. " "NIE zakładaj struktury multi-modułowej Ścieżki SMART." ), "legal_references": [], }, # Startup Booster / First Team style PARP-NCBR tracks. "FIRST_TEAM": { "family": "FIRST_TEAM", "instrument_kind": "startup_team_grant", "label": "First Team / Startup Booster — zespół i komercjalizacja", "funds_rnd": True, "funds_infrastructure": False, "funds_implementation": True, "budget_form": "eligible_costs", "required_sections": [ "Informacje o naborze i operatorze", "Zespół / founderzy i kwalifikowalność", "Opis rozwiązania i modelu biznesowego", "Plan prac / milestones", "Budżet", "Harmonogram", "Wskaźniki (traction / IP / sprzedaż)", "Załączniki", ], "allowed_costs": ["koszty kwalifikowalne wg regulaminu naboru"], "forbidden_costs": [ "kopiowanie multi-modułów SMART bez podstawy w regulaminie", ], "obligatory_criteria": ["kwalifikowalność zespołu / MŚP wg naboru"], "indicators": ["wskaźniki naboru (traction, IP, zatrudnienie)"], "required_company_fields": ["nip", "company_name", "msp_status"], "required_project_fields": ["project_description", "innovation_description"], "allow_smart_modules": False, "generation_rules": ( "First Team / Startup Booster — NIE używaj struktury SMART multi-module. " "Trzymaj się sekcji i kosztów z regulaminu naboru." ), "legal_references": [], }, # Direct EU Horizon / EIC prep schema (not SMART PL). "HORIZON_PREP": { "family": "HORIZON_PREP", "instrument_kind": "direct_eu_prep", "label": "Horizon Europe / EIC — przygotowanie wniosku (Direct EU)", "funds_rnd": True, "funds_infrastructure": False, "funds_implementation": False, "budget_form": "eligible_costs", "required_sections": [ "Call / Work Programme / deadline", "Excellence — cele i podejście", "Impact — ścieżka wpływu", "Implementation — work packages / konsorcjum", "Budżet HE (jeśli wymagany na etapie prep)", "TRL i ryzyko", "Załączniki (concept note / short proposal)", ], "allowed_costs": ["koszty przygotowawcze wg naboru / Eurograntu jeśli dotyczy"], "forbidden_costs": [ "struktura Ścieżki SMART FENG.01 jako domyślny szablon HE", "moduły Cyfryzacja/Zazielenienie SMART bez podstawy w callu UE", ], "obligatory_criteria": ["zgodność z call / topic HE lub EIC"], "indicators": ["TRL, impact KPIs wg callu"], "required_company_fields": ["nip", "company_name"], "required_project_fields": [ "project_description", "eurogrant_programme", "eurogrant_call", ], "allow_smart_modules": False, "generation_rules": ( "Direct EU / Horizon prep. NIGDY nie mapuj na multi-moduł SMART PL. " "Użyj sekcji HE/EIC lub Eurogranty prep — nie FENG.01." ), "legal_references": [], }, } def list_gold_families() -> List[str]: """Canonical gold family ids (excluding GENERIC empty fallback).""" return sorted(k for k in _GOLD.keys() if k and k != "GENERIC") def empty_schema(*, reason: str = "unknown") -> Dict[str, Any]: return { "version": SCHEMA_VERSION, "family": "GENERIC", "instrument_kind": "unknown", "label": "Nieznany instrument", "funds_rnd": False, "funds_infrastructure": False, "funds_implementation": False, "budget_form": "unknown", "required_sections": [], "allowed_costs": [], "forbidden_costs": [], "obligatory_criteria": [], "indicators": [], "key_rules": [], "required_company_fields": ["nip", "company_name"], "required_project_fields": ["project_description"], "allow_smart_modules": False, "generation_rules": ( "Instrument nie w pełni rozpoznany. NIE zakładaj modułów SMART. " "Opieraj się wyłącznie na regułach z regulaminu w kontekście. " f"(reason={reason})" ), "source": reason, "confidence": 0.0, } def _merge_schema(base: Dict[str, Any], overlay: Dict[str, Any]) -> Dict[str, Any]: out = dict(base) for k, v in (overlay or {}).items(): if v is None or v == "" or v == []: continue if isinstance(v, list) and isinstance(out.get(k), list): # prefer non-empty overlay lists out[k] = list(v) if v else out[k] else: out[k] = v return out _LEGAL_REF_PATTERNS = [ # Dz.U. / Dziennik Ustaw re.compile( r"(?:Dz\.\s*U\.|Dziennik\s+Ustaw)[^\n]{0,80}?(?:poz\.\s*\d+|\d{4}\s*(?:r\.?|rok)?[^\n]{0,40})", re.IGNORECASE, ), # CELEX re.compile(r"\bCELEX[:\s]*[0-9A-Z]{5,20}\b", re.IGNORECASE), # ELI / EUR-Lex URLs re.compile( r"https?://(?:eur-lex\.europa\.eu|data\.europa\.eu/eli)[^\s\)\"']{5,180}", re.IGNORECASE, ), # ISAP / sejm re.compile( r"https?://isap\.sejm\.gov\.pl[^\s\)\"']{5,180}", re.IGNORECASE, ), # art. / § with act hint re.compile( r"(?:art\.?\s*\d+[a-z]?(?:\s*ust\.?\s*\d+)?[^\n]{0,40}?(?:ustawy|rozporządzenia|regulaminu))", re.IGNORECASE, ), ] def extract_legal_references_from_text(text: str, *, limit: int = 15) -> List[Dict[str, str]]: """Pull legal act citations / ISAP / EUR-Lex URLs from regulation text.""" if not text or len(text.strip()) < 20: return [] found: List[Dict[str, str]] = [] seen: set[str] = set() for pat in _LEGAL_REF_PATTERNS: for m in pat.finditer(text): raw = re.sub(r"\s+", " ", m.group(0).strip())[:240] key = raw.lower() if len(raw) < 8 or key in seen: continue seen.add(key) role = "citation" low = raw.lower() if "isap.sejm" in low: role = "isap" elif "eur-lex" in low or "eli/" in low or "celex" in low: role = "eurlex" elif "dz.u" in low or "dziennik" in low: role = "dz_u" entry: Dict[str, str] = {"title": raw, "role": role} if raw.startswith("http"): entry["url"] = raw found.append(entry) if len(found) >= limit: return found return found def extract_instrument_hints_from_text(text: str) -> Dict[str, Any]: """Heuristic enrichment from regulation/announcement text.""" if not text or len(text.strip()) < 40: return {} t = text low = t.lower() out: Dict[str, Any] = {"key_rules": extract_key_rules_from_text(t)[:15]} brief = build_advisor_brief_from_text(t) if brief.get("required_sections"): out["required_sections_extracted"] = list(brief["required_sections"])[:20] if brief.get("required_attachments"): out["required_attachments"] = list(brief["required_attachments"])[:15] if brief.get("attention_points"): out["attention_points"] = list(brief["attention_points"])[:10] legal = extract_legal_references_from_text(t) if legal: out["legal_references"] = legal if any(k in low for k in ("ryczałt", "ryczalt", "kwota ryczałtowa", "lump sum")): out["budget_form"] = "lump_sum" elif any(k in low for k in ("koszty kwalifikowalne", "refundac", "dofinansowanie kosztów")): out["budget_form"] = "eligible_costs" if any(k in low for k in ("eurogrant", "horyzont europa", "horizon europe", "digital europe")): out["instrument_kind_hint"] = "preparatory_lump_sum" if any(k in low for k in ("moduł b+r", "ścieżka smart", "sciezka smart", "trl")): out["instrument_kind_hint"] = out.get("instrument_kind_hint") or "innovation_modules" # Cost allow/forbid snippets allowed: List[str] = [] forbidden: List[str] = [] for m in re.finditer( r"koszty?\s+kwalifikowalne[^\n.]{0,120}", t, re.I, ): allowed.append(m.group(0).strip()[:200]) for m in re.finditer( r"(?:koszty?\s+niekwalifikowalne|nie\s+kwalifikuje\s+się)[^\n.]{0,120}", t, re.I, ): forbidden.append(m.group(0).strip()[:200]) if allowed: out["allowed_costs"] = allowed[:10] if forbidden: out["forbidden_costs"] = forbidden[:10] return out def build_instrument_schema( *, program_type: str = "", program_name: str = "", grant_id: str = "", description: str = "", regulation_text: str = "", advisor_brief: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """ Build full instrument_schema for a project/nabor. Never defaults to allow_smart_modules=True unless family is SMART. """ family = resolve_program_type( program_type=program_type, program_name=program_name, grant_id=grant_id, description=description or regulation_text[:1500], ) gold = _GOLD.get(family) if gold: schema = { "version": SCHEMA_VERSION, "family": family, "source": "gold_profile", "confidence": 0.85, **{k: v for k, v in gold.items()}, } else: schema = empty_schema(reason="no_gold_profile") schema["family"] = family # Pull light defaults from instrument_profile if any prof = get_instrument_profile(family) if prof.get("required_sections"): schema["required_sections"] = list(prof["required_sections"]) schema["confidence"] = 0.45 schema["source"] = "instrument_profile" if prof.get("generation_rules"): schema["generation_rules"] = prof["generation_rules"] schema["instrument_kind"] = prof.get("instrument_kind") or schema["instrument_kind"] schema["label"] = prof.get("label") or family # Text / brief enrichment hints = extract_instrument_hints_from_text(regulation_text or "") if hints.get("key_rules"): schema["key_rules"] = hints["key_rules"] if hints.get("budget_form"): schema["budget_form"] = hints["budget_form"] if hints.get("allowed_costs") and not schema.get("allowed_costs"): schema["allowed_costs"] = hints["allowed_costs"] if hints.get("forbidden_costs"): existing = list(schema.get("forbidden_costs") or []) schema["forbidden_costs"] = (existing + list(hints["forbidden_costs"]))[:15] if hints.get("legal_references"): existing_legal = list(schema.get("legal_references") or []) # merge by title/url seen_l = { (str(x.get("url") or x.get("title") or "")).lower() for x in existing_legal if isinstance(x, dict) } for ref in hints["legal_references"]: if not isinstance(ref, dict): continue key = str(ref.get("url") or ref.get("title") or "").lower() if key and key not in seen_l: existing_legal.append(ref) seen_l.add(key) schema["legal_references"] = existing_legal[:15] # Prefer gold/profile sections; if empty, use extracted headers (still no SMART invent) if not schema.get("required_sections") and hints.get("required_sections_extracted"): schema["required_sections"] = hints["required_sections_extracted"] schema["confidence"] = max(float(schema.get("confidence") or 0), 0.4) schema["source"] = f"{schema.get('source')}+text_sections" if advisor_brief and isinstance(advisor_brief, dict): if advisor_brief.get("required_sections") and not schema.get("required_sections"): schema["required_sections"] = list(advisor_brief["required_sections"])[:20] if advisor_brief.get("key_rules"): schema["key_rules"] = list( dict.fromkeys( list(schema.get("key_rules") or []) + list(advisor_brief.get("key_rules") or []) ) )[:20] if advisor_brief.get("attention_points"): schema["attention_points"] = list(advisor_brief["attention_points"])[:10] # Hard safety: only SMART family may use smart modules if family != "SMART": schema["allow_smart_modules"] = False else: schema["allow_smart_modules"] = True schema["family"] = family schema["version"] = SCHEMA_VERSION schema["program_name"] = program_name or "" schema["grant_id"] = grant_id or "" return schema def schema_seed_sections(schema: Optional[Dict[str, Any]]) -> List[str]: """Section titles to seed for a project.""" if not schema: return [] return [str(s).strip() for s in (schema.get("required_sections") or []) if str(s).strip()] def schema_generation_prompt(schema: Optional[Dict[str, Any]]) -> str: if not schema: return "" rules = (schema.get("generation_rules") or "").strip() lines = [ f"=== INSTRUMENT SCHEMA v{schema.get('version')} | family={schema.get('family')} " f"| kind={schema.get('instrument_kind')} | budget={schema.get('budget_form')} ===", f"Label: {schema.get('label')}", ] if schema.get("allowed_costs"): lines.append("Koszty dopuszczalne (kierunkowo):") lines.extend(f"- {c}" for c in schema["allowed_costs"][:12]) if schema.get("forbidden_costs"): lines.append("ZAKAZANE / nie finansowane:") lines.extend(f"- {c}" for c in schema["forbidden_costs"][:12]) if schema.get("obligatory_criteria"): lines.append("Kryteria obligatoryjne:") lines.extend(f"- {c}" for c in schema["obligatory_criteria"][:10]) if schema.get("indicators"): lines.append("Wskaźniki oczekiwane:") lines.extend(f"- {c}" for c in schema["indicators"][:8]) if rules: lines.append(rules) if not schema.get("allow_smart_modules"): lines.append( "ZAKAZ: nie twórz modułów SMART (B+R, Wdrożenie, Infrastruktura B+R, " "Cyfryzacja, Zazielenienie, Kompetencje), chyba że wprost w required_sections." ) return "\n".join(lines) def schema_allows_smart_fallback(schema: Optional[Dict[str, Any]]) -> bool: """True only for SMART family with explicit allow.""" if not schema: return False return bool(schema.get("allow_smart_modules")) and schema.get("family") == "SMART"