SPB Git

spb/wp45_uqo

Public
5.5 KB · 99 lines python
1"""jof250 command-line interface (Typer)."""2from __future__ import annotations3import json, typer4from pathlib import Path5from typing import Optional6app = typer.Typer(help="JoF-250 Writing Engine — UQO WP45", no_args_is_help=True)7corpus_app = typer.Typer(help="Corpus inventory and parsing"); app.add_typer(corpus_app, name="corpus")8profiles_app = typer.Typer(help="Writing profiles"); app.add_typer(profiles_app, name="profiles")910@corpus_app.command("audit")11def corpus_audit(verify: bool = typer.Option(True, help="Verify DOIs against Crossref (network)")):12    """Phase 1: inventory, validate, deduplicate, verify and freeze the corpus manifest."""13    from . import inventory14    df, s = inventory.build(verify=verify)15    typer.echo(json.dumps(s, indent=1))1617@corpus_app.command("parse")18def corpus_parse(limit: Optional[int] = None, force: bool = False, doc: Optional[str] = None):19    """Phase 2: structured extraction of every included PDF (PyMuPDF)."""20    from . import parsing21    typer.echo(json.dumps(parsing.parse_corpus(limit=limit, force=force, only=doc), indent=1))2223@corpus_app.command("annotate")24def corpus_annotate(limit: Optional[int] = None, force: bool = False, doc: Optional[str] = None, workers: int = 4):25    """Phase 3: schema-validated LLM annotation of rhetorical structure (uses API; budget-capped)."""26    from . import rhetoric27    typer.echo(json.dumps(rhetoric.annotate_corpus(limit=limit, force=force, only=doc, workers=workers), indent=1))2829@profiles_app.command("build")30def profiles_build():31    """Phase 4: compute measured writing profiles (YAML) + figures from parsed/annotated corpus."""32    from . import features33    typer.echo(json.dumps(features.build_profiles(), indent=1, default=str))3435@app.command()36def index():37    """Phase 5: build the hybrid retrieval index (BM25 + local embeddings)."""38    from . import embeddings39    typer.echo(json.dumps(embeddings.build_index(), indent=1))4041@app.command()42def retrieve(topic: str = typer.Option(..., "--topic"), k: int = 8, section: Optional[str] = None, year: Optional[int] = None, unit: str = "annotation"):43    """Hybrid retrieval with provenance."""44    from . import retrieval45    for r in retrieval.search(topic, k=k, section=section, year=year, unit=unit):46        typer.echo(json.dumps(r, ensure_ascii=False))4748@app.command()49def write(paper: Path = typer.Option(..., "--paper"), section: str = typer.Option("introduction", "--section"), output: Optional[Path] = None, mode: str = typer.Option("write", help="outline|write|upgrade"), profile: Optional[str] = None, condition: str = "E_full_engine"):50    """Draft / outline / upgrade a manuscript section using the engine (proposes a new file, never edits the manuscript)."""51    from . import generation52    res = generation.run(paper=paper, section=section, mode=mode, output=output, profile=profile, condition=condition)53    typer.echo(json.dumps({k: v for k, v in res.items() if k != "text"}, indent=1, default=str))54    if not output: typer.echo(res["text"])5556@app.command()57def review(paper: Path = typer.Option(..., "--paper"), output: Optional[Path] = None, full: bool = True, role: str = "referee"):58    """Referee-style review of a manuscript (role: referee|editor|table_editor|integrity)."""59    from . import generation60    res = generation.review(paper=paper, output=output, role=role)61    typer.echo(json.dumps({k: v for k, v in res.items() if k != "text"}, indent=1, default=str))62    if not output: typer.echo(res["text"])6364@app.command()65def audit(paper: Path = typer.Option(..., "--paper"), tables: bool = True, equations: bool = True, citations: bool = True, overlap: bool = True, output: Optional[Path] = None):66    """Deterministic manuscript audit: tables, equations, citations/DOIs, numeric consistency, overlap with corpus."""67    from . import auditing68    rep = auditing.audit_manuscript(paper, tables=tables, equations=equations, citations=citations, overlap=overlap)69    txt = auditing.render_report(rep)70    if output: Path(output).write_text(txt); typer.echo(f"written {output}")71    else: typer.echo(txt)7273@app.command()74def evidence(paper: Path = typer.Option(..., "--paper"), claim: str = typer.Option(..., "--claim")):75    """Locate evidence for a claim inside the manuscript (tables, numbers, sections) and report support status."""76    from . import auditing77    typer.echo(json.dumps(auditing.evidence_for_claim(paper, claim), indent=1, ensure_ascii=False))7879@app.command()80def benchmark(config_path: Path = typer.Option(Path("config/evaluation.yaml"), "--config"), protocol: Optional[str] = None, dry_run: bool = False, tasks: Optional[str] = None, conditions: Optional[str] = None, reps: Optional[int] = None):81    """Run the preregistered evaluation protocol (A–E conditions) — logs cost, tokens, latency; never fabricates."""82    from . import evaluation83    typer.echo(json.dumps(evaluation.run_benchmark(config_path, protocol=protocol, dry_run=dry_run, tasks=tasks, conditions=conditions, reps=reps), indent=1, default=str))8485@app.command()86def status():87    """Show pipeline status (manifest, parsed, annotated, profiles, index, experiments)."""88    from . import reporting89    typer.echo(reporting.status_text())9091@app.command("install-claude-code")92def install_claude_code(scope: str = typer.Option("project", help="project|user"), force: bool = False):93    """Write Claude Code commands/skills/agents (project scope) or copy them to ~/.claude (user scope, explicit)."""94    from .integrations import claude_code95    typer.echo(json.dumps(claude_code.install(scope=scope, force=force), indent=1))9697if __name__ == "__main__":98    app()99
Files · 7ef3b9a9613136407bc3e07ab58a5795f2bfab55
Menu

Theme