From 94cb2063afd5b7034669d270913f4c7b4a9098ca Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 19 Feb 2026 02:29:53 +0100 Subject: [PATCH] feat(example): migrate to infospace config with tooling integration (S3.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add infospace.yaml declaring topic, disciplines, schemas, viability thresholds. Integrate infospace tooling into process_chapters.py with --infospace-status, --infospace-check, and --infospace-viability flags. Initial check: 85 entities, 4/5 viable (coverage 0.36 < 0.50 — only 7/35 chapters processed so far). Co-Authored-By: Claude Opus 4.6 --- .../infospace-with-history/infospace.yaml | 51 +++++++ .../process_chapters.py | 134 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 examples/infospace-with-history/infospace.yaml diff --git a/examples/infospace-with-history/infospace.yaml b/examples/infospace-with-history/infospace.yaml new file mode 100644 index 00000000..cfe0ba74 --- /dev/null +++ b/examples/infospace-with-history/infospace.yaml @@ -0,0 +1,51 @@ +# Infospace: The Wealth of Nations through the Viable System Model +# +# This configuration declares the infospace built by processing +# Adam Smith's "The Wealth of Nations" (1776) through the lens of +# Stafford Beer's Viable System Model (VSM). + +topic: + name: "The Wealth of Nations" + domain: "Classical Economics" + sources: artifacts/sources/ + +disciplines: + - name: "Viable System Model" + path: artifacts/vsm-reference/ + +schemas: + entity: schemas/economic-entity-schema-v1.0.md + mapping: schemas/vsm-mapping-schema-v1.0.md + analysis: schemas/chapter-analysis-schema-v1.0.md + +competency_questions: | + 1. How does Smith's division of labour map to VSM System 1 operations? + 2. What mechanisms in WoN correspond to VSM coordination (System 2)? + 3. Where does Smith describe self-organising regulation (System 3)? + 4. What role does the "invisible hand" play as a System 4 mechanism? + 5. How do Smith's views on government map to System 5 policy? + 6. Is the WoN entity set viable as an explanatory framework? + +viability: + redundancy_ratio: + max: 0.10 + coverage_ratio: + min: 0.50 + coherence_components: + max: 3 + consistency_cycles: + max: 0 + granularity_entropy: + min: 1.0 + +pipeline: + stages: + - name: extract-entities + template: templates/extract-entities.md + - name: map-to-vsm + template: templates/map-to-vsm.md + - name: synthesize-analysis + template: templates/synthesize-analysis.md + post_batch: + - name: assess-metrics + template: templates/assess-metrics.md diff --git a/examples/infospace-with-history/process_chapters.py b/examples/infospace-with-history/process_chapters.py index ce754a16..ca2e852b 100644 --- a/examples/infospace-with-history/process_chapters.py +++ b/examples/infospace-with-history/process_chapters.py @@ -856,6 +856,125 @@ class ChapterProcessor: print(f" (No data yet: {e})") +# ── Infospace tooling integration ───────────────────────────────── + + +def _load_infospace(example_dir: Path): + """Load infospace config and entities from the example directory.""" + from markitect.infospace.config import load_infospace_config + from markitect.infospace.entity_parser import parse_entity_directory + + config_path = example_dir / "infospace.yaml" + if not config_path.is_file(): + print("Error: No infospace.yaml found. Create one first.") + sys.exit(1) + + config = load_infospace_config(config_path) + entities_dir = example_dir / config.entities_dir + entities = parse_entity_directory(entities_dir) if entities_dir.is_dir() else [] + return config, config_path, entities + + +def _run_infospace_status(example_dir: Path): + """Show infospace status using the tooling layer.""" + from markitect.infospace.state import build_state + + config, config_path, entities = _load_infospace(example_dir) + state = build_state(config, entities=entities) + + print(f"Infospace: {state.topic_name}") + print(f"Domain: {config.topic.domain}") + print(f"Entities: {state.entity_count}") + if state.domains: + print(f"Domains: {', '.join(state.domains)}") + if config.disciplines: + names = [d.name for d in config.disciplines] + print(f"Disciplines: {', '.join(names)}") + + # Show processing progress + sources_dir = example_dir / "artifacts" / "sources" + total_chapters = len(list(sources_dir.glob("*.md"))) + processed = len(list((example_dir / "output" / "analyses").glob("*-analysis.md"))) + print(f"Chapters: {processed}/{total_chapters} processed") + + +def _run_infospace_check(example_dir: Path): + """Run collection-level quality checks.""" + from markitect.infospace.checks import run_all_checks + from markitect.infospace.history import record_check_results + + config, config_path, entities = _load_infospace(example_dir) + + if not entities: + print("No entities to check.") + return + + print(f"Running collection checks on {len(entities)} entities...\n") + report = run_all_checks(entities=entities) + + d = report.to_dict() + for concern_name, concern_data in d.items(): + label = concern_data.get("concern", concern_name.upper()) + print(f" {label} — {concern_name}") + for k, v in concern_data.items(): + if k == "concern": + continue + print(f" {k}: {v}") + print() + + m = report.metrics() + if m: + print("Metrics summary:") + for k, v in sorted(m.items()): + print(f" {k}: {v:.4f}") + snap = record_check_results(report, config, example_dir, entity_count=len(entities)) + print(f"\nRecorded snapshot {snap.snapshot_id}") + + +def _run_infospace_viability(example_dir: Path): + """Show viability dashboard.""" + from markitect.infospace.history import read_metrics_file + from markitect.infospace.state import build_state + + config, config_path, entities = _load_infospace(example_dir) + + if not config.viability: + print("No viability thresholds configured.") + return + + metrics = read_metrics_file(example_dir / config.metrics_dir / "metrics.yaml") + if not metrics: + print("No metrics available. Run --infospace-check first.") + print("\nConfigured thresholds:") + for name, t in config.viability.items(): + bounds = [] + if t.min is not None: + bounds.append(f"min={t.min}") + if t.max is not None: + bounds.append(f"max={t.max}") + print(f" {name}: {', '.join(bounds)}") + return + + state = build_state(config, entities=entities, metrics=metrics) + + print(f"{'Metric':<30} {'Value':>8} {'Threshold':>15} {'Status':>8}") + print("-" * 63) + for r in state.viability_results: + bounds = [] + if r.threshold.min is not None: + bounds.append(f"min={r.threshold.min}") + if r.threshold.max is not None: + bounds.append(f"max={r.threshold.max}") + status_str = "PASS" if r.passed else "FAIL" + print(f"{r.metric:<30} {r.value:>8.4f} {', '.join(bounds):>15} {status_str:>8}") + + print() + if state.is_viable: + print(f"Viable: YES ({state.viability_pass_count}/{state.viability_total_count} thresholds met)") + else: + print(f"Viable: NO ({state.viability_pass_count}/{state.viability_total_count} thresholds met)") + + def main(): parser = argparse.ArgumentParser( description="Process Wealth of Nations chapters through VSM analysis pipeline" @@ -869,6 +988,12 @@ def main(): group.add_argument("--stats", action="store_true", help="Show dependency statistics") group.add_argument("--archive-entity", type=str, metavar="SLUG", help="Archive an entity (move to archive/ with reason)") + group.add_argument("--infospace-status", action="store_true", + help="Show infospace status via infospace tooling") + group.add_argument("--infospace-check", action="store_true", + help="Run collection-level quality checks (C1-C5)") + group.add_argument("--infospace-viability", action="store_true", + help="Show viability dashboard") parser.add_argument("--reason", type=str, default=None, help="Reason for archiving (used with --archive-entity)") @@ -930,6 +1055,15 @@ def main(): for ch in chapters: processor.process_chapter(ch, auto_commit=not args.no_commit) print() + elif args.infospace_status: + _run_infospace_status(example_dir) + return + elif args.infospace_check: + _run_infospace_check(example_dir) + return + elif args.infospace_viability: + _run_infospace_viability(example_dir) + return processor.show_stats()