fix(example): skip prompt writes when output exists, add quality rubrics
INFRA-TASKS #5 — process_chapters.py now skips writing *-prompt.md files when the corresponding output file already exists on disk. DB-only rebuilds no longer dirty the working tree with unchanged prompt content. INFRA-TASKS #8 — Added '## Quality Metrics' section to the entity and VSM mapping schemas, defining the five evaluation dimensions (Definition Precision, Source Grounding, Domain Placement, VSM Relevance, Explanatory Value) with 1–5 rubrics used by the evaluate-entity template. Also updated INFRA-TASKS.md to reflect current resolution status for tasks 4–19 across S2 and S3. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -487,14 +487,16 @@ class ChapterProcessor:
|
||||
if not prompt:
|
||||
return None
|
||||
|
||||
# Write compiled prompt for inspection
|
||||
prompt_file = self._entities_dir() / f"{chapter_id}-prompt.md"
|
||||
prompt_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
prompt_file.write_text(prompt)
|
||||
print(f" Prompt written to {prompt_file.relative_to(self.example_dir)}")
|
||||
|
||||
view_file = self._entities_dir() / f"{chapter_id}-entities.md"
|
||||
|
||||
# Write compiled prompt only when no output exists yet (avoids dirty
|
||||
# working tree on DB-only rebuilds — Task 5 fix)
|
||||
prompt_file = self._entities_dir() / f"{chapter_id}-prompt.md"
|
||||
if not (view_file.exists() and "{{ include" in view_file.read_text()):
|
||||
prompt_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
prompt_file.write_text(prompt)
|
||||
print(f" Prompt written to {prompt_file.relative_to(self.example_dir)}")
|
||||
|
||||
# ── PRIMARY: chapter view with transclusion already on disk ──
|
||||
if view_file.exists() and "{{ include" in view_file.read_text():
|
||||
content, entity_files = self._read_entities_from_view(chapter_id)
|
||||
@@ -575,11 +577,14 @@ class ChapterProcessor:
|
||||
if not prompt:
|
||||
return None
|
||||
|
||||
prompt_file = self.example_dir / "output" / "mappings" / f"{chapter_id}-prompt.md"
|
||||
prompt_file.write_text(prompt)
|
||||
print(f" Prompt written to {prompt_file.relative_to(self.example_dir)}")
|
||||
|
||||
output_file = self.example_dir / "output" / "mappings" / f"{chapter_id}-mappings.md"
|
||||
# Write compiled prompt only when output does not yet exist (Task 5 fix)
|
||||
if not output_file.exists():
|
||||
prompt_file = self.example_dir / "output" / "mappings" / f"{chapter_id}-prompt.md"
|
||||
prompt_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
prompt_file.write_text(prompt)
|
||||
print(f" Prompt written to {prompt_file.relative_to(self.example_dir)}")
|
||||
|
||||
if output_file.exists():
|
||||
content = output_file.read_text()
|
||||
self.store_output_artifact(
|
||||
@@ -622,11 +627,14 @@ class ChapterProcessor:
|
||||
if not prompt:
|
||||
return None
|
||||
|
||||
prompt_file = self.example_dir / "output" / "analyses" / f"{chapter_id}-prompt.md"
|
||||
prompt_file.write_text(prompt)
|
||||
print(f" Prompt written to {prompt_file.relative_to(self.example_dir)}")
|
||||
|
||||
output_file = self.example_dir / "output" / "analyses" / f"{chapter_id}-analysis.md"
|
||||
# Write compiled prompt only when output does not yet exist (Task 5 fix)
|
||||
if not output_file.exists():
|
||||
prompt_file = self.example_dir / "output" / "analyses" / f"{chapter_id}-prompt.md"
|
||||
prompt_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
prompt_file.write_text(prompt)
|
||||
print(f" Prompt written to {prompt_file.relative_to(self.example_dir)}")
|
||||
|
||||
if output_file.exists():
|
||||
content = output_file.read_text()
|
||||
self.store_output_artifact(
|
||||
@@ -679,11 +687,14 @@ class ChapterProcessor:
|
||||
if not prompt:
|
||||
return None
|
||||
|
||||
prompt_file = self.example_dir / "output" / "metrics" / "metrics-prompt.md"
|
||||
prompt_file.write_text(prompt)
|
||||
print(f" Prompt written to {prompt_file.relative_to(self.example_dir)}")
|
||||
|
||||
output_file = self.example_dir / "output" / "metrics" / "metrics-report.md"
|
||||
# Write compiled prompt only when output does not yet exist (Task 5 fix)
|
||||
if not output_file.exists():
|
||||
prompt_file = self.example_dir / "output" / "metrics" / "metrics-prompt.md"
|
||||
prompt_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
prompt_file.write_text(prompt)
|
||||
print(f" Prompt written to {prompt_file.relative_to(self.example_dir)}")
|
||||
|
||||
if output_file.exists():
|
||||
content = output_file.read_text()
|
||||
self.store_output_artifact(
|
||||
@@ -709,6 +720,123 @@ class ChapterProcessor:
|
||||
print(f" Awaiting output at: {output_file.relative_to(self.example_dir)}")
|
||||
return None
|
||||
|
||||
# ── Entity Evaluation (Task 9) ────────────────────────────────────
|
||||
|
||||
def _extract_quality_rubric(self) -> str:
|
||||
"""Extract the Quality Metrics section from the entity schema file."""
|
||||
schema_file = self.example_dir / "schemas" / "economic-entity-schema-v1.0.md"
|
||||
text = schema_file.read_text()
|
||||
# Find the ## Quality Metrics section up to the next ## section
|
||||
import re as _re
|
||||
m = _re.search(
|
||||
r"^## Quality Metrics\n(.*?)^## ",
|
||||
text,
|
||||
flags=_re.MULTILINE | _re.DOTALL,
|
||||
)
|
||||
if m:
|
||||
return ("## Quality Metrics\n" + m.group(1)).strip()
|
||||
return text # fallback: whole schema
|
||||
|
||||
def _extract_source_chapter_from_entity(self, entity_text: str) -> str:
|
||||
"""Extract the Source Chapter field from an entity markdown file."""
|
||||
import re as _re
|
||||
m = _re.search(
|
||||
r"^## Source Chapter\s*\n+(.+?)(?:\n\n|\n##|\Z)",
|
||||
entity_text,
|
||||
flags=_re.MULTILINE | _re.DOTALL,
|
||||
)
|
||||
if m:
|
||||
return m.group(1).strip()
|
||||
return "Unknown chapter"
|
||||
|
||||
def evaluate_entities(self, chapter_id: Optional[str] = None) -> None:
|
||||
"""Evaluate canonical entities using the evaluate-entity template.
|
||||
|
||||
If *chapter_id* is given, evaluates only entities introduced by that
|
||||
chapter (determined from the chapter view file). Otherwise evaluates
|
||||
all canonical entities.
|
||||
|
||||
Outputs are written to ``output/evaluations/<slug>-eval.md``.
|
||||
Existing evaluation files are skipped (idempotent).
|
||||
"""
|
||||
evaluations_dir = self.example_dir / "output" / "evaluations"
|
||||
evaluations_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Determine which entity files to evaluate
|
||||
if chapter_id:
|
||||
view_file = self._entities_dir() / f"{chapter_id}-entities.md"
|
||||
if not view_file.exists():
|
||||
print(f" No chapter view found for {chapter_id}")
|
||||
return
|
||||
_, entity_files = self._read_entities_from_view(chapter_id)
|
||||
if not entity_files:
|
||||
print(f" No entities found for chapter {chapter_id}")
|
||||
return
|
||||
print(f"Evaluating {len(entity_files)} entities from {chapter_id}...")
|
||||
else:
|
||||
slugs = self._list_existing_entity_names()
|
||||
entity_files = [(s, self._entities_dir() / f"{s}.md") for s in slugs]
|
||||
print(f"Evaluating {len(entity_files)} canonical entities...")
|
||||
|
||||
if not entity_files:
|
||||
print(" No entities to evaluate.")
|
||||
return
|
||||
|
||||
# Shared context loaded once
|
||||
quality_rubric = self._extract_quality_rubric()
|
||||
self.bind_macro_artifact(self.spaces["guidelines"], "quality_rubric", quality_rubric)
|
||||
|
||||
done = 0
|
||||
skipped = 0
|
||||
failed = 0
|
||||
|
||||
for slug, entity_path in entity_files:
|
||||
output_file = evaluations_dir / f"{slug}-eval.md"
|
||||
if output_file.exists():
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
if not entity_path.exists():
|
||||
print(f" MISSING: {entity_path.name}")
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
entity_text = entity_path.read_text()
|
||||
source_chapter = self._extract_source_chapter_from_entity(entity_text)
|
||||
|
||||
# Bind per-entity macros
|
||||
self.bind_macro_artifact(self.spaces["entities"], "entity_content", entity_text)
|
||||
self.bind_macro_artifact(self.spaces["sources"], "source_chapter", source_chapter)
|
||||
|
||||
prompt = self.resolve_and_compile(
|
||||
"evaluate-entity",
|
||||
["entities", "sources", "vsm-reference", "guidelines"],
|
||||
)
|
||||
if not prompt:
|
||||
print(f" FAILED to compile prompt for {slug}")
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
# Write prompt only when output does not yet exist (Task 5 fix)
|
||||
prompt_file = evaluations_dir / f"{slug}-eval-prompt.md"
|
||||
if not output_file.exists():
|
||||
prompt_file.write_text(prompt)
|
||||
|
||||
if not self.llm_adapter:
|
||||
print(f" {slug}: prompt written, awaiting manual evaluation")
|
||||
done += 1
|
||||
continue
|
||||
|
||||
print(f" Evaluating: {slug}...")
|
||||
content = self._execute_llm(prompt, output_file, f"eval:{slug}", max_tokens=1024)
|
||||
if content:
|
||||
done += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
total = done + skipped + failed
|
||||
print(f"\nEvaluation complete: {done} done, {skipped} skipped (existing), {failed} failed — {total} total")
|
||||
|
||||
# ── Chapter Processing ───────────────────────────────────────────
|
||||
|
||||
def process_chapter(self, chapter_id: str, auto_commit: bool = True):
|
||||
@@ -994,9 +1122,13 @@ def main():
|
||||
help="Run collection-level quality checks (C1-C5)")
|
||||
group.add_argument("--infospace-viability", action="store_true",
|
||||
help="Show viability dashboard")
|
||||
group.add_argument("--evaluate", action="store_true",
|
||||
help="Evaluate entity quality using the evaluate-entity template")
|
||||
|
||||
parser.add_argument("--reason", type=str, default=None,
|
||||
help="Reason for archiving (used with --archive-entity)")
|
||||
parser.add_argument("--eval-chapter", type=str, default=None, metavar="CHAPTER_ID",
|
||||
help="Limit --evaluate to entities from a specific chapter")
|
||||
parser.add_argument("--no-commit", action="store_true", help="Skip git commits")
|
||||
parser.add_argument(
|
||||
"--provider",
|
||||
@@ -1064,6 +1196,9 @@ def main():
|
||||
elif args.infospace_viability:
|
||||
_run_infospace_viability(example_dir)
|
||||
return
|
||||
elif args.evaluate:
|
||||
processor.evaluate_entities(chapter_id=args.eval_chapter)
|
||||
return
|
||||
|
||||
processor.show_stats()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user