Rifqi Hafizuddin
[NOTICKET] feat(knowledge-extraction): derived outline + corroborated key parameters
6f5d619 Download src/knowledge_extraction/queue/review_queue.py from DataEyond/Agentic-Service-Data-Eyond-Catalog: direct link, hf CLI and curl.
- Browser
- Download file 5.5 kB
-
https://huggingface.co/spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/resolve/717452f69ec32394dea2a8fc11aba47eebe5bc53/src/knowledge_extraction/queue/review_queue.py
- Command line
-
hf download hf://spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog@717452f69ec32394dea2a8fc11aba47eebe5bc53/src/knowledge_extraction/queue/review_queue.py
-
curl -L -o review_queue.py https://huggingface.co/spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/resolve/717452f69ec32394dea2a8fc11aba47eebe5bc53/src/knowledge_extraction/queue/review_queue.py
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 | |