"""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)}}