146 lines
7.2 KiB
Python
146 lines
7.2 KiB
Python
"""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.'
|