"""Source-heading selectors; negated project numbers never become positive filters.""" from dataclasses import dataclass import re import unicodedata def normalize(text): return ''.join(c for c in unicodedata.normalize('NFKD',text).casefold() if not unicodedata.combining(c)) def label_key(text): return ' '.join(re.sub(r'[^\w]+',' ',normalize(text)).split()) QUOTED=re.compile(r'[„“"«]([^„“”"»]+)[“”"»]') HEADING=re.compile(r'(?:diplomovy|bakalarsky|timovy|vyskumny|vedecky) projekt(?: [1-9]\d?)? [12]\d{3}|' r'(?:diplomova|bakalarska|dizertacna) praca [12]\d{3}') NUMBER=re.compile(r'\bprojekt\w*\s+([1-9]\d?)(?!\d)\b') NEGATIVE=re.compile(r'\b(?:nie|okrem|bez|s vynimkou)\s+(?:(?:diplomov|bakalarsk|timov|vyskumn|vedeck)\w*\s+)?$') @dataclass(frozen=True) class WorkSelector: labels: tuple=() excluded_labels: tuple=() numbers: tuple=() excluded_numbers: tuple=() unnumbered: bool=False multiple: bool=False positive_text: str='' unsupported_negation: bool=False @property def specific(self): return bool(self.labels or self.excluded_labels or self.numbers or self.excluded_numbers or self.unnumbered) def matches(self,label): key=label_key(label);match=NUMBER.search(key);number=int(match[1]) if match else None return ((not self.labels or key in self.labels) and key not in self.excluded_labels and (not self.numbers or number in self.numbers) and number not in self.excluded_numbers and (not self.unnumbered or number is None)) def parse_work_selector(question): text=normalize(question);positive=[];negative=[];excluded_spans=[] for match in QUOTED.finditer(text): key=label_key(match[1]) if not HEADING.fullmatch(key):continue neg=NEGATIVE.search(text[:match.start()]) if neg: negative.append(key);excluded_spans.append((neg.start(),match.end())) else:positive.append(key) numbers=[];excluded=[];unsupported=False for match in NUMBER.finditer(text): if any(quote.start()<=match.start()1 or len(set(numbers))>1 return WorkSelector(tuple(dict.fromkeys(positive)),tuple(dict.fromkeys(negative)), tuple(dict.fromkeys(numbers)),tuple(dict.fromkeys(excluded)), bool(re.search(r'\bnecislo\w*\b|\bprojekt\w*\s+bez cisla\b',text)),multiple,cleaned,unsupported) def selector_years(question): selector=parse_work_selector(question) text=QUOTED.sub(' ',selector.positive_text) years=[int(v) for v in re.findall(r'\b\d{4}\b',text)] years.extend(int(label.split()[-1]) for label in selector.labels) return list(dict.fromkeys(years))