89 lines
4.6 KiB
Python
89 lines
4.6 KiB
Python
"""ZP Wiki -> CRZP: a thesis fact the wiki profile lacks, taken from CRZP.
|
|
|
|
A student's ZP Wiki profile may be incomplete (no opponent, no keywords, no
|
|
abstract, no final title). When /rag or /graphrag answers "no_answer" for a
|
|
person the wiki knows, the same question is asked in CRZP for exactly that
|
|
person (the canonical wiki name) at Technická univerzita v Košiciach, the school
|
|
of ZP Wiki. The answer keeps the wiki's own message first and marks the CRZP
|
|
part with its source, so it is never presented as a wiki fact.
|
|
|
|
Not used for what CRZP does not hold (meetings, progress, tasks, assignments,
|
|
results beyond the abstract, repositories) and never for an unknown or
|
|
ambiguous person, a refused question or another failure. CRZP_WIKI_FALLBACK=0
|
|
turns it off.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
|
|
from tools.answers.context_policy import normalized
|
|
from tools.answers.delivery_contract import fingerprint
|
|
|
|
# What only the wiki records: CRZP is not asked.
|
|
WIKI_ONLY = re.compile(r"\b(?:vysled\w*|stretnut\w*|pokrok\w*|progres\w*|uloh\w*|zadan\w*|plan\w*|termin\w*|"
|
|
r"tyzd\w*|repozitar\w*|git\w*|kod\w*|stav\w*|poznam\w*|konzult\w*|zaciat\w*|studi\w*|"
|
|
r"obhaj\w*|obhaj\w*|ukonc\w*|dokonc\w*|skoncil\w*|uspes\w*|statnic\w*|hodnot\w*|znamk\w*)\b")
|
|
# A CRZP record means a submitted work, not a defended or graded one: completion stays a wiki question.
|
|
INTENTS = ("list", "about", "keywords", "relation", "count")
|
|
|
|
|
|
def enabled() -> bool:
|
|
return os.getenv("CRZP_WIKI_FALLBACK", "1") != "0"
|
|
|
|
|
|
def wiki_person(question: str, db_file=None) -> str | None:
|
|
"""The one ZP Wiki person the question names (canonical name), else None."""
|
|
match = wiki_match(question, db_file)
|
|
return match["name"] if match else None
|
|
|
|
|
|
def wiki_match(question: str, db_file=None) -> dict | None:
|
|
"""{"name", "student"}: the one ZP Wiki person the question names; student = has a profile page."""
|
|
from tools.answers.question_plan import planning_catalog
|
|
from tools.rag.common import DB_FILE
|
|
from tools.retrieval.person_names import resolve_person_names
|
|
try:
|
|
catalog = planning_catalog(db_file or DB_FILE)
|
|
except (OSError, ValueError, RuntimeError):
|
|
return None
|
|
resolution = resolve_person_names(question, catalog or [])
|
|
matches = resolution.get("matches") or []
|
|
if resolution.get("status") != "ok" or len({m["name"] for m in matches}) != 1:
|
|
return None
|
|
source = matches[0].get("source_document") or ""
|
|
return {"name": matches[0]["name"], "student": source.startswith(("pages/students/", "pages/interns/"))}
|
|
|
|
|
|
def supplement(question: str, wiki_result: dict, engine: str, *, db_file=None, complete=None) -> dict | None:
|
|
"""The wiki result extended by the CRZP answer for the same person, or None."""
|
|
if not enabled() or wiki_result.get("status") != "no_answer" or WIKI_ONLY.search(normalized(question)):
|
|
return None
|
|
person = wiki_person(question, db_file)
|
|
if not person:
|
|
return None
|
|
from tools.crzp.answering import answer as crzp
|
|
from tools.crzp.answering.understand import Person, understand
|
|
query = understand(question, complete=complete)
|
|
if query.intent not in INTENTS:
|
|
return None
|
|
role = query.people[0].role if query.people else "any"
|
|
query.people = [Person(name=person, role=role, typed=person)]
|
|
query.from_model = True # the canonical wiki name: no other name forms
|
|
query.school, query.school_name = "tuke", None
|
|
query.title = query.title if query.title and person not in query.title else None
|
|
result = crzp._answer_query(question, query, complete)
|
|
if result.get("status") != "ok":
|
|
return None
|
|
wiki_text = (wiki_result.get("answer") or "").strip()
|
|
text = ((wiki_text + "\n\n" if wiki_text else "") + "**Doplnené z CRZP** (Centrálny register záverečných prác, "
|
|
f"Technická univerzita v Košiciach; ZP Wiki tento údaj neuvádza):\n\n" + result["answer"])
|
|
truncated = bool(result.get("truncated"))
|
|
return {**{k: v for k, v in wiki_result.items() if k not in ("answer", "reason", "delivery_check")},
|
|
"status": "ok", "reason": None, "accepted_for_delivery": True, "answer": text,
|
|
"source_urls": list(dict.fromkeys([*(wiki_result.get("source_urls") or []), *result["source_urls"]])),
|
|
"claims": [], "complete": not truncated, "truncated": truncated,
|
|
"truncation_reasons": result.get("truncation_reasons", []), "supplement": "crzp",
|
|
"delivery_check": {"layer": "delivery", "code": "partial" if truncated else "complete",
|
|
"request_fingerprint": fingerprint(question, engine)}}
|