chronem 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. chronem/__init__.py +111 -0
  2. chronem/answers/__init__.py +18 -0
  3. chronem/answers/models.py +92 -0
  4. chronem/answers/service.py +160 -0
  5. chronem/entities/__init__.py +6 -0
  6. chronem/entities/normalization.py +15 -0
  7. chronem/entities/resolver.py +57 -0
  8. chronem/evaluation/__init__.py +40 -0
  9. chronem/evaluation/data/__init__.py +1 -0
  10. chronem/evaluation/data/recall_planning.json +14 -0
  11. chronem/evaluation/entities.py +174 -0
  12. chronem/evaluation/grounding.py +305 -0
  13. chronem/evaluation/lifecycle.py +221 -0
  14. chronem/evaluation/metrics.py +51 -0
  15. chronem/evaluation/models.py +221 -0
  16. chronem/evaluation/planning.py +56 -0
  17. chronem/evaluation/runner.py +44 -0
  18. chronem/evaluation/structured_recall.py +49 -0
  19. chronem/ingestion/__init__.py +18 -0
  20. chronem/ingestion/models.py +66 -0
  21. chronem/ingestion/service.py +336 -0
  22. chronem/interfaces/__init__.py +35 -0
  23. chronem/interfaces/answers.py +23 -0
  24. chronem/interfaces/clock.py +13 -0
  25. chronem/interfaces/entities.py +64 -0
  26. chronem/interfaces/extraction.py +19 -0
  27. chronem/interfaces/queries.py +97 -0
  28. chronem/interfaces/recall.py +22 -0
  29. chronem/interfaces/repositories.py +109 -0
  30. chronem/lifecycle/engine.py +260 -0
  31. chronem/models/_shared.py +63 -0
  32. chronem/models/candidate.py +92 -0
  33. chronem/models/capture.py +52 -0
  34. chronem/models/entity.py +61 -0
  35. chronem/models/memory.py +154 -0
  36. chronem/models/types.py +47 -0
  37. chronem/persistence/__init__.py +1 -0
  38. chronem/persistence/postgres/__init__.py +22 -0
  39. chronem/persistence/postgres/mapping.py +99 -0
  40. chronem/persistence/postgres/migration.py +31 -0
  41. chronem/persistence/postgres/migrations/__init__.py +1 -0
  42. chronem/persistence/postgres/migrations/__main__.py +22 -0
  43. chronem/persistence/postgres/migrations/env.py +61 -0
  44. chronem/persistence/postgres/migrations/script.py.mako +25 -0
  45. chronem/persistence/postgres/migrations/versions/20261008_0001_initial_postgres.py +143 -0
  46. chronem/persistence/postgres/migrations/versions/20261009_0002_structured_recall_index.py +27 -0
  47. chronem/persistence/postgres/migrations/versions/__init__.py +1 -0
  48. chronem/persistence/postgres/repositories.py +334 -0
  49. chronem/persistence/postgres/session.py +14 -0
  50. chronem/persistence/postgres/tables.py +145 -0
  51. chronem/providers/__init__.py +1 -0
  52. chronem/providers/openai/__init__.py +35 -0
  53. chronem/providers/openai/answer_prompt.py +56 -0
  54. chronem/providers/openai/answer_schemas.py +30 -0
  55. chronem/providers/openai/answer_synthesizer.py +82 -0
  56. chronem/providers/openai/extractor.py +64 -0
  57. chronem/providers/openai/prompt.py +45 -0
  58. chronem/providers/openai/recall_planner.py +78 -0
  59. chronem/providers/openai/recall_prompt.py +53 -0
  60. chronem/providers/openai/recall_schemas.py +88 -0
  61. chronem/providers/openai/schemas.py +154 -0
  62. chronem/py.typed +1 -0
  63. chronem/recall/__init__.py +34 -0
  64. chronem/recall/models.py +154 -0
  65. chronem/recall/orchestration.py +119 -0
  66. chronem/recall/service.py +168 -0
  67. chronem-0.1.0.dist-info/METADATA +240 -0
  68. chronem-0.1.0.dist-info/RECORD +70 -0
  69. chronem-0.1.0.dist-info/WHEEL +4 -0
  70. chronem-0.1.0.dist-info/licenses/LICENSE +201 -0
chronem/__init__.py ADDED
@@ -0,0 +1,111 @@
1
+ """Chronem temporal memory engine."""
2
+
3
+ from chronem.answers import (
4
+ NO_EVIDENCE_MESSAGE,
5
+ RecallAnswerInvariantError,
6
+ RecallAnswerOutcome,
7
+ RecallAnswerResult,
8
+ RecallAnswerService,
9
+ SynthesizedAnswer,
10
+ )
11
+ from chronem.entities import DeterministicEntityResolver, normalize_entity_name
12
+ from chronem.ingestion import (
13
+ CandidateIngestionOutcome,
14
+ CandidateIngestionResult,
15
+ EntityMentionRole,
16
+ IngestionInvariantError,
17
+ IngestionResult,
18
+ IngestionService,
19
+ )
20
+ from chronem.interfaces import (
21
+ AnswerSynthesizer,
22
+ CaptureRepository,
23
+ Clock,
24
+ EntityRepository,
25
+ EntityResolution,
26
+ EntityResolutionOutcome,
27
+ EntityResolver,
28
+ Extractor,
29
+ MemoryQuery,
30
+ MemoryQueryOrder,
31
+ MemoryQueryRepository,
32
+ MemoryRepository,
33
+ RecallPlanner,
34
+ RepositoryInvariantError,
35
+ )
36
+ from chronem.lifecycle.engine import LifecycleAction, LifecycleDecision, LifecycleEngine
37
+ from chronem.models.candidate import CandidateMemory
38
+ from chronem.models.capture import Capture, SourceType
39
+ from chronem.models.entity import Entity, EntityType
40
+ from chronem.models.memory import Memory
41
+ from chronem.models.types import MemoryKind, MemoryStatus
42
+ from chronem.recall import (
43
+ RecallExecutionOutcome,
44
+ RecallExecutionResult,
45
+ RecallInvariantError,
46
+ RecallMode,
47
+ RecallOrchestrationInvariantError,
48
+ RecallOrchestrationService,
49
+ RecallOutcome,
50
+ RecallPlan,
51
+ RecallPlanOutcome,
52
+ RecallRequestExecution,
53
+ StructuredRecallRequest,
54
+ StructuredRecallResult,
55
+ StructuredRecallService,
56
+ )
57
+
58
+ __all__ = [
59
+ "AnswerSynthesizer",
60
+ "CandidateMemory",
61
+ "CandidateIngestionOutcome",
62
+ "CandidateIngestionResult",
63
+ "Capture",
64
+ "CaptureRepository",
65
+ "Clock",
66
+ "DeterministicEntityResolver",
67
+ "Entity",
68
+ "EntityResolution",
69
+ "EntityResolutionOutcome",
70
+ "EntityRepository",
71
+ "EntityResolver",
72
+ "EntityType",
73
+ "EntityMentionRole",
74
+ "Extractor",
75
+ "IngestionInvariantError",
76
+ "IngestionResult",
77
+ "IngestionService",
78
+ "LifecycleAction",
79
+ "LifecycleDecision",
80
+ "LifecycleEngine",
81
+ "Memory",
82
+ "MemoryKind",
83
+ "MemoryRepository",
84
+ "MemoryQuery",
85
+ "MemoryQueryOrder",
86
+ "MemoryQueryRepository",
87
+ "MemoryStatus",
88
+ "NO_EVIDENCE_MESSAGE",
89
+ "RepositoryInvariantError",
90
+ "RecallInvariantError",
91
+ "RecallAnswerInvariantError",
92
+ "RecallAnswerOutcome",
93
+ "RecallAnswerResult",
94
+ "RecallAnswerService",
95
+ "RecallExecutionOutcome",
96
+ "RecallExecutionResult",
97
+ "RecallMode",
98
+ "RecallOutcome",
99
+ "RecallOrchestrationInvariantError",
100
+ "RecallOrchestrationService",
101
+ "RecallPlan",
102
+ "RecallPlanner",
103
+ "RecallPlanOutcome",
104
+ "RecallRequestExecution",
105
+ "SourceType",
106
+ "StructuredRecallRequest",
107
+ "StructuredRecallResult",
108
+ "StructuredRecallService",
109
+ "SynthesizedAnswer",
110
+ "normalize_entity_name",
111
+ ]
@@ -0,0 +1,18 @@
1
+ """Grounded answer synthesis public API."""
2
+
3
+ from chronem.answers.models import (
4
+ NO_EVIDENCE_MESSAGE,
5
+ RecallAnswerOutcome,
6
+ RecallAnswerResult,
7
+ SynthesizedAnswer,
8
+ )
9
+ from chronem.answers.service import RecallAnswerInvariantError, RecallAnswerService
10
+
11
+ __all__ = [
12
+ "NO_EVIDENCE_MESSAGE",
13
+ "RecallAnswerInvariantError",
14
+ "RecallAnswerOutcome",
15
+ "RecallAnswerResult",
16
+ "RecallAnswerService",
17
+ "SynthesizedAnswer",
18
+ ]
@@ -0,0 +1,92 @@
1
+ """Provider-independent models for grounded recall answers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+ from uuid import UUID
7
+
8
+ from pydantic import BaseModel, Field, field_validator, model_validator
9
+
10
+ from chronem.models._shared import require_non_blank
11
+ from chronem.models.memory import Memory
12
+ from chronem.recall.models import RecallExecutionOutcome, RecallExecutionResult
13
+
14
+ NO_EVIDENCE_MESSAGE = "I don't have that saved."
15
+
16
+
17
+ class SynthesizedAnswer(BaseModel):
18
+ """User-facing wording and the grounded memory ids supporting it."""
19
+
20
+ text: str = Field(min_length=1)
21
+ cited_memory_ids: tuple[UUID, ...] = Field(min_length=1)
22
+
23
+ @field_validator("text")
24
+ @classmethod
25
+ def _validate_text(cls, value: str) -> str:
26
+ return require_non_blank(value, field_name="answer text")
27
+
28
+ @model_validator(mode="after")
29
+ def _reject_duplicate_citations(self) -> SynthesizedAnswer:
30
+ if len(self.cited_memory_ids) != len(set(self.cited_memory_ids)):
31
+ raise ValueError("cited_memory_ids must not contain duplicates")
32
+ return self
33
+
34
+
35
+ class RecallAnswerOutcome(StrEnum):
36
+ """Outcome of deterministic recall plus optional grounded phrasing."""
37
+
38
+ ANSWERED = "ANSWERED"
39
+ PARTIAL = "PARTIAL"
40
+ NOT_FOUND = "NOT_FOUND"
41
+ UNSUPPORTED = "UNSUPPORTED"
42
+ AMBIGUOUS_PLAN = "AMBIGUOUS_PLAN"
43
+ AMBIGUOUS_SUBJECT = "AMBIGUOUS_SUBJECT"
44
+ SUBJECT_TYPE_CONFLICT = "SUBJECT_TYPE_CONFLICT"
45
+
46
+
47
+ class RecallAnswerResult(BaseModel):
48
+ """Complete recall evidence and its validated user-facing answer, if any."""
49
+
50
+ outcome: RecallAnswerOutcome
51
+ text: str | None = None
52
+ cited_memory_ids: tuple[UUID, ...] = ()
53
+ recall_result: RecallExecutionResult
54
+
55
+ @property
56
+ def evidence(self) -> tuple[Memory, ...]:
57
+ """Return evidence unchanged in deterministic recall order."""
58
+ return self.recall_result.memories
59
+
60
+ @model_validator(mode="after")
61
+ def _validate_shape(self) -> RecallAnswerResult:
62
+ if self.outcome in (RecallAnswerOutcome.ANSWERED, RecallAnswerOutcome.PARTIAL):
63
+ if self.text is None or not self.text.strip():
64
+ raise ValueError("answered results require nonblank text")
65
+ if not self.cited_memory_ids:
66
+ raise ValueError("answered results require at least one citation")
67
+ elif self.cited_memory_ids:
68
+ raise ValueError("non-answered results cannot contain citations")
69
+
70
+ if self.outcome == RecallAnswerOutcome.NOT_FOUND:
71
+ if self.text != NO_EVIDENCE_MESSAGE:
72
+ raise ValueError("NOT_FOUND requires the deterministic no-evidence message")
73
+ elif self.outcome not in (
74
+ RecallAnswerOutcome.ANSWERED,
75
+ RecallAnswerOutcome.PARTIAL,
76
+ ) and self.text is not None:
77
+ raise ValueError("abstained results cannot contain answer text")
78
+
79
+ expected_recall_outcome = {
80
+ RecallAnswerOutcome.ANSWERED: RecallExecutionOutcome.FOUND,
81
+ RecallAnswerOutcome.PARTIAL: RecallExecutionOutcome.PARTIAL,
82
+ RecallAnswerOutcome.NOT_FOUND: RecallExecutionOutcome.NOT_FOUND,
83
+ RecallAnswerOutcome.UNSUPPORTED: RecallExecutionOutcome.UNSUPPORTED,
84
+ RecallAnswerOutcome.AMBIGUOUS_PLAN: RecallExecutionOutcome.AMBIGUOUS_PLAN,
85
+ RecallAnswerOutcome.AMBIGUOUS_SUBJECT: RecallExecutionOutcome.AMBIGUOUS_SUBJECT,
86
+ RecallAnswerOutcome.SUBJECT_TYPE_CONFLICT: (
87
+ RecallExecutionOutcome.SUBJECT_TYPE_CONFLICT
88
+ ),
89
+ }[self.outcome]
90
+ if self.recall_result.outcome != expected_recall_outcome:
91
+ raise ValueError("answer outcome must match the deterministic recall outcome")
92
+ return self
@@ -0,0 +1,160 @@
1
+ """Provider-independent orchestration from grounded recall to an answer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import TYPE_CHECKING
7
+ from uuid import UUID
8
+
9
+ from chronem.answers.models import (
10
+ NO_EVIDENCE_MESSAGE,
11
+ RecallAnswerOutcome,
12
+ RecallAnswerResult,
13
+ SynthesizedAnswer,
14
+ )
15
+ from chronem.models._shared import require_non_blank, require_timezone_aware
16
+ from chronem.models.memory import Memory
17
+ from chronem.models.types import MemoryKind
18
+ from chronem.recall.models import RecallExecutionOutcome, RecallExecutionResult
19
+ from chronem.recall.orchestration import RecallOrchestrationService
20
+
21
+ if TYPE_CHECKING:
22
+ from chronem.interfaces.answers import AnswerSynthesizer
23
+
24
+
25
+ class RecallAnswerInvariantError(ValueError):
26
+ """Raised when grounded synthesis violates the answer boundary."""
27
+
28
+
29
+ class RecallAnswerService:
30
+ """Phrase deterministic recall evidence without adding retrieval authority."""
31
+
32
+ def __init__(
33
+ self,
34
+ *,
35
+ recall_orchestration_service: RecallOrchestrationService,
36
+ answer_synthesizer: AnswerSynthesizer,
37
+ ) -> None:
38
+ self._recall_orchestration_service = recall_orchestration_service
39
+ self._answer_synthesizer = answer_synthesizer
40
+
41
+ def answer(
42
+ self,
43
+ *,
44
+ user_id: str,
45
+ question: str,
46
+ asked_at: datetime,
47
+ ) -> RecallAnswerResult:
48
+ scoped_user_id = require_non_blank(user_id, field_name="user_id")
49
+ question_text = require_non_blank(question, field_name="question")
50
+ reference_time = require_timezone_aware(asked_at, field_name="asked_at")
51
+ recall_result = self._recall_orchestration_service.recall(
52
+ user_id=scoped_user_id,
53
+ question=question_text,
54
+ asked_at=reference_time,
55
+ )
56
+
57
+ deterministic_outcome = _non_synthesis_outcome(recall_result.outcome)
58
+ if deterministic_outcome is not None:
59
+ return RecallAnswerResult(
60
+ outcome=deterministic_outcome,
61
+ text=(
62
+ NO_EVIDENCE_MESSAGE
63
+ if deterministic_outcome == RecallAnswerOutcome.NOT_FOUND
64
+ else None
65
+ ),
66
+ recall_result=recall_result,
67
+ )
68
+
69
+ evidence = recall_result.memories
70
+ self._validate_evidence(user_id=scoped_user_id, evidence=evidence)
71
+ synthesized = self._answer_synthesizer.synthesize(
72
+ question=question_text,
73
+ evidence=evidence,
74
+ asked_at=reference_time,
75
+ )
76
+ self._validate_synthesis(
77
+ synthesized=synthesized,
78
+ evidence=evidence,
79
+ recall_result=recall_result,
80
+ )
81
+
82
+ return RecallAnswerResult(
83
+ outcome=(
84
+ RecallAnswerOutcome.ANSWERED
85
+ if recall_result.outcome == RecallExecutionOutcome.FOUND
86
+ else RecallAnswerOutcome.PARTIAL
87
+ ),
88
+ text=synthesized.text,
89
+ cited_memory_ids=synthesized.cited_memory_ids,
90
+ recall_result=recall_result,
91
+ )
92
+
93
+ @staticmethod
94
+ def _validate_evidence(*, user_id: str, evidence: tuple[Memory, ...]) -> None:
95
+ if not evidence:
96
+ raise RecallAnswerInvariantError(
97
+ "FOUND and PARTIAL recall outcomes require grounded evidence"
98
+ )
99
+ if any(memory.user_id != user_id for memory in evidence):
100
+ raise RecallAnswerInvariantError(
101
+ "recall evidence contains a memory from another user scope"
102
+ )
103
+
104
+ @staticmethod
105
+ def _validate_synthesis(
106
+ *,
107
+ synthesized: SynthesizedAnswer,
108
+ evidence: tuple[Memory, ...],
109
+ recall_result: RecallExecutionResult,
110
+ ) -> None:
111
+ cited_ids = synthesized.cited_memory_ids
112
+ if not cited_ids:
113
+ raise RecallAnswerInvariantError("a grounded answer requires at least one citation")
114
+ if len(cited_ids) != len(set(cited_ids)):
115
+ raise RecallAnswerInvariantError(
116
+ "grounded answer citations must not contain duplicates"
117
+ )
118
+
119
+ evidence_ids = {memory.id for memory in evidence}
120
+ unknown_ids = set(cited_ids) - evidence_ids
121
+ if unknown_ids:
122
+ raise RecallAnswerInvariantError(
123
+ "grounded answer cites memory ids outside the supplied evidence"
124
+ )
125
+
126
+ required_fact_ids = _multi_fact_request_ids(recall_result)
127
+ if not required_fact_ids.issubset(cited_ids):
128
+ raise RecallAnswerInvariantError(
129
+ "grounded answer must cite every FACT memory returned by a "
130
+ "multi-memory FACT request"
131
+ )
132
+
133
+
134
+ def _non_synthesis_outcome(
135
+ outcome: RecallExecutionOutcome,
136
+ ) -> RecallAnswerOutcome | None:
137
+ return {
138
+ RecallExecutionOutcome.NOT_FOUND: RecallAnswerOutcome.NOT_FOUND,
139
+ RecallExecutionOutcome.UNSUPPORTED: RecallAnswerOutcome.UNSUPPORTED,
140
+ RecallExecutionOutcome.AMBIGUOUS_PLAN: RecallAnswerOutcome.AMBIGUOUS_PLAN,
141
+ RecallExecutionOutcome.AMBIGUOUS_SUBJECT: RecallAnswerOutcome.AMBIGUOUS_SUBJECT,
142
+ RecallExecutionOutcome.SUBJECT_TYPE_CONFLICT: (
143
+ RecallAnswerOutcome.SUBJECT_TYPE_CONFLICT
144
+ ),
145
+ }.get(outcome)
146
+
147
+
148
+ def _multi_fact_request_ids(recall_result: RecallExecutionResult) -> set[UUID]:
149
+ required_ids: set[UUID] = set()
150
+ for execution in recall_result.executions:
151
+ if execution.request.kind != MemoryKind.FACT:
152
+ continue
153
+ fact_memories = tuple(
154
+ memory
155
+ for memory in execution.result.memories
156
+ if memory.kind == MemoryKind.FACT
157
+ )
158
+ if len(fact_memories) > 1:
159
+ required_ids.update(memory.id for memory in fact_memories)
160
+ return required_ids
@@ -0,0 +1,6 @@
1
+ """Deterministic, conservative entity-resolution behavior."""
2
+
3
+ from chronem.entities.normalization import normalize_entity_name
4
+ from chronem.entities.resolver import DeterministicEntityResolver
5
+
6
+ __all__ = ["DeterministicEntityResolver", "normalize_entity_name"]
@@ -0,0 +1,15 @@
1
+ """Pure normalization for deterministic entity-name comparison."""
2
+
3
+ import unicodedata
4
+
5
+
6
+ def normalize_entity_name(value: str) -> str:
7
+ """Normalize a name without erasing identity-significant distinctions.
8
+
9
+ NFKC normalization is followed by outer trimming, whitespace collapsing,
10
+ and Unicode case folding. Punctuation and diacritics remain intact.
11
+ """
12
+ normalized = " ".join(unicodedata.normalize("NFKC", value).split()).casefold()
13
+ if not normalized:
14
+ raise ValueError("entity name must not be blank")
15
+ return normalized
@@ -0,0 +1,57 @@
1
+ """Conservative deterministic entity resolution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from chronem.entities.normalization import normalize_entity_name
8
+ from chronem.interfaces.entities import EntityResolution, EntityResolutionOutcome, EntityResolver
9
+ from chronem.interfaces.repositories import EntityRepository, RepositoryInvariantError
10
+ from chronem.models._shared import require_non_blank
11
+ from chronem.models.entity import Entity
12
+
13
+
14
+ class DeterministicEntityResolver(EntityResolver):
15
+ """Match only normalized canonical names and explicit aliases.
16
+
17
+ Resolution is read-only. Unmatched entity creation and all merge decisions
18
+ belong outside this resolver; ingestion orchestration may create an entity.
19
+ """
20
+
21
+ def __init__(self, repository: EntityRepository) -> None:
22
+ self._repository = repository
23
+
24
+ def resolve(self, *, user_id: str, mention: str) -> EntityResolution:
25
+ scoped_user_id = require_non_blank(user_id, field_name="user_id")
26
+ normalized_mention = normalize_entity_name(mention)
27
+ matches: dict[UUID, Entity] = {}
28
+
29
+ for entity in self._repository.list_for_user(user_id=scoped_user_id):
30
+ if entity.user_id != scoped_user_id:
31
+ raise RepositoryInvariantError(
32
+ "entity repository returned a record from a different user scope"
33
+ )
34
+ names = (entity.canonical_name, *entity.aliases)
35
+ if any(normalize_entity_name(name) == normalized_mention for name in names):
36
+ matches[entity.id] = entity
37
+
38
+ candidates = tuple(matches[entity_id] for entity_id in sorted(matches))
39
+ if not candidates:
40
+ return EntityResolution(
41
+ user_id=scoped_user_id,
42
+ mention=mention,
43
+ outcome=EntityResolutionOutcome.UNMATCHED,
44
+ )
45
+ if len(candidates) == 1:
46
+ return EntityResolution(
47
+ user_id=scoped_user_id,
48
+ mention=mention,
49
+ outcome=EntityResolutionOutcome.MATCHED,
50
+ matched_entity=candidates[0],
51
+ )
52
+ return EntityResolution(
53
+ user_id=scoped_user_id,
54
+ mention=mention,
55
+ outcome=EntityResolutionOutcome.AMBIGUOUS,
56
+ candidates=candidates,
57
+ )
@@ -0,0 +1,40 @@
1
+ """Provider-independent deterministic evaluation public API."""
2
+
3
+ from chronem.evaluation.metrics import classification_metrics, summarize_results
4
+ from chronem.evaluation.models import (
5
+ AnswerGroundingEvaluationCase,
6
+ ClassificationMetrics,
7
+ EntityResolutionEvaluationCase,
8
+ EvaluationCase,
9
+ EvaluationDomain,
10
+ EvaluationReport,
11
+ EvaluationResult,
12
+ EvaluationSummary,
13
+ ExtractionEvaluationCase,
14
+ LifecycleEvaluationCase,
15
+ RecallPlanningEvaluationCase,
16
+ StructuredRecallEvaluationCase,
17
+ )
18
+ from chronem.evaluation.runner import build_report, evaluation_exit_code, run_core_evaluations
19
+ from chronem.evaluation.structured_recall import evaluate_structured_recall
20
+
21
+ __all__ = [
22
+ "AnswerGroundingEvaluationCase",
23
+ "ClassificationMetrics",
24
+ "EntityResolutionEvaluationCase",
25
+ "EvaluationCase",
26
+ "EvaluationDomain",
27
+ "EvaluationReport",
28
+ "EvaluationResult",
29
+ "EvaluationSummary",
30
+ "ExtractionEvaluationCase",
31
+ "LifecycleEvaluationCase",
32
+ "RecallPlanningEvaluationCase",
33
+ "StructuredRecallEvaluationCase",
34
+ "classification_metrics",
35
+ "build_report",
36
+ "evaluate_structured_recall",
37
+ "evaluation_exit_code",
38
+ "run_core_evaluations",
39
+ "summarize_results",
40
+ ]
@@ -0,0 +1 @@
1
+ """Versioned evaluation datasets packaged with Chronem."""
@@ -0,0 +1,14 @@
1
+ [
2
+ {"name":"parking_current","question":"Where did I park?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"PLANNED","expected_requests":[{"subject":"my car","expected_subject_type":"vehicle","kind":"CURRENT_STATE","predicate":"parked_at","mode":"CURRENT"}]},
3
+ {"name":"passport_current","question":"Where is my passport?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"PLANNED","expected_requests":[{"subject":"my passport","expected_subject_type":"object","kind":"CURRENT_STATE","predicate":"located_at","mode":"CURRENT"}]},
4
+ {"name":"birthday_fact","question":"When is Kevin's birthday?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"PLANNED","expected_requests":[{"subject":"Kevin","expected_subject_type":"person","kind":"FACT","predicate":"birthday","mode":"CURRENT"}]},
5
+ {"name":"friend_preferences","question":"What does Kevin like?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"PLANNED","expected_requests":[{"subject":"Kevin","expected_subject_type":"person","kind":"PREFERENCE","predicate":"likes","mode":"ACTIVE"}]},
6
+ {"name":"bench_history","question":"Show my recent bench press history.","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"PLANNED","expected_requests":[{"subject":"me","expected_subject_type":"person","kind":"EVENT","predicate":"bench_press","mode":"HISTORY"}]},
7
+ {"name":"latest_oil_change","question":"When did I last change the oil?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"PLANNED","expected_requests":[{"subject":"my vehicle","expected_subject_type":"vehicle","kind":"EVENT","predicate":"oil_changed","mode":"LATEST"}]},
8
+ {"name":"active_shopping","question":"What am I still planning to buy?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"PLANNED","expected_requests":[{"subject":"me","expected_subject_type":"person","kind":"INTENT","predicate":"buy","mode":"ACTIVE"}]},
9
+ {"name":"workouts_this_week","question":"What workouts did I log this week?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"UNSUPPORTED","expected_requests":[]},
10
+ {"name":"bench_yesterday","question":"What did I bench yesterday?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"PLANNED","expected_requests":[{"subject":"me","expected_subject_type":"person","kind":"EVENT","predicate":"bench_press","mode":"HISTORY","since":"2026-10-08T00:00:00+00:00","until":"2026-10-08T23:59:59.999999+00:00"}]},
11
+ {"name":"maintenance_last_month","question":"What maintenance did I record last month?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"UNSUPPORTED","expected_requests":[]},
12
+ {"name":"unsupported_semantic","question":"Find memories similar to my happiest notes.","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"UNSUPPORTED","expected_requests":[]},
13
+ {"name":"ambiguous_target","question":"Where is it?","asked_at":"2026-10-09T15:30:00+00:00","expected_outcome":"AMBIGUOUS","expected_requests":[]}
14
+ ]