Apply ZIP update: 205-didactopus-learner-state-progression-update.zip [2026-03-14T13:20:33]

This commit is contained in:
welsberr 2026-03-14 13:29:56 -04:00
parent adac1e1637
commit ed60c1d8f2
3 changed files with 36 additions and 15 deletions

View File

@ -6,18 +6,7 @@ build-backend = "setuptools.build_meta"
name = "didactopus" name = "didactopus"
version = "0.1.0" version = "0.1.0"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = ["pydantic>=2.7", "pyyaml>=6.0"]
"pydantic>=2.7",
"fastapi>=0.115",
"uvicorn>=0.30",
"sqlalchemy>=2.0",
"passlib[bcrypt]>=1.7",
"python-jose[cryptography]>=3.3"
]
[project.scripts]
didactopus-api = "didactopus.api:main"
didactopus-export-svg = "didactopus.export_svg:main"
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["src"] where = ["src"]

View File

@ -1,6 +1,10 @@
from __future__ import annotations from __future__ import annotations
from datetime import datetime, timezone
from .learner_state import LearnerState, EvidenceEvent, MasteryRecord from .learner_state import LearnerState, EvidenceEvent, MasteryRecord
def _parse_ts(ts: str) -> datetime:
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
def apply_evidence( def apply_evidence(
state: LearnerState, state: LearnerState,
event: EvidenceEvent, event: EvidenceEvent,
@ -19,13 +23,34 @@ def apply_evidence(
) )
state.records.append(rec) state.records.append(rec)
prev_score = rec.score
prev_conf = rec.confidence
# weighted incremental update
weight = max(0.05, min(1.0, event.confidence_hint)) weight = max(0.05, min(1.0, event.confidence_hint))
rec.score = ((rec.score * rec.evidence_count) + (event.score * weight)) / max(1, rec.evidence_count + 1) rec.score = ((prev_score * rec.evidence_count) + (event.score * weight)) / max(1, rec.evidence_count + 1)
# confidence grows with repeated evidence and quality, but is bounded
rec.confidence = min( rec.confidence = min(
1.0, 1.0,
max(0.0, rec.confidence * (1.0 - decay) + reinforcement * weight + 0.10 * max(0.0, min(1.0, event.score))), max(
0.0,
prev_conf * (1.0 - decay) + reinforcement * weight + 0.10 * max(0.0, min(1.0, event.score))
),
) )
rec.evidence_count += 1 rec.evidence_count += 1
rec.last_updated = event.timestamp rec.last_updated = event.timestamp
state.history.append(event) state.history.append(event)
return state return state
def decay_confidence(state: LearnerState, now_ts: str, daily_decay: float = 0.0025) -> LearnerState:
now = _parse_ts(now_ts)
for rec in state.records:
if not rec.last_updated:
continue
then = _parse_ts(rec.last_updated)
delta_days = max(0.0, (now - then).total_seconds() / 86400.0)
factor = max(0.0, 1.0 - daily_decay * delta_days)
rec.confidence = max(0.0, rec.confidence * factor)
return state

View File

@ -13,7 +13,14 @@ def recommend_next_concepts(
for concept in concepts: for concept in concepts:
cid = concept.get("id") cid = concept.get("id")
prereqs = list(concept.get("prerequisites", []) or []) prereqs = list(concept.get("prerequisites", []) or [])
ready = concept_ready(state, cid, prereqs, dimension=dimension, min_score=min_score, min_confidence=min_confidence) ready = concept_ready(
state,
cid,
prereqs,
dimension=dimension,
min_score=min_score,
min_confidence=min_confidence,
)
if ready: if ready:
existing = state.get_record(cid, dimension) existing = state.get_record(cid, dimension)
if existing is None or existing.score < min_score or existing.confidence < min_confidence: if existing is None or existing.score < min_score or existing.confidence < min_confidence: