Rifqi Hafizuddin
[NOTICKET] feat(knowledge-extraction): derived outline + corroborated key parameters
6f5d619
Raw History Blame
5.5 kB
"""The frequency-sorted review queue — the pipeline's actual product.
Ordering is the product decision here, and it targets the bottleneck directly:
the expert is the scarce resource, so they should hit the terms whose definition
propagates furthest first. Conflicts are promoted above frequency regardless,
because a contradiction is a decision only they can make.
Each row carries page, section and the verbatim span so review is a matter of
checking a quote against a page, not a claim against memory. That is what makes
the queue finishable.
v2 dropped `extraction_status`, `diff_status`, `definition_conflict` and
`conflict_variants` from the row: all four are already expressed by
`review_reason`, which is the field a reviewer actually reads, and they remain
on the entry for a consumer that wants them.
"""
from __future__ import annotations
def build_queue(entries: list[dict], domain: dict | None = None) -> list[dict]:
"""Rows an expert works through, most-decision-worthy first.
**The queue is no longer glossary-only.** Every row carries `row_kind`, and
a consumer must switch on it rather than assume `term`: an unresolved key
parameter has a surface and no term, no definition and no mention count.
"""
def sort_key(entry: dict):
conflicting = (
entry.get("diff_status") == "conflicting"
or entry.get("definition_conflict") is True
)
return (0 if conflicting else 2, -int(entry.get("mention_count", 0) or 0))
queue = []
for entry in sorted(entries, key=sort_key):
provenance = entry.get("provenance") or {}
queue.append(
{
"row_kind": "term",
# What an approve/edit/reject decision attaches to. Without it a
# re-run loses which rows the expert already cleared.
"term_id": entry.get("term_id"),
"term": entry.get("term"),
"definition": entry.get("definition"),
"source_wording": entry.get("source_wording"),
"mention_count": entry.get("mention_count", 0),
"page": provenance.get("page"),
"section_no": provenance.get("section_no"),
"span": provenance.get("span"),
"review_reason": _reason(entry),
"_sort": sort_key(entry),
}
)
queue.extend(_key_parameter_rows(domain))
# Ranked once, over both kinds. Unresolved parameters sit between conflicts
# and routine confirmations: a contradiction is still the more urgent call,
# but "the model named a parameter nothing in the document corroborates" is
# a judgement only the expert can make, and it should not sink beneath 60
# routine rows where nobody reaches it.
queue.sort(key=lambda row: row["_sort"])
for rank, row in enumerate(queue, start=1):
row["rank"] = rank
del row["_sort"]
return queue
def _key_parameter_rows(domain: dict | None) -> list[dict]:
"""One row per key parameter that resolved to no extracted term.
Resolved parameters produce no row — they are corroborated and need nothing
from a human. Only the dangles are worth an expert's attention, and they are
worth it precisely because this branch has no span check: an unresolvable
surface is the one hallucination signal it can produce.
"""
provenance = (domain or {}).get("provenance") or {}
rows = []
for parameter in (domain or {}).get("key_parameters") or []:
if parameter.get("term_id"):
continue
rows.append(
{
"row_kind": "key_parameter",
# The decision attaches to the domain entry plus the surface —
# there is no term_id, which is the entire point of the row.
"term_id": None,
"domain_id": (domain or {}).get("domain_id"),
"term": parameter.get("surface"),
"definition": None,
"source_wording": None,
"mention_count": 0,
"page": provenance.get("page"),
"section_no": provenance.get("section_no"),
"span": provenance.get("span"),
"review_reason": (
"named as a key parameter but no extracted term corroborates it "
"— confirm it is real"
),
"_sort": (1, 0),
}
)
return rows
def _reason(entry: dict) -> str:
if entry.get("definition_conflict") or entry.get("diff_status") == "conflicting":
return "conflicting definitions — expert decision required"
if _wording_differs(entry):
return "source wording differs from the expanded name — confirm which is correct"
if entry.get("extraction_status") == "no_definition_found":
return "term found but no definition in document"
if not entry.get("definition"):
return "definition rejected by span check or absent"
return "routine confirmation"
def _wording_differs(entry: dict) -> bool:
"""The document says "Physical of Availability"; the expansion says
"Physical Availability". Surfacing that to the expert is a locked
requirement, so it earns its own review reason."""
source = (entry.get("source_wording") or "").strip().casefold()
full = (entry.get("full_name") or "").strip().casefold()
return bool(source and full) and full not in source