"""Parsowanie pól WYSZUKIWARKA pod filtr eligibility i matcher.""" from __future__ import annotations import re from typing import Any, Dict, List, Optional, Tuple VOIVODESHIPS = ( "mazowieckie", "małopolskie", "malopolskie", "śląskie", "slaskie", "wielkopolskie", "dolnośląskie", "dolnoslaskie", "pomorskie", "łódzkie", "lodzkie", "lubelskie", "podkarpackie", "zachodniopomorskie", "warmińsko-mazurskie", "warminsko-mazurskie", "podlaskie", "świętokrzyskie", "swietokrzyskie", "lubuskie", "opolskie", "kujawsko-pomorskie", ) VOIVODESHIP_CANONICAL = { "malopolskie": "małopolskie", "slaskie": "śląskie", "dolnoslaskie": "dolnośląskie", "lodzkie": "łódzkie", "warminsko-mazurskie": "warmińsko-mazurskie", "swietokrzyskie": "świętokrzyskie", } NATIONAL_MARKERS = ( "cała polska", "cala polska", "ogólnopolsk", "ogolnopolsk", "krajowy", "wszystkie województwa", "wszystkie wojewodztwa", "terytorium kraju", ) BENEFICIARY_RULES: Tuple[Tuple[str, str], ...] = ( ( r"\bmśp\b|\bmsp\b|mikroprzedsi|mikro[\s,].*przedsi|mał[eych]*[\s,].*przedsi|" r"średni[ech]*[\s,].*przedsi|mikro,\s*mał", "mśp", ), (r"\bngo\b|pozarządow|pozarzadow|trzeci sektor|organizacj[ae] pozarząd", "ngo"), (r"\bjst\b|jednostk[ai] samorząd|samorząd|gmin[ay]|powiat|marszałk", "jst"), (r"rolnik|rolnictw|gospodarstw[ao] rol|arimr|wpr\b|prow\b", "rolnik"), (r"osob[ay] fizyczn|gospodarstw[ao] domow", "osoba_fizyczna"), (r"duż[eych]* przedsi|duz[eych]* przedsi|large enterprise", "duże"), (r"startup|scale-?up", "startup"), (r"przedsiębiorc|przedsiebiorc|firma|przedsiębiorstw", "przedsiębiorca"), (r"uczelni|uniwersytet|badawcz|naukow|instytut nauk", "nauka"), (r"szkoł|placówek oświat|oświat", "edukacja"), ) SIZE_FROM_TAGS = { "mśp": ["mikro", "małe", "średnie"], "startup": ["mikro", "małe"], "duże": ["duże"], } def _norm_voiv(name: str) -> str: low = name.lower().strip() return VOIVODESHIP_CANONICAL.get(low, low) def parse_beneficiary_tags(*texts: str) -> List[str]: """Zwraca unikalne tagi beneficjentów (MŚP, NGO, JST, rolnik…).""" combined = " ".join(t for t in texts if t).lower() if not combined.strip(): return [] found: List[str] = [] for pattern, tag in BENEFICIARY_RULES: if re.search(pattern, combined, re.I): if tag not in found: found.append(tag) return found def parse_eligible_company_sizes( beneficjenci: str = "", warunki_wejscia: str = "", name: str = "" ) -> List[str]: """Mapuje beneficjentów na rozmiary firm dla filtra MŚP.""" tags = parse_beneficiary_tags(beneficjenci, warunki_wejscia, name) sizes: List[str] = [] text = f"{beneficjenci} {warunki_wejscia} {name}".lower() if re.search(r"tylko.*duż|wyłącznie.*duż|large enterprise", text, re.I): return ["duże"] for tag in tags: for size in SIZE_FROM_TAGS.get(tag, []): if size not in sizes: sizes.append(size) if "mśp" in tags or re.search(r"\bmikro\b|\bmał[ey]\b|\bśredni", text, re.I): for s in ("mikro", "małe", "średnie"): if s not in sizes: sizes.append(s) if re.search(r"wyłącznie.*mikro|tylko.*mikro", text, re.I): return ["mikro"] if re.search(r"wyłącznie.*mał|tylko.*mał", text, re.I): return ["małe"] return sizes def parse_eligible_regions( operator: str = "", zrodlo: str = "", beneficjenci: str = "", nazwa: str = "", ) -> List[str]: """Wyciąga województwo z operatora/źródła lub oznacza program krajowy.""" hay = f"{operator} {zrodlo} {beneficjenci} {nazwa}".lower() regions: List[str] = [] for voiv in VOIVODESHIPS: if voiv in hay: canon = _norm_voiv(voiv) if canon not in regions: regions.append(canon) rpo_match = re.search(r"rpo\s+([\wąćęłńóśźż\-]+)", hay, re.I) if rpo_match and not regions: hint = rpo_match.group(1).lower() for voiv in VOIVODESHIPS: if hint in voiv or voiv.startswith(hint[:4]): regions.append(_norm_voiv(voiv)) wfos_match = re.search(r"wf[oó]śigw\s+([\wąćęłńóśźż\-]+)", hay, re.I) if wfos_match and not regions: hint = wfos_match.group(1).lower() for voiv in VOIVODESHIPS: if hint in voiv: regions.append(_norm_voiv(voiv)) if any(m in hay for m in NATIONAL_MARKERS): return ["cała Polska"] national_operators = ( "parp", "ncbr", "ncn", "bgk", "kpk", "feniks", "kpo", "granty.pl", "atlas dotacji", "fundusze europejskie.gov", "arimr", "nfosigw", "eeagrants", "horyzont", ) if not regions and any(op in hay for op in national_operators): return ["cała Polska"] return regions def parse_amount_pln(raw: Any) -> Optional[int]: """Parsuje kwotę z pola kwota_max/min (tekst lub liczba).""" if raw is None: return None if isinstance(raw, (int, float)): val = int(raw) return val if val > 0 else None text = str(raw).lower().replace("\xa0", " ").strip() if not text: return None m = re.search(r"([\d\s]+(?:[.,]\d+)?)\s*(mln|mld|tys|zł|pln)?", text) if not m: return None num_str = m.group(1).replace(" ", "").replace(",", ".") try: num = float(num_str) except ValueError: return None unit = (m.group(2) or "").lower() if "mld" in unit: num *= 1_000_000_000 elif "mln" in unit: num *= 1_000_000 elif "tys" in unit: num *= 1_000 val = int(num) if val > 500_000_000_000: return None return val if val > 0 else None def extract_pkd_hints(*texts: str) -> List[str]: """Wyciąga kody PKD z warunków wejścia / opisu.""" combined = " ".join(t for t in texts if t) found: List[str] = [] for m in re.finditer(r"\b(\d{2}\.\d{2}(?:\.[A-Z])?)\b", combined, re.I): code = m.group(1).upper() if code not in found: found.append(code) return found[:15] def parse_entry_restrictions(warunki_wejscia: str) -> List[str]: """Skrócone tagi ograniczeń wejścia (do matchera).""" if not warunki_wejscia: return [] text = warunki_wejscia.lower() tags: List[str] = [] rules = ( (r"de minimis|pomoc publiczn", "de_minimis"), (r"dns[hH]|zrównoważon", "dnsh"), (r"minimum\s+\d+\s*%|wkład własn", "wkład_własny"), (r"do\s+\d+\s+lat|wiek|41\s+r", "limit_wieku"), (r"konsorcj|partner", "konsorcjum"), (r"innowacj|b\+r|badania", "innowacje"), (r"eksport", "eksport"), (r"zatrudnien|pracownik|fte", "zatrudnienie"), ) for pattern, tag in rules: if re.search(pattern, text, re.I) and tag not in tags: tags.append(tag) return tags def is_exclusive_beneficiary(text: str, tag: str) -> bool: """Czy beneficjent jest wyłącznym (tylko NGO, tylko JST…).""" if not text: return False low = text.lower() exclusive_markers = ("wyłącznie", "wylacznie", "tylko dla", "jedynie") if not any(m in low for m in exclusive_markers): return False tag_patterns = { "ngo": r"ngo|pozarządow|trzeci sektor", "jst": r"jst|samorząd|gmin|powiat", "rolnik": r"rolnik|rolnictw|gospodarstw rol", "osoba_fizyczna": r"osob[ay] fizyczn|gospodarstw domow", } pat = tag_patterns.get(tag) return bool(pat and re.search(pat, low, re.I))