Spaces:
Sleeping
Sleeping
| """ | |
| Direct EU prep helpers (PLAN_ECOSYSTEM F6) — pure, no network. | |
| Product surface for Horizon Europe / EIC preparation checklists and grant | |
| classification. Must never map Direct EU prep to SMART PL multi-module structure. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Dict, List, Optional | |
| # Canonical strategy path id (core.strategy.recommend.PATH_DIRECT_EU) | |
| PATH_DIRECT_EU = "direct_eu_prep" | |
| # Advisor checklist id (not a submission form) | |
| CHECKLIST_ID = "horizon_prep" | |
| _HORIZON_MARKERS = ( | |
| "horizon europe", | |
| "horyzont europa", | |
| "horyzont", | |
| "horizon", | |
| "eic", | |
| "eic accelerator", | |
| "pathfinder", | |
| "transition", | |
| "digital europe", | |
| "heu", | |
| "work programme", | |
| "funding & tenders", | |
| "funding and tenders", | |
| ) | |
| # Strong Direct EU program families (explicit — never SMART modules) | |
| _DIRECT_EU_FAMILIES = frozenset( | |
| { | |
| "HORIZON_PREP", | |
| "HORIZON", | |
| "EIC", | |
| "EUROGRANTY", | |
| "DIRECT_EU", | |
| } | |
| ) | |
| _SMART_PL_MARKERS = ( | |
| "ścieżka smart", | |
| "sciezka smart", | |
| "feng.01", | |
| "moduł b+r smart", | |
| "modul b+r smart", | |
| ) | |
| _STRONG_EU_MARKERS = ( | |
| "horizon europe", | |
| "horyzont europa", | |
| "eic accelerator", | |
| "digital europe", | |
| ) | |
| _PREP_CHECKLIST = [ | |
| "Ustal call / Work Programme i deadline w Funding & Tenders Portal.", | |
| "Sprawdź TRL i typ działania (RIA/IA/CSA / EIC Accelerator).", | |
| "Zdefiniuj konsorcjum lub tryb single-beneficiary (EIC).", | |
| "Przygotuj Concept Note / short proposal wg szablonu callu.", | |
| "Oceń potrzebę Granty na Eurogranty (PARP) jako przygotowanie — osobny tor PL.", | |
| "NIE zakładaj struktury Ścieżki SMART FENG.01 jako domyślnej dla naboru UE.", | |
| ] | |
| _PATH_NOTES = [ | |
| "Direct EU ≠ Ścieżka SMART. Schema/family must not inherit SMART multi-module structure.", | |
| "Eurogranty (PARP FENG.02.12) to tor przygotowawczy PL — nie mylić z samym call Horizon/EIC.", | |
| ] | |
| def horizon_prep_checklist() -> List[str]: | |
| """Advisor checklist for Direct EU / Horizon prep (not a submission form).""" | |
| return list(_PREP_CHECKLIST) | |
| def _blob(grant: Dict[str, Any]) -> str: | |
| parts = [ | |
| str(grant.get("name") or ""), | |
| str(grant.get("title") or ""), | |
| str(grant.get("program") or ""), | |
| str(grant.get("program_name") or ""), | |
| str(grant.get("program_type") or ""), | |
| str(grant.get("description") or "")[:1500], | |
| str(grant.get("instrument_type") or ""), | |
| str(grant.get("source") or ""), | |
| str(grant.get("family") or ""), | |
| " ".join(str(t) for t in (grant.get("tags") or []) if t), | |
| ] | |
| return " ".join(parts).lower() | |
| def is_direct_eu_grant(grant: Optional[Dict[str, Any]]) -> bool: | |
| """ | |
| True if grant looks like Horizon / EIC / Direct EU / Eurogranty prep. | |
| Never classifies pure SMART PL (Ścieżka SMART / FENG.01) as Direct EU | |
| unless strong Horizon/EIC markers are also present. | |
| """ | |
| if not isinstance(grant, dict) or not grant: | |
| return False | |
| fam = str(grant.get("family") or grant.get("program_type") or "").strip().upper() | |
| if grant.get("direct_eu") is True or fam in _DIRECT_EU_FAMILIES: | |
| return True | |
| b = _blob(grant) | |
| smartish = any(m in b for m in _SMART_PL_MARKERS) | |
| strong_eu = any(m in b for m in _STRONG_EU_MARKERS) | |
| if smartish and not strong_eu: | |
| return False | |
| return any(m in b for m in _HORIZON_MARKERS) | |
| def never_maps_to_smart_pl(grant: Optional[Dict[str, Any]] = None) -> bool: | |
| """ | |
| Product invariant: Direct EU prep path and classified Direct EU grants | |
| must not use SMART PL multi-module structure. | |
| """ | |
| if grant is None: | |
| return True | |
| if is_direct_eu_grant(grant): | |
| return True | |
| return False | |
| def direct_eu_prep_path_meta() -> Dict[str, Any]: | |
| """Attachable metadata for strategy PATH_DIRECT_EU.""" | |
| return { | |
| "id": PATH_DIRECT_EU, | |
| "checklist_id": CHECKLIST_ID, | |
| "checklist": horizon_prep_checklist(), | |
| "never_use_smart_modules": True, | |
| "notes": list(_PATH_NOTES), | |
| } | |
| def attach_direct_eu_meta(path: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Attach Direct EU prep checklist onto a strategy path dict when id matches. | |
| Returns a new dict; does not mutate the input. Non-Direct-EU paths get a | |
| shallow copy without Direct EU fields. | |
| """ | |
| out = dict(path) if isinstance(path, dict) else {} | |
| if str(out.get("id") or "") != PATH_DIRECT_EU: | |
| return out | |
| meta = direct_eu_prep_path_meta() | |
| out["checklist_id"] = meta["checklist_id"] | |
| out["checklist"] = list(meta["checklist"]) | |
| out["never_use_smart_modules"] = True | |
| out["notes"] = list(meta["notes"]) | |
| # Surface guardrail in reasons for cascade UI consumers | |
| reasons = list(out.get("reasons") or []) | |
| guard = "Direct EU ≠ SMART PL — nie używaj multi-modułów FENG.01." | |
| if guard not in reasons: | |
| reasons.append(guard) | |
| out["reasons"] = reasons | |
| return out | |