Spaces:
Sleeping
Sleeping
| """ | |
| 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" | |