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