Apply ZIP update: 130-didactopus-semantic-qa-update.zip [2026-03-14T13:19:13]

This commit is contained in:
welsberr 2026-03-14 13:29:55 -04:00
parent 943f65e0e1
commit c049fc4d86
18 changed files with 94 additions and 81 deletions

View File

@ -1,33 +1,13 @@
concepts:
- id: a
title: Foundations
description: Introductory foundations concept with broad scope.
prerequisites: [c]
- id: b
title: Advanced Analysis
description: Advanced analysis of results in a difficult domain.
prerequisites: [a]
- id: c
title: Methods
description: Methods and procedures for analysis in context.
prerequisites: [b]
- id: isolated
title: Isolated Topic
description: A topic disconnected from the rest of the graph.
- id: prior-and-posterior
title: Prior and Posterior
description: Beliefs before and after evidence.
prerequisites: []
- id: bottleneck
title: Core Bottleneck
description: Central concept required by many dependents in the pack.
- id: posterior-analysis
title: Posterior Analysis
description: Beliefs before and after evidence.
prerequisites: []
- id: statistics-and-probability
title: Statistics and Probability
description: General overview.
prerequisites: []
- id: d1
title: Dependent One
description: Depends on a single core bottleneck concept.
prerequisites: [bottleneck]
- id: d2
title: Dependent Two
description: Depends on a single core bottleneck concept.
prerequisites: [bottleneck]
- id: d3
title: Dependent Three
description: Depends on a single core bottleneck concept.
prerequisites: [bottleneck]

View File

@ -1,13 +1,9 @@
stages:
- id: stage-1
title: Foundations
concepts: [a, bottleneck]
concepts:
- statistics-and-probability
- id: stage-2
title: Advanced Analysis
concepts: [b, d1]
- id: stage-3
title: Methods
concepts: [c, d2, d3]
- id: stage-4
title: Detached Topic
concepts: [isolated]
title: Advanced Inference
concepts:
- posterior-analysis

View File

@ -1 +1,5 @@
rubrics: []
rubrics:
- id: basic-rubric
title: Basic Rubric
criteria:
- correctness

View File

@ -1,24 +1,24 @@
# FAQ
## Why add graph-aware analysis?
## Why add semantic QA?
Because Didactopus is fundamentally concerned with mastery paths and dependency structure.
A pack can look fine in text form and still behave badly as a learning graph.
## What kinds of issues can it find?
Examples:
- cycles
- isolated concepts
- dependency bottlenecks
- packs that are too flat
- packs that are too deep
Because a pack can be structurally valid and still be awkward or misleading as a
learning domain.
## How does this help with the activation-energy problem?
It reduces the chance that a user imports a pack and only later discovers that the
prerequisite structure is confusing or fragile.
It catches likely high-level issues earlier, so users do not have to discover
them only after they have already committed to review or study.
## Does this replace human review?
## Does semantic QA prove that a pack is good?
No. It gives the reviewer better signals earlier.
No. It is a heuristic curation aid.
## What kinds of problems can it flag?
Examples:
- duplicate or near-duplicate concepts
- over-broad concepts
- abrupt stage transitions
- weak prerequisite structure
- descriptions that are too similar to each other

View File

@ -3,13 +3,19 @@ concepts:
title: Bayes Prior
description: Prior beliefs before evidence in a probabilistic model.
prerequisites: []
mastery_signals:
- Explain a prior distribution.
- id: bayes-posterior
title: Bayes Posterior
description: Updated beliefs after evidence in a probabilistic model.
prerequisites:
- bayes-prior
mastery_signals:
- Compare prior and posterior beliefs.
- id: model-checking
title: Model Checking
description: Evaluate whether model assumptions and fit remain plausible.
prerequisites:
- bayes-posterior
mastery_signals:
- Critique a model fit.

View File

@ -1 +1,8 @@
projects: []
projects:
- id: compare-beliefs
title: Compare Prior and Posterior
prerequisites:
- bayes-prior
- bayes-posterior
deliverables:
- short report

View File

@ -1,10 +1,13 @@
stages:
- id: stage-1
title: Prior Beliefs
concepts: [bayes-prior]
concepts:
- bayes-prior
- id: stage-2
title: Posterior Updating
concepts: [bayes-posterior]
concepts:
- bayes-posterior
- id: stage-3
title: Model Checking
concepts: [model-checking]
concepts:
- model-checking

View File

@ -1 +1,6 @@
rubrics: []
rubrics:
- id: basic-rubric
title: Basic Rubric
criteria:
- correctness
- explanation

View File

@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "didactopus"
version = "0.1.0"
description = "Didactopus: graph-aware prerequisite analysis"
description = "Didactopus: semantic QA layer for domain packs"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}

View File

@ -3,12 +3,10 @@ from pathlib import Path
from .review_schema import ImportPreview
from .pack_validator import validate_pack_directory
from .semantic_qa import semantic_qa_for_pack
from .graph_qa import graph_qa_for_pack
def preview_draft_pack_import(source_dir: str | Path, workspace_id: str, overwrite_required: bool = False) -> ImportPreview:
result = validate_pack_directory(source_dir)
semantic = semantic_qa_for_pack(source_dir) if result["ok"] else {"warnings": [], "summary": {}}
graph = graph_qa_for_pack(source_dir) if result["ok"] else {"warnings": [], "summary": {}}
preview = ImportPreview(
source_dir=str(Path(source_dir)),
workspace_id=workspace_id,
@ -18,6 +16,5 @@ def preview_draft_pack_import(source_dir: str | Path, workspace_id: str, overwri
warnings=list(result["warnings"]),
summary=dict(result["summary"]),
semantic_warnings=list(semantic["warnings"]),
graph_warnings=list(graph["warnings"]),
)
return preview

View File

@ -120,7 +120,7 @@ class ReviewBridgeHandler(BaseHTTPRequestHandler):
json_response(self, 404, {"error": "not found"})
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Didactopus local review bridge server with graph QA")
parser = argparse.ArgumentParser(description="Didactopus local review bridge server with semantic QA")
parser.add_argument("--config", default="configs/config.example.yaml")
return parser

View File

@ -56,4 +56,3 @@ class ImportPreview(BaseModel):
warnings: list[str] = Field(default_factory=list)
summary: dict = Field(default_factory=dict)
semantic_warnings: list[str] = Field(default_factory=list)
graph_warnings: list[str] = Field(default_factory=list)

View File

@ -1,4 +1,5 @@
from __future__ import annotations
from pathlib import Path
import re
from difflib import SequenceMatcher
from .pack_validator import load_pack_artifacts
@ -14,7 +15,7 @@ def similarity(a: str, b: str) -> float:
def token_set(text: str) -> set[str]:
return {t for t in normalize_title(text).split() if t}
def semantic_qa_for_pack(source_dir) -> dict:
def semantic_qa_for_pack(source_dir: str | Path) -> dict:
loaded = load_pack_artifacts(source_dir)
if not loaded["ok"]:
return {"warnings": [], "summary": {"semantic_warning_count": 0}}
@ -25,6 +26,7 @@ def semantic_qa_for_pack(source_dir) -> dict:
warnings: list[str] = []
# Near-duplicate titles
for i in range(len(concepts)):
for j in range(i + 1, len(concepts)):
a = concepts[i]
@ -33,6 +35,7 @@ def semantic_qa_for_pack(source_dir) -> dict:
if sim >= 0.86 and a.get("id") != b.get("id"):
warnings.append(f"Near-duplicate concept titles: '{a.get('title')}' vs '{b.get('title')}'")
# Over-broad titles
for concept in concepts:
title = concept.get("title", "")
toks = token_set(title)
@ -41,6 +44,7 @@ def semantic_qa_for_pack(source_dir) -> dict:
if " and " in title.lower():
warnings.append(f"Concept '{title}' is compound and may combine multiple ideas.")
# Similar descriptions
for i in range(len(concepts)):
for j in range(i + 1, len(concepts)):
da = str(concepts[i].get("description", "") or "")
@ -52,12 +56,14 @@ def semantic_qa_for_pack(source_dir) -> dict:
f"Concept descriptions are very similar: '{concepts[i].get('title')}' vs '{concepts[j].get('title')}'"
)
# Thin prerequisite chains on advanced-sounding concepts
for concept in concepts:
title = normalize_title(concept.get("title", ""))
prereqs = concept.get("prerequisites", []) or []
if any(h in title for h in ["advanced", "posterior", "model", "inference", "analysis"]) and len(prereqs) == 0:
warnings.append(f"Concept '{concept.get('title')}' looks advanced but has no prerequisites.")
# Missing bridge concepts between roadmap stages
concept_by_id = {c.get("id"): c for c in concepts if c.get("id")}
for idx in range(len(roadmap) - 1):
current_stage = roadmap[idx]
@ -78,5 +84,8 @@ def semantic_qa_for_pack(source_dir) -> dict:
return {
"warnings": warnings,
"summary": {"semantic_warning_count": len(warnings), "pack_name": pack.get("name", "")},
"summary": {
"semantic_warning_count": len(warnings),
"pack_name": pack.get("name", ""),
},
}

View File

@ -1,14 +1,14 @@
from pathlib import Path
from didactopus.import_validator import preview_draft_pack_import
def test_preview_includes_graph_warnings(tmp_path: Path) -> None:
def test_preview_includes_semantic_warnings(tmp_path: Path) -> None:
(tmp_path / "pack.yaml").write_text("name: p\ndisplay_name: P\nversion: 0.1.0\n", encoding="utf-8")
(tmp_path / "concepts.yaml").write_text(
"concepts:\n - id: a\n title: A\n description: description long enough\n prerequisites: [b]\n - id: b\n title: B\n description: description long enough\n prerequisites: [a]\n",
"concepts:\n - id: c1\n title: Prior and Posterior\n description: Beliefs before and after evidence.\n - id: c2\n title: Posterior Analysis\n description: Beliefs before and after evidence.\n",
encoding="utf-8"
)
(tmp_path / "roadmap.yaml").write_text("stages:\n - id: s1\n title: One\n concepts: [a,b]\n", encoding="utf-8")
(tmp_path / "roadmap.yaml").write_text("stages:\n - id: s1\n title: Foundations\n concepts: [c1]\n - id: s2\n title: Advanced Inference\n concepts: [c2]\n", encoding="utf-8")
(tmp_path / "projects.yaml").write_text("projects: []\n", encoding="utf-8")
(tmp_path / "rubrics.yaml").write_text("rubrics: []\n", encoding="utf-8")
preview = preview_draft_pack_import(tmp_path, "ws1")
assert isinstance(preview.graph_warnings, list)
assert isinstance(preview.semantic_warnings, list)

View File

@ -5,7 +5,7 @@ def test_valid_pack(tmp_path: Path) -> None:
(tmp_path / "pack.yaml").write_text("name: p\ndisplay_name: P\nversion: 0.1.0\n", encoding="utf-8")
(tmp_path / "concepts.yaml").write_text("concepts:\n - id: c1\n title: C1\n description: A full enough description.\n", encoding="utf-8")
(tmp_path / "roadmap.yaml").write_text("stages:\n - id: s1\n title: S1\n concepts: [c1]\n", encoding="utf-8")
(tmp_path / "projects.yaml").write_text("projects: []\n", encoding="utf-8")
(tmp_path / "rubrics.yaml").write_text("rubrics: []\n", encoding="utf-8")
(tmp_path / "projects.yaml").write_text("projects:\n - id: p1\n title: P1\n prerequisites: [c1]\n", encoding="utf-8")
(tmp_path / "rubrics.yaml").write_text("rubrics:\n - id: r1\n title: R1\n criteria: [correctness]\n", encoding="utf-8")
result = validate_pack_directory(tmp_path)
assert result["ok"] is True

View File

@ -153,10 +153,10 @@ export default function App() {
<div className="page">
<header className="hero">
<div>
<h1>Didactopus Graph QA</h1>
<h1>Didactopus Semantic QA</h1>
<p>
Reduce the activation-energy hump from generated draft packs to curated review workspaces
by surfacing prerequisite-graph problems before import.
by surfacing semantic curation issues before import.
</p>
<div className="small">{message}</div>
</div>
@ -202,19 +202,22 @@ export default function App() {
<div><strong>Pack:</strong> {importPreview.summary?.display_name || importPreview.summary?.pack_name || "-"}</div>
<div><strong>Version:</strong> {importPreview.summary?.version || "-"}</div>
<div><strong>Concepts:</strong> {importPreview.summary?.concept_count ?? "-"}</div>
<div><strong>Roadmap Stages:</strong> {importPreview.summary?.roadmap_stage_count ?? "-"}</div>
<div><strong>Projects:</strong> {importPreview.summary?.project_count ?? "-"}</div>
<div><strong>Rubrics:</strong> {importPreview.summary?.rubric_count ?? "-"}</div>
</div>
<div className="card">
<h2>Validation Errors</h2>
<ul>{(importPreview.errors || []).length ? importPreview.errors.map((x, i) => <li key={i}>{x}</li>) : <li>none</li>}</ul>
</div>
<div className="card">
<h2>Validation Warnings</h2>
<ul>{(importPreview.warnings || []).length ? importPreview.warnings.map((x, i) => <li key={i}>{x}</li>) : <li>none</li>}</ul>
</div>
<div className="card semantic-card">
<h2>Semantic QA Warnings</h2>
<ul>{(importPreview.semantic_warnings || []).length ? importPreview.semantic_warnings.map((x, i) => <li key={i}>{x}</li>) : <li>none</li>}</ul>
</div>
<div className="card">
<h2>Graph QA Warnings</h2>
<ul>{(importPreview.graph_warnings || []).length ? importPreview.graph_warnings.map((x, i) => <li key={i}>{x}</li>) : <li>none</li>}</ul>
</div>
</section>
)}

View File

@ -18,6 +18,7 @@ button:hover { border-color: var(--accent); }
.preview-grid { margin-top: 16px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.layout { margin-top: 16px; display: grid; grid-template-columns: 290px 1fr 360px; gap: 16px; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 18px; padding: 16px; }
.semantic-card { grid-column: span 4; }
.sidebar, .content, .rightbar { display: flex; flex-direction: column; gap: 16px; }
.concept-btn { width: 100%; text-align: left; display: flex; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.concept-btn.active { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(45,108,223,0.08); }
@ -36,5 +37,6 @@ ul { padding-left: 18px; }
.checkline input { width: auto; margin-top: 0; }
@media (max-width: 1100px) {
.summary-grid, .preview-grid { grid-template-columns: repeat(2, 1fr); }
.semantic-card { grid-column: span 2; }
.layout { grid-template-columns: 1fr; }
}

View File

@ -3,3 +3,5 @@ concepts:
title: Descriptive Statistics
description: Measures of center and spread in descriptive data analysis.
prerequisites: []
mastery_signals:
- Explain mean, median, and variance.