Spaces:
Runtime error
Runtime error
| """Generate human-readable claim extraction examples from real runs. | |
| Creates Markdown documentation showing: | |
| - Original generated text excerpts with block structure | |
| - Extracted claims with offsets, triggers, claim_kind, block_kind | |
| - Verification that all offsets are exact substrings | |
| Usage: | |
| python scripts/make_claim_examples.py --n 3 --out docs/claim_extraction_examples.md | |
| # With custom materials | |
| python scripts/make_claim_examples.py --prefer-materials digital_ad,faq,blog_post_promo | |
| # From specific claims directory | |
| python scripts/make_claim_examples.py --claims-dir analysis/claims --outputs-dir outputs | |
| """ | |
| import json | |
| import random | |
| import argparse | |
| from pathlib import Path | |
| from typing import List, Dict, Any, Optional, Tuple | |
| import csv | |
| def classify_text_kind(text: str) -> str: | |
| """Classify text as 'prompt', 'output', or 'unknown'. | |
| Uses deterministic heuristics to detect prompt/template language. | |
| Args: | |
| text: Text to classify | |
| Returns: | |
| One of: 'prompt', 'output', 'unknown' | |
| """ | |
| # Check first 500 chars for prompt markers | |
| header = text[:500].lower() | |
| # Prompt markers (high confidence) | |
| prompt_markers = [ | |
| "you are an elite content marketing strategist", | |
| "you are writing", | |
| "your mission:", | |
| "compliance framework", | |
| "authorized claims", | |
| "prohibited claims", | |
| "mandatory disclaimers", | |
| "output format", | |
| "before writing, mentally confirm", | |
| "hard rules:", | |
| "use only the information provided below", | |
| "do not invent or infer" | |
| ] | |
| for marker in prompt_markers: | |
| if marker in header: | |
| return "prompt" | |
| # Output markers (typical generated content) | |
| output_markers = [ | |
| "experience the", | |
| "discover", | |
| "introducing the", | |
| "## ", # Markdown headings in blog posts | |
| "headline:", | |
| "primary text:", | |
| "description:", | |
| "**q", # FAQ questions | |
| ] | |
| has_output_markers = any(marker in header for marker in output_markers) | |
| # If it has output markers and no prompt markers, it's likely output | |
| if has_output_markers: | |
| return "output" | |
| return "unknown" | |
| def find_output_text(run_id: str, outputs_dir: Path) -> Optional[Path]: | |
| """Find output text file for a run_id using discovery strategy. | |
| Prioritizes actual generated outputs over prompts/templates. | |
| Args: | |
| run_id: Run identifier | |
| outputs_dir: Root outputs directory | |
| Returns: | |
| Path to output file or None if not found | |
| """ | |
| # Strategy A: Look for per_run.json artifacts (most reliable) | |
| per_run_json = Path("analysis/per_run.json") | |
| if per_run_json.exists(): | |
| try: | |
| with open(per_run_json, 'r') as f: | |
| per_run_data = json.load(f) | |
| for record in per_run_data: | |
| if record.get('run_id') == run_id: | |
| artifacts = record.get('artifacts', {}) | |
| output_path = artifacts.get('output_path') | |
| if output_path and Path(output_path).exists(): | |
| text = Path(output_path).read_text(encoding='utf-8') | |
| if classify_text_kind(text) == "output": | |
| return Path(output_path) | |
| except (json.JSONDecodeError, KeyError): | |
| pass | |
| # Strategy B: Direct lookup with _output.txt suffix (common pattern) | |
| output_patterns = [ | |
| outputs_dir / f"{run_id}_output.txt", | |
| outputs_dir / f"{run_id}.txt", | |
| ] | |
| for path in output_patterns: | |
| if path.exists(): | |
| text = path.read_text(encoding='utf-8') | |
| kind = classify_text_kind(text) | |
| if kind == "output": | |
| return path | |
| elif kind == "prompt": | |
| continue # Skip prompts, keep searching | |
| # Strategy C: Search known output directories | |
| output_dirs = [ | |
| outputs_dir, | |
| outputs_dir / "comprehensive_test" / "test_b_materials", | |
| Path("results/outputs"), | |
| Path("results/text"), | |
| ] | |
| for out_dir in output_dirs: | |
| if not out_dir.exists(): | |
| continue | |
| # Look for files matching run_id pattern | |
| for pattern in [f"{run_id}_output.txt", f"{run_id}.txt", f"*{run_id[:12]}*.txt"]: | |
| matches = list(out_dir.glob(pattern)) | |
| for match in matches: | |
| # Skip prompt files | |
| if "prompt" in match.name.lower(): | |
| continue | |
| text = match.read_text(encoding='utf-8') | |
| kind = classify_text_kind(text) | |
| if kind == "output": | |
| return match | |
| # Strategy D: Bounded recursive search (cap at 200 files) | |
| all_txt_files = list(outputs_dir.glob("**/*.txt"))[:200] | |
| for file_path in all_txt_files: | |
| # Skip obvious prompt files | |
| if "prompt" in file_path.name.lower(): | |
| continue | |
| # Check if filename contains run_id | |
| if run_id[:12] in file_path.name or run_id in file_path.name: | |
| text = file_path.read_text(encoding='utf-8') | |
| kind = classify_text_kind(text) | |
| if kind == "output": | |
| return file_path | |
| return None | |
| def load_or_generate_claims( | |
| run_id: str, | |
| output_path: Optional[Path], | |
| claims_dir: Path | |
| ) -> List[Dict[str, Any]]: | |
| """Load existing claims or generate from output text. | |
| Args: | |
| run_id: Run identifier | |
| output_path: Path to output text (if found) | |
| claims_dir: Directory containing claim JSONs | |
| Returns: | |
| List of claim records | |
| """ | |
| # Try loading existing claims | |
| claims_file = claims_dir / f"{run_id}.json" | |
| if claims_file.exists(): | |
| with open(claims_file, 'r', encoding='utf-8') as f: | |
| claims = json.load(f) | |
| # Filter to ensure we have v2.0 claims with block_kind | |
| if claims and all('block_kind' in c for c in claims): | |
| return claims | |
| # Generate claims if output text exists | |
| if output_path and output_path.exists(): | |
| # Import claim extractor | |
| import sys | |
| sys.path.insert(0, '.') | |
| from analysis.claim_extractor import extract_claim_candidates | |
| full_text = output_path.read_text(encoding='utf-8') | |
| # Infer material type from path or use unknown | |
| material_type = "unknown" | |
| if "digital_ad" in str(output_path) or "ad" in str(output_path): | |
| material_type = "digital_ad.j2" | |
| elif "faq" in str(output_path): | |
| material_type = "faq.j2" | |
| elif "blog" in str(output_path): | |
| material_type = "blog_post_promo.j2" | |
| run_metadata = { | |
| "run_id": run_id, | |
| "product_id": "unknown", | |
| "material_type": material_type, | |
| "engine": "unknown", | |
| "temperature": 0.6, | |
| "time_of_day": "unknown", | |
| "repetition_id": 1 | |
| } | |
| claims = extract_claim_candidates(full_text, run_metadata, include_meta=False) | |
| return claims | |
| return [] | |
| def select_example_runs( | |
| outputs_dir: Path, | |
| claims_dir: Path, | |
| n: int, | |
| prefer_materials: List[str], | |
| seed: int, | |
| require_block_kinds: bool | |
| ) -> List[Tuple[str, Path, List[Dict[str, Any]], str]]: | |
| """Select representative runs for examples. | |
| Args: | |
| outputs_dir: Outputs directory | |
| claims_dir: Claims directory | |
| n: Number of examples | |
| prefer_materials: Preferred material types | |
| seed: Random seed | |
| require_block_kinds: If True, only select v2.0 claims | |
| Returns: | |
| List of (run_id, output_path, claims, material_type) tuples | |
| """ | |
| random.seed(seed) | |
| # Scan outputs directory for _output.txt files (skip prompts) | |
| output_files = [ | |
| f for f in outputs_dir.glob("*.txt") | |
| if "prompt" not in f.name.lower() | |
| ] | |
| # Also check for comprehensive test outputs (skip prompts) | |
| comprehensive_outputs = [ | |
| f for f in outputs_dir.glob("comprehensive_test/**/*.txt") | |
| if "prompt" not in f.name.lower() | |
| ] | |
| all_outputs = output_files + comprehensive_outputs | |
| random.shuffle(all_outputs) | |
| selected = [] | |
| materials_found = set() | |
| for output_path in all_outputs: | |
| if len(selected) >= n: | |
| break | |
| # Verify this is an output, not a prompt | |
| text = output_path.read_text(encoding='utf-8') | |
| text_kind = classify_text_kind(text) | |
| if text_kind == "prompt": | |
| continue # Skip prompts | |
| # Infer material type from path | |
| path_str = str(output_path).lower() | |
| material_type = None | |
| if any(m in path_str for m in ["digital_ad", "ad_output"]): | |
| material_type = "digital_ad" | |
| elif "faq" in path_str: | |
| material_type = "faq" | |
| elif "blog" in path_str: | |
| material_type = "blog_post" | |
| # Skip if we already have this material type | |
| if material_type and material_type in materials_found: | |
| continue | |
| # Try to extract run_id from filename | |
| filename = output_path.stem | |
| run_id = filename.replace("_output", "").replace("_prompt", "") | |
| # Load or generate claims | |
| claims = load_or_generate_claims(run_id, output_path, claims_dir) | |
| # Filter by requirements | |
| if require_block_kinds and claims: | |
| if not all('block_kind' in c and 'claim_kind' in c for c in claims): | |
| continue | |
| # Skip if no claims | |
| if not claims: | |
| continue | |
| # Add to selected | |
| if material_type: | |
| selected.append((run_id, output_path, claims, material_type)) | |
| materials_found.add(material_type) | |
| return selected | |
| def extract_claim_aware_excerpt( | |
| full_text: str, | |
| claims: List[Dict[str, Any]], | |
| max_chars: int | |
| ) -> str: | |
| """Extract excerpt using claim offsets to show relevant context. | |
| Args: | |
| full_text: Full text | |
| claims: List of claim records with char_span | |
| max_chars: Maximum characters | |
| Returns: | |
| Excerpt showing claims in context | |
| """ | |
| if len(full_text) <= max_chars: | |
| return full_text | |
| # Find claim span range | |
| if claims: | |
| char_spans = [c.get('char_span', (0, 0)) for c in claims if c.get('char_span')] | |
| if char_spans: | |
| min_start = min(s[0] for s in char_spans) | |
| max_end = max(s[1] for s in char_spans) | |
| # Expand window by Β±200 chars | |
| window_start = max(0, min_start - 200) | |
| window_end = min(len(full_text), max_end + 200) | |
| # If window is still too large, truncate | |
| if window_end - window_start <= max_chars: | |
| return full_text[window_start:window_end] | |
| # Fallback: show beginning and end | |
| half = max_chars // 2 - 50 | |
| return full_text[:half] + "\n\n[... middle section omitted ...]\n\n" + full_text[-half:] | |
| def truncate_text(text: str, max_chars: int) -> str: | |
| """Truncate text to max_chars with ellipsis if needed. | |
| Args: | |
| text: Input text | |
| max_chars: Maximum characters | |
| Returns: | |
| Truncated text | |
| """ | |
| if len(text) <= max_chars: | |
| return text | |
| # Show beginning and end | |
| half = max_chars // 2 - 50 | |
| return text[:half] + "\n\n[... middle section omitted ...]\n\n" + text[-half:] | |
| def format_claim_table_row(claim: Dict[str, Any]) -> str: | |
| """Format a claim as a Markdown table row. | |
| Args: | |
| claim: Claim record | |
| Returns: | |
| Markdown table row | |
| """ | |
| claim_id = claim.get('claim_id', 'N/A') | |
| claim_kind = claim.get('claim_kind', 'N/A') | |
| block_kind = claim.get('block_kind', 'N/A') | |
| triggers = ', '.join(claim.get('trigger_types', [])) | |
| char_span = claim.get('char_span', (0, 0)) | |
| sentence = claim.get('sentence', '').replace('|', '\\|').replace('\n', ' ')[:80] | |
| return f"| `{claim_id[:20]}...` | {claim_kind} | {block_kind} | {triggers} | {char_span} | {sentence}... |" | |
| def verify_offsets( | |
| claims: List[Dict[str, Any]], | |
| full_text: str | |
| ) -> Tuple[int, List[str]]: | |
| """Verify that all claim char_spans are exact substrings. | |
| Args: | |
| claims: List of claim records | |
| full_text: Original full text | |
| Returns: | |
| (num_verified, warnings) tuple | |
| """ | |
| verified = 0 | |
| warnings = [] | |
| for claim in claims: | |
| sentence = claim.get('sentence', '') | |
| char_span = claim.get('char_span') | |
| if not char_span: | |
| warnings.append(f"Claim {claim.get('claim_id')} missing char_span") | |
| continue | |
| start, end = char_span | |
| if start >= len(full_text) or end > len(full_text): | |
| warnings.append(f"Claim {claim.get('claim_id')} has out-of-bounds char_span: {char_span}") | |
| continue | |
| extracted = full_text[start:end] | |
| if extracted == sentence: | |
| verified += 1 | |
| else: | |
| warnings.append( | |
| f"Claim {claim.get('claim_id')} char_span mismatch:\n" | |
| f" Expected: {sentence[:50]}...\n" | |
| f" Got: {extracted[:50]}..." | |
| ) | |
| return verified, warnings | |
| def generate_markdown_example( | |
| run_id: str, | |
| output_path: Optional[Path], | |
| claims: List[Dict[str, Any]], | |
| material_type: str, | |
| max_excerpt_chars: int, | |
| max_claims: int, | |
| example_num: int | |
| ) -> str: | |
| """Generate Markdown section for one example. | |
| Args: | |
| run_id: Run identifier | |
| output_path: Path to output text (or None if not found) | |
| claims: List of claim records | |
| material_type: Material type name | |
| max_excerpt_chars: Max chars for text excerpt | |
| max_claims: Max claims to show | |
| example_num: Example number (1, 2, 3, ...) | |
| Returns: | |
| Markdown string | |
| """ | |
| md = [] | |
| md.append(f"## Example {example_num} β {material_type.replace('_', ' ').title()}") | |
| # Extract metadata from first claim | |
| if claims: | |
| product = claims[0].get('product', 'unknown') | |
| engine = claims[0].get('engine', 'unknown') | |
| extractor_version = claims[0].get('extractor_version', 'unknown') | |
| md.append(f"**Run ID:** `{run_id}` ") | |
| md.append(f"**Product:** {product} | **Engine:** {engine} ") | |
| md.append(f"**Extractor:** {extractor_version}") | |
| else: | |
| md.append(f"**Run ID:** `{run_id}`") | |
| md.append("") | |
| # Text excerpt | |
| if output_path and output_path.exists(): | |
| full_text = output_path.read_text(encoding='utf-8') | |
| # Verify this is actually output, not a prompt | |
| text_kind = classify_text_kind(full_text) | |
| if text_kind == "prompt": | |
| md.append("### Generated Text Excerpt") | |
| md.append("") | |
| md.append("β οΈ **WARNING:** Located file appears to be a prompt/template, not generated output.") | |
| md.append("Showing extracted claims only (offsets may not match).") | |
| md.append("") | |
| else: | |
| # Use claim-aware excerpt to show relevant context | |
| excerpt = extract_claim_aware_excerpt(full_text, claims, max_excerpt_chars) | |
| md.append("### Generated Text Excerpt (Verbatim Model Output)") | |
| md.append("") | |
| md.append("```") | |
| md.append(excerpt) | |
| md.append("```") | |
| md.append("") | |
| # Verify offsets | |
| verified, warnings = verify_offsets(claims, full_text) | |
| if warnings: | |
| md.append("**Offset Verification:**") | |
| md.append(f"- {verified}/{len(claims)} claims verified") | |
| if warnings: | |
| md.append(f"- {len(warnings)} warnings (see debug output)") | |
| md.append("") | |
| else: | |
| md.append("### Generated Text Excerpt") | |
| md.append("") | |
| md.append("β οΈ **Generated output text file not found for this run_id.**") | |
| md.append("") | |
| md.append("_Showing extracted claims only (from analysis/claims/*.json)._") | |
| md.append("") | |
| # Claims table | |
| md.append("### Extracted Claims (Verbatim)") | |
| md.append("") | |
| if claims: | |
| # Show up to max_claims | |
| display_claims = claims[:max_claims] | |
| md.append("| Claim ID | Claim Kind | Block Kind | Triggers | Char Span | Sentence |") | |
| md.append("|----------|------------|------------|----------|-----------|----------|") | |
| for claim in display_claims: | |
| md.append(format_claim_table_row(claim)) | |
| md.append("") | |
| # Summary stats | |
| product_claims = sum(1 for c in claims if c.get('claim_kind') == 'product_claim') | |
| disclaimer_claims = sum(1 for c in claims if c.get('claim_kind') == 'disclaimer') | |
| meta_claims = sum(1 for c in claims if c.get('claim_kind') == 'meta') | |
| md.append("**Summary:**") | |
| md.append(f"- Total extracted claims: {len(claims)}") | |
| md.append(f"- Product claims: {product_claims}") | |
| md.append(f"- Disclaimer claims: {disclaimer_claims}") | |
| md.append(f"- Meta claims: {meta_claims}") | |
| if len(claims) > max_claims: | |
| md.append(f"- _(Showing {max_claims} of {len(claims)} claims)_") | |
| md.append("- **Note:** All sentences are exact substrings (offset-traceable)") | |
| else: | |
| md.append("_No claims extracted_") | |
| md.append("") | |
| md.append("---") | |
| md.append("") | |
| return '\n'.join(md) | |
| def write_json_preview( | |
| run_id: str, | |
| claims: List[Dict[str, Any]], | |
| out_dir: Path, | |
| max_claims: int = 2 | |
| ) -> Optional[Path]: | |
| """Write JSON preview of claims for technical appendix. | |
| Args: | |
| run_id: Run identifier | |
| claims: List of claim records | |
| out_dir: Output directory (docs/examples/) | |
| max_claims: Max claims to include | |
| Returns: | |
| Path to JSON file or None | |
| """ | |
| if not claims: | |
| return None | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| json_file = out_dir / f"run_{run_id[:12]}_claims_preview.json" | |
| preview_claims = claims[:max_claims] | |
| with open(json_file, 'w', encoding='utf-8') as f: | |
| json.dump(preview_claims, f, indent=2, ensure_ascii=False) | |
| return json_file | |
| def main(): | |
| """Main entry point.""" | |
| parser = argparse.ArgumentParser( | |
| description="Generate claim extraction examples from real runs" | |
| ) | |
| parser.add_argument( | |
| '--claims-dir', | |
| default='analysis/claims', | |
| help='Directory containing claim JSONs' | |
| ) | |
| parser.add_argument( | |
| '--outputs-dir', | |
| default='outputs', | |
| help='Directory containing output text files' | |
| ) | |
| parser.add_argument( | |
| '--n', | |
| type=int, | |
| default=3, | |
| help='Number of examples to generate' | |
| ) | |
| parser.add_argument( | |
| '--prefer-materials', | |
| default='digital_ad,faq,blog_post', | |
| help='Comma-separated material types to prefer' | |
| ) | |
| parser.add_argument( | |
| '--out', | |
| default='docs/claim_extraction_examples.md', | |
| help='Output Markdown file' | |
| ) | |
| parser.add_argument( | |
| '--seed', | |
| type=int, | |
| default=42, | |
| help='Random seed for deterministic selection' | |
| ) | |
| parser.add_argument( | |
| '--max-excerpt-chars', | |
| type=int, | |
| default=900, | |
| help='Max characters for text excerpt' | |
| ) | |
| parser.add_argument( | |
| '--max-claims', | |
| type=int, | |
| default=6, | |
| help='Max claims to show per example' | |
| ) | |
| parser.add_argument( | |
| '--require-block-kinds', | |
| action='store_true', | |
| default=True, | |
| help='Require v2.0 claims with block_kind/claim_kind' | |
| ) | |
| parser.add_argument( | |
| '--write-json-previews', | |
| action='store_true', | |
| help='Write JSON previews to docs/examples/' | |
| ) | |
| args = parser.parse_args() | |
| # Parse preferred materials | |
| prefer_materials = [m.strip() for m in args.prefer_materials.split(',')] | |
| # Setup paths | |
| claims_dir = Path(args.claims_dir) | |
| outputs_dir = Path(args.outputs_dir) | |
| out_file = Path(args.out) | |
| print("Claim Extraction Example Generator") | |
| print("=" * 60) | |
| print(f"Claims directory: {claims_dir}") | |
| print(f"Outputs directory: {outputs_dir}") | |
| print(f"Preferred materials: {prefer_materials}") | |
| print(f"Output file: {out_file}") | |
| print() | |
| # Create claims dir if it doesn't exist (for on-the-fly generation) | |
| claims_dir.mkdir(parents=True, exist_ok=True) | |
| # Select examples | |
| print("Selecting example runs...") | |
| selected = select_example_runs( | |
| outputs_dir=outputs_dir, | |
| claims_dir=claims_dir, | |
| n=args.n, | |
| prefer_materials=prefer_materials, | |
| seed=args.seed, | |
| require_block_kinds=args.require_block_kinds | |
| ) | |
| if not selected: | |
| print("ERROR: No suitable examples found!") | |
| print(" - Check that outputs_dir contains .txt files") | |
| print(" - Or generate claims first: python -m analysis.evaluate") | |
| return 1 | |
| print(f"β Selected {len(selected)} examples") | |
| print() | |
| # Self-checks | |
| print("Self-checks:") | |
| for run_id, output_path, claims, material_type in selected: | |
| print(f" - Run {run_id[:12]}: {material_type}") | |
| print(f" Output text: {'β found' if output_path and output_path.exists() else 'β not found'}") | |
| if claims: | |
| extractor_version = claims[0].get('extractor_version', 'unknown') | |
| print(f" Extractor version: {extractor_version}") | |
| print(f" Claims count: {len(claims)}") | |
| # Verify offsets if text available | |
| if output_path and output_path.exists(): | |
| full_text = output_path.read_text(encoding='utf-8') | |
| verified, warnings = verify_offsets(claims, full_text) | |
| if warnings: | |
| print(f" β Offset warnings: {len(warnings)}") | |
| for warning in warnings[:2]: # Show first 2 | |
| print(f" {warning.split(chr(10))[0]}") | |
| else: | |
| print(f" β All {verified} offsets verified") | |
| print() | |
| # Generate Markdown | |
| print("Generating Markdown examples...") | |
| out_file.parent.mkdir(parents=True, exist_ok=True) | |
| with open(out_file, 'w', encoding='utf-8') as f: | |
| # Header | |
| f.write("# Claim Extraction Examples\n\n") | |
| f.write("Real examples from the LLM research pipeline, showing structure-aware claim extraction (v2.0).\n\n") | |
| f.write("**All excerpts below are verbatim segments of model-generated outputs (not prompts).** \n") | |
| f.write("Claims are exact substrings; offsets are shown for traceability.\n\n") | |
| f.write("**Features demonstrated:**\n") | |
| f.write("- Block-aware parsing (headlines, Q/A, disclaimers)\n") | |
| f.write("- Claim kind tagging (product_claim vs disclaimer)\n") | |
| f.write("- Anchor-based trigger detection (numeric, guarantee, medical, financial, comparative)\n") | |
| f.write("- Exact char_span offsets (all sentences are verifiable substrings)\n\n") | |
| f.write("---\n\n") | |
| # Examples | |
| for i, (run_id, output_path, claims, material_type) in enumerate(selected, 1): | |
| example_md = generate_markdown_example( | |
| run_id=run_id, | |
| output_path=output_path, | |
| claims=claims, | |
| material_type=material_type, | |
| max_excerpt_chars=args.max_excerpt_chars, | |
| max_claims=args.max_claims, | |
| example_num=i | |
| ) | |
| f.write(example_md) | |
| # Optional JSON preview | |
| if args.write_json_previews and claims: | |
| json_path = write_json_preview( | |
| run_id=run_id, | |
| claims=claims, | |
| out_dir=out_file.parent / "examples", | |
| max_claims=2 | |
| ) | |
| if json_path: | |
| print(f" β JSON preview: {json_path}") | |
| print(f"β Generated examples: {out_file}") | |
| print() | |
| # Final sanity report | |
| print("=" * 60) | |
| print("SANITY REPORT") | |
| print("=" * 60) | |
| print() | |
| all_passed = True | |
| for i, (run_id, output_path, claims, material_type) in enumerate(selected, 1): | |
| print(f"Example {i} β {material_type}") | |
| print(f" Run ID: {run_id[:20]}...") | |
| print(f" Output path: {output_path if output_path else 'NOT FOUND'}") | |
| if output_path and output_path.exists(): | |
| full_text = output_path.read_text(encoding='utf-8') | |
| text_kind = classify_text_kind(full_text) | |
| print(f" Text kind: {text_kind} {'β (expected: output)' if text_kind == 'output' else 'β WARNING'}") | |
| if text_kind == "prompt": | |
| print(f" β FAILED: Found prompt instead of output!") | |
| all_passed = False | |
| # Check extractor version | |
| if claims: | |
| extractor_version = claims[0].get('extractor_version', 'unknown') | |
| print(f" Extractor version: {extractor_version} {'β' if extractor_version == 'v2.0' else 'β '}") | |
| # Verify offsets | |
| verified, warnings = verify_offsets(claims, full_text) | |
| match_rate = verified / len(claims) if claims else 0 | |
| print(f" Offset match rate: {verified}/{len(claims)} ({match_rate:.1%})") | |
| if match_rate < 0.98: | |
| print(f" β WARNING: Match rate below 98%!") | |
| if warnings: | |
| print(f" First mismatch: {warnings[0][:80]}...") | |
| all_passed = False | |
| else: | |
| print(f" β All offsets verified") | |
| else: | |
| print(f" β WARNING: Output file not found") | |
| all_passed = False | |
| print() | |
| print("=" * 60) | |
| if all_passed: | |
| print("β ALL SANITY CHECKS PASSED") | |
| else: | |
| print("β SOME SANITY CHECKS FAILED - Review warnings above") | |
| print("=" * 60) | |
| print() | |
| print("β Done! Examples ready for documentation.") | |
| return 0 if all_passed else 1 | |
| if __name__ == "__main__": | |
| import sys | |
| sys.exit(main()) | |