173 lines
8.8 KiB
Python
173 lines
8.8 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, person_token_pattern, text_urls
|
|
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):
|
|
valid &= bool(re.fullmatch(person_token_pattern(token), normalized(match[0])))
|
|
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ť',
|
|
'zistite': 'Zistiť', 'naučte': 'Naučiť', 'zostavte': 'Zostaviť',
|
|
'využite': 'Využiť', 'zlepšite': 'Zlepšiť', 'použite': 'Použiť',
|
|
'natrénujte': 'Natrénovať', 'optimalizujte': 'Optimalizovať',
|
|
'identifikujte': 'Identifikovať', 'preštudujte': 'Preštudovať',
|
|
'pripravte': 'Pripraviť',
|
|
}
|
|
|
|
|
|
def goal_text(unit: str) -> tuple[str, bool]:
|
|
"""Only bounded grammatical substitutions; unknown prose stays verbatim."""
|
|
if text_urls(unit) or '`' in unit:
|
|
# A word inside a URL/code literal must never be conjugated. Preserve
|
|
# technical excerpts exactly rather than changing their references.
|
|
return unit.strip(), False
|
|
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):
|
|
tail = text[len(original):]
|
|
# Infinitives retain the original object cases when a second
|
|
# command or coordinated object makes a noun substitution unsafe.
|
|
if (re.search(r'\b(?:' + '|'.join(map(re.escape, GOAL_VERBS)) + r')\b', tail, re.I)
|
|
or re.search(r'\ba\s+(?:spôsoby|metódy|aplikácie|systémy|experimenty|možnosti)\b', tail, re.I)):
|
|
break
|
|
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
|
|
match = re.match(r'[^\W\d_]+', text)
|
|
first = match[0].casefold() if match else ''
|
|
if first in GOAL_VERBS or (first == 'na' and text.casefold().startswith('na základe ')
|
|
and re.search(r'\b(?:' + '|'.join(map(re.escape, GOAL_VERBS)) + r')\b', text, re.I)):
|
|
def infinitive(match):
|
|
word = GOAL_VERBS[match[0].casefold()]
|
|
prefix = text[:match.start()].rstrip()
|
|
return word if not prefix or prefix[-1] in '.!?' else word.lower()
|
|
result = re.sub(r'\b(' + '|'.join(map(re.escape, GOAL_VERBS)) + r')\b',
|
|
infinitive, text, flags=re.I)
|
|
return result[0].upper() + result[1:], True
|
|
if first in {value.casefold() for value in GOAL_VERBS.values()}:
|
|
return text, 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', 'ciele na semester', 'navrh na zadanie', 'navrh zadania'}]
|
|
converted = [goal_text(unit) for unit in units]
|
|
subject = work_subject(person, kind, question) if person else 'Práca'
|
|
goals = [text for text, is_goal in converted if is_goal]
|
|
other = [text for text, is_goal in converted if not is_goal]
|
|
if goals:
|
|
lines = [subject + ' má podľa dostupných podkladov tieto ciele:', '',
|
|
*[f'{index}. {text}' for index, text in enumerate(goals, 1)]]
|
|
if other:
|
|
lines.extend(['', 'Ďalšie podklady:', *other])
|
|
return lines
|
|
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.'
|