dp-zp-agent/tools/answer_evaluation/presentation.py
2026-11-03 02:02:05 +01:00

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.'