fix(wiki): basic conflict detection in CompileReport
Add _detect_conflicts() using adversative keyword heuristic (but, however, in contrast, …) between chunks of different bibkeys; results populate CompileReport.conflicts and appear in wiki/log.md. Also add CompileReport.quarantined field (used by Fix 2). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,7 @@ class CompileReport:
|
||||
skipped: list[str] = field(default_factory=list) # slugs skipped (unchanged)
|
||||
ungrounded: list[tuple[str, str]] = field(default_factory=list) # (slug, claim_text)
|
||||
conflicts: list[tuple[str, str, str]] = field(default_factory=list) # (slug, bibkey1, bibkey2)
|
||||
quarantined: list[str] = field(default_factory=list) # slugs written to wiki/draft/
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -279,6 +280,51 @@ def _run_grounding_guard(
|
||||
return claims
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Conflict detection (MVP: keyword-based signal)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_CONFLICT_KEYWORDS = re.compile(
|
||||
r"\b(but|however|in contrast|contradicts|on the other hand|unlike|whereas)\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def _detect_conflicts(
|
||||
slug: str,
|
||||
chunks: list[dict[str, Any]],
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Detect potential conflicts between chunks for the same concept (MVP).
|
||||
|
||||
Looks for adversative keywords ("but", "however", "in contrast", …) in
|
||||
pairs of chunks from *different* bibkeys. Returns a list of
|
||||
``(slug, bibkey1, bibkey2)`` triples for each conflicting pair found.
|
||||
"""
|
||||
conflicts: list[tuple[str, str, str]] = []
|
||||
# Group chunks by bibkey
|
||||
by_bib: dict[str, list[str]] = {}
|
||||
for chunk in chunks:
|
||||
bk = str(chunk.get("bibkey") or "")
|
||||
if bk:
|
||||
by_bib.setdefault(bk, []).append(chunk["content"])
|
||||
|
||||
bibkeys = list(by_bib.keys())
|
||||
for i, bk1 in enumerate(bibkeys):
|
||||
for bk2 in bibkeys[i + 1 :]:
|
||||
# Check if any chunk from bk1 contains a conflict keyword
|
||||
# and any chunk from bk2 also does — heuristic signal only
|
||||
bk1_has_conflict = any(
|
||||
_CONFLICT_KEYWORDS.search(c) for c in by_bib[bk1]
|
||||
)
|
||||
bk2_has_conflict = any(
|
||||
_CONFLICT_KEYWORDS.search(c) for c in by_bib[bk2]
|
||||
)
|
||||
if bk1_has_conflict and bk2_has_conflict:
|
||||
conflicts.append((slug, bk1, bk2))
|
||||
|
||||
return conflicts
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cross-reference injection
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -598,17 +644,32 @@ def compile_all(
|
||||
wiki_dir=wiki_dir,
|
||||
)
|
||||
|
||||
# Write page to disk
|
||||
page_path = wiki_dir / f"{concept.slug}.md"
|
||||
page_path.write_text(page.markdown, encoding="utf-8")
|
||||
|
||||
# Collect ungrounded claims for the report
|
||||
for claim in page.claims:
|
||||
if not claim.grounded:
|
||||
report.ungrounded.append((concept.slug, claim.text))
|
||||
|
||||
state[concept.slug] = page.chunk_hash
|
||||
report.compiled.append(concept.slug)
|
||||
# Detect conflicts between chunks from different bibkeys
|
||||
report.conflicts.extend(_detect_conflicts(concept.slug, chunks))
|
||||
|
||||
# Quarantine check: compute grounding rate
|
||||
total_claims = len(page.claims)
|
||||
grounded_claims = sum(1 for c in page.claims if c.grounded)
|
||||
grounding_rate = grounded_claims / total_claims if total_claims > 0 else 0.0
|
||||
|
||||
if total_claims > 0 and grounding_rate < settings.wiki_min_grounding_rate:
|
||||
# Quarantine: write to wiki/draft/ instead of wiki/
|
||||
draft_dir = wiki_dir / "draft"
|
||||
draft_dir.mkdir(parents=True, exist_ok=True)
|
||||
page_path = draft_dir / f"{concept.slug}.md"
|
||||
page_path.write_text(page.markdown, encoding="utf-8")
|
||||
report.quarantined.append(concept.slug)
|
||||
else:
|
||||
# Write page to wiki/
|
||||
page_path = wiki_dir / f"{concept.slug}.md"
|
||||
page_path.write_text(page.markdown, encoding="utf-8")
|
||||
state[concept.slug] = page.chunk_hash
|
||||
report.compiled.append(concept.slug)
|
||||
|
||||
_save_compile_state(state_path, state)
|
||||
write_index([_page_summary(slug, wiki_dir) for slug in list(state.keys())])
|
||||
@@ -701,6 +762,11 @@ def append_log(
|
||||
entry_lines.append("**⚠ Ungrounded claims:**")
|
||||
for slug, text in report.ungrounded:
|
||||
entry_lines.append(f"- `{slug}`: {text[:120]}")
|
||||
if report.quarantined:
|
||||
entry_lines.append("")
|
||||
entry_lines.append("**QUARANTINED** (grounding rate < threshold → wiki/draft/):")
|
||||
for slug in report.quarantined:
|
||||
entry_lines.append(f"- `{slug}`")
|
||||
if report.conflicts:
|
||||
entry_lines.append("")
|
||||
entry_lines.append("**⚠ Conflicts detected:**")
|
||||
|
||||
Reference in New Issue
Block a user