45 lines
1.5 KiB
Python
45 lines
1.5 KiB
Python
from __future__ import annotations
|
|
from datetime import datetime
|
|
|
|
from .learner_state import LearnerState, EvidenceEvent, MasteryRecord
|
|
|
|
def apply_evidence(
|
|
state: LearnerState,
|
|
event: EvidenceEvent,
|
|
decay: float = 0.05,
|
|
reinforcement: float = 0.25,
|
|
) -> LearnerState:
|
|
rec = state.get_record(event.concept_id, event.dimension)
|
|
if rec is None:
|
|
rec = MasteryRecord(
|
|
concept_id=event.concept_id,
|
|
dimension=event.dimension,
|
|
score=0.0,
|
|
confidence=0.0,
|
|
evidence_count=0,
|
|
last_updated=event.timestamp,
|
|
)
|
|
state.records.append(rec)
|
|
|
|
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.confidence = min(
|
|
1.0,
|
|
max(0.0, rec.confidence * (1.0 - decay) + reinforcement * weight + 0.10 * max(0.0, min(1.0, event.score))),
|
|
)
|
|
rec.evidence_count += 1
|
|
rec.last_updated = event.timestamp
|
|
state.history.append(event)
|
|
return state
|
|
|
|
|
|
def decay_confidence(state: LearnerState, now_timestamp: str, daily_decay: float = 0.01) -> LearnerState:
|
|
now = datetime.fromisoformat(now_timestamp)
|
|
for record in state.records:
|
|
if not record.last_updated:
|
|
continue
|
|
updated = datetime.fromisoformat(record.last_updated)
|
|
elapsed_days = max(0.0, (now - updated).total_seconds() / 86400.0)
|
|
record.confidence = max(0.0, record.confidence * ((1.0 - daily_decay) ** elapsed_days))
|
|
return state
|