"""Slovak presentation templates for validated facts and literal source excerpts. No model, gold answers or extra retrieval is used here. Task wording can be converted to a goal, never to a claim that the task has already been completed. """ from __future__ import annotations import re from .context_policy import normalized from .rag_evidence import WORK_LABELS ACCUSATIVE = { 'bachelor_thesis': 'bakalársku prácu', 'diploma_thesis': 'diplomovú prácu', 'doctoral_thesis': 'dizertačnú prácu', 'bachelor_project': 'bakalársky projekt', 'diploma_project': 'diplomový projekt', 'team_project': 'tímový projekt', 'research_project': 'výskumný projekt', } def owner_in_question(person: str, question: str) -> str | None: """Reuse a bound genitive from the question; never guess a name's declension.""" words = list(re.finditer(r"[^\W\d_]+(?:[-'][^\W\d_]+)*", question)) tokens = person.split() for index in range(len(words) - len(tokens) + 1): found = words[index:index + len(tokens)] if not re.search(r'\b(?:práca|práce|prácu|prác|projekt|projektu|projektov)\s*$', question[:found[0].start()], re.I): continue valid = True for token, match in zip(tokens, found): base = normalized(token) variants = {base, base + 'a', base + 'u'} valid &= normalized(match[0]) in variants if valid: return question[found[0].start():found[-1].end()] return None def work_subject(person: str, kind: str | None, question: str = '') -> str: label = WORK_LABELS.get(kind, 'práca') owner = owner_in_question(person, question) return label.capitalize() + (' ' + owner if owner else f' ({person})') def fact_sentences(claim: dict, question: str = '') -> str: """Keep each person's academic tuple together, with explicit year semantics.""" person = claim['person']; kind = claim.get('work_type') title = claim.get('title'); year = claim.get('year') displayed_title = title.rstrip('.') if title is not None else None parts = [] if kind or title is not None or year is not None: owner = owner_in_question(person, question) if owner: subject = work_subject(person, kind, question) if title is not None: parts.append(f'{subject} má názov **{displayed_title}**.') elif year is not None: parts.append(f'{subject} je v podkladoch evidovaná s rokom **{year}**.' if not (kind or '').endswith('_project') else f'{subject} je v podkladoch evidovaný s rokom **{year}**.') else: parts.append(f'V podkladoch je uvedená {subject[0].lower() + subject[1:]}.' if not (kind or '').endswith('_project') else f'V podkladoch je uvedený {subject[0].lower() + subject[1:]}.') else: sentence = f'{person} má {ACCUSATIVE.get(kind, "prácu")}' if title is not None: sentence += f' s názvom **{displayed_title}**' if year is not None: sentence += f' (rok práce: **{year}**)' parts.append(sentence + '.') if owner and title is not None and year is not None: parts.append(f'Rok práce je **{year}**.') if claim.get('start_year') is not None: parts.append(f'{person} má uvedený začiatok štúdia v roku **{claim["start_year"]}**.') for key, label in [('keyword', 'kľúčové slovo'), ('category', 'kategória'), ('document_author', 'autor dokumentu')]: if claim.get(key) is not None: parts.append(f'Pri osobe {person} je uvedené: {label} **{claim[key]}**.') return ' '.join(parts) or person + '.' GOAL_NOUNS = ( ('Vypracujte prehľad', 'Vypracovanie prehľadu'), ('Vypracujte rešerš', 'Vypracovanie rešerše'), ('Implementujte jednoduchý systém', 'Implementácia jednoduchého systému'), ('Implementujte systém', 'Implementácia systému'), ('Implementujte vyhľadávač', 'Implementácia vyhľadávača'), ('Navrhnite a vypracujte experimenty', 'Návrh a vypracovanie experimentov'), ('Navrhnite možné zlepšenia', 'Návrh možných zlepšení'), ('Navrhnite zlepšenia', 'Návrh zlepšení'), ('Preskúmajte metódy', 'Preskúmanie metód'), ('Vyhodnoťte presnosť', 'Vyhodnotenie presnosti'), ) GOAL_VERBS = { 'vypracujte': 'Vypracovať', 'implementujte': 'Implementovať', 'navrhnite': 'Navrhnúť', 'preskúmajte': 'Preskúmať', 'vyhodnoťte': 'Vyhodnotiť', 'porovnajte': 'Porovnať', 'vytvorte': 'Vytvoriť', 'analyzujte': 'Analyzovať', 'otestujte': 'Otestovať', 'overte': 'Overiť', } def goal_text(unit: str) -> tuple[str, bool]: """Only bounded grammatical substitutions; unknown prose stays verbatim.""" text = re.sub(r'^\s*(?:\d+[.)]|[-+*])\s+', '', unit.strip()) for original, replacement in GOAL_NOUNS: if re.match(re.escape(original) + r'\b', text, re.I): result = replacement + text[len(original):] result = result.replace('v ktorom využijete', 'v ktorom sa má využiť') result = result.replace(', v ktorých vyhodnotíte', ' s cieľom vyhodnotiť') result = result.replace('v ktorých vyhodnotíte', 's cieľom vyhodnotiť') return result, True first = text.split(maxsplit=1)[0].casefold() if text else '' if first in GOAL_VERBS: result = re.sub(r'\b(' + '|'.join(map(re.escape, GOAL_VERBS)) + r')\b', lambda match: GOAL_VERBS[match[0].casefold()].lower(), text, flags=re.I) return result[0].upper() + result[1:], True return unit.strip(), False def content_lines(units: list[str], person: str | None, kind: str | None, question: str = '') -> list[str]: """Distinguish documented goals from factual descriptions or progress notes.""" # A task heading has no factual content of its own and otherwise prevents # a whole assignment quote from being presented as individual goals. units = [unit for unit in units if normalized(unit) not in { 'zadanie', 'zadanie diplomovej prace', 'zadanie bakalarskej prace', 'ciele prace'}] converted = [goal_text(unit) for unit in units] subject = work_subject(person, kind, question) if person else 'Práca' if converted and all(is_goal for _, is_goal in converted): return [subject + ' má podľa dostupného zadania tieto ciele:', '', *[f'{index}. {text}' for index, (text, _) in enumerate(converted, 1)]] return [subject + ' je v dostupných podkladoch opísaná takto:' if not (kind or '').endswith('_project') else subject + ' je v dostupných podkladoch opísaný takto:', '', *units] def no_answer_text(context: dict) -> str: people = context.get('requested_people') or context.get('requirements', {}).get('people', []) if len(people) == 1: return (f'Pre osobu {people[0]} sa v dostupných podkladoch nepodarilo spoľahlivo nájsť ' 'údaje zodpovedajúce tejto otázke.') return 'K tejto otázke sa v dostupných podkladoch nepodarilo spoľahlivo nájsť zodpovedajúce údaje.'