Files
mem0/integrations/mem0-agent/eval/fixtures.py
T
Deshraj YadavandClaude Fable 5 4d5f2e653c feat(mem0-agent): capture gate, eval harness, and mixed-window fix
- triggers.py: hard-drop rules now filter PER TURN, so a durable fact sitting
  between two progress lines survives instead of being dropped with them. The
  eval harness caught this: 3 mixed-window fixtures were being destroyed
  client-side before the extractor ever saw them.
- widened flag rules for 13 plainly durable windows the gate was missing
  (standing preferences, X-over-Y decisions, stated rules, diagnoses, verified
  procedures); verified procedures moved from aggressive to balanced
- eval/: 56 labeled fixtures from the audited v1 corpus plus offline and live
  runners with a --check regression gate
- un-ignore integrations/mem0-agent/eval (root .gitignore excluded it silently)

Offline scorecard: hard_drop_recall 1.000, extract_recall 1.000,
flag_precision 1.000, 0 misclassified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 02:02:37 -07:00

703 lines
32 KiB
Python

"""Labeled windows for the write-gate evaluation.
Every fixture is one turn-window exactly as the client would hand it to the capture
path. The wording of the `drop` and `exclude` entries is taken from the audited v1
corpus -- these are the classes that made 20.5% of that corpus near-duplicate
heartbeats and drove organic searches from 257/month down to 75/month. The `extract`
entries are the knowledge the gate must never throw away.
Labels
------
drop The client's local trigger rules must hard-drop the window. It never reaches
the platform, so it costs nothing and can never be stored. Failing to drop
these is what produced the v1 heartbeat corpus.
exclude The window may legitimately be sent (a local rule cannot cheaply tell it
apart from real content), but the platform's custom instructions must store
NOTHING from it. This is the layer that catches narration, activity logs,
repo-file contents and one-off directives.
extract The window must produce at least one memory, ideally of `expect_type`.
Losing these is the expensive failure: the gate gets quiet and useless.
Schema (every entry, exactly these keys)
----------------------------------------
id stable identifier, also the per-fixture user_id suffix in live runs
window list of {"role", "content"} messages
label "drop" | "exclude" | "extract"
expect_type one of mem0_agent.config.project_config.TYPES, or None
note why this fixture exists / what regression it guards
Adding a fixture: append it to the right block, give it a fresh id, and say in `note`
which real failure it represents. tests/test_fixtures.py enforces the schema.
"""
from __future__ import annotations
LABELS: tuple[str, ...] = ("drop", "exclude", "extract")
# ---------------------------------------------------------------------------
# DROP -- mechanical noise. The client must never send these.
# ---------------------------------------------------------------------------
_DROP: list[dict] = [
{
"id": "d01_train_epoch_eta",
"window": [
{"role": "assistant", "content": "Task notification (task-id bukn4vw5n): v4 train metrics at epoch "
"0.7381/2 (37% complete) with loss 0.4727, gradient norm 0.4716, "
"ETA 124 minutes."},
{"role": "assistant", "content": "Still training. Next update in about 10 minutes."},
],
"label": "drop",
"expect_type": None,
"note": "The single most duplicated shape in the audited v1 corpus: epoch/loss/ETA training heartbeat.",
},
{
"id": "d02_train_epoch_eta_later",
"window": [
{"role": "assistant", "content": "Task notification (task-id bukn4vw5n): v4 train metrics at epoch "
"0.9124/2 (46% complete) with loss 0.4412, gradient norm 0.5031, "
"ETA 101 minutes."},
],
"label": "drop",
"expect_type": None,
"note": "Same shape as d01 twenty minutes later. Repeated same-shape turns are the near-duplicate engine.",
},
{
"id": "d03_train_step_metrics",
"window": [
{"role": "assistant", "content": "step 4200/12000 | loss 0.3318 | lr 1.2e-05 | grad_norm 0.61 | "
"throughput 812 tok/s | eta 02:41:15"},
],
"label": "drop",
"expect_type": None,
"note": "Bare training metric line with no prose at all -- pure telemetry.",
},
{
"id": "d04_chunks_progress",
"window": [
{"role": "assistant", "content": "Progress for task bnzbd1uay: 218 of 928 chunks processed "
"(23% complete), approximately 5,141 synthetic memories generated, "
"11 chunk failures, ETA about 55 minutes."},
],
"label": "drop",
"expect_type": None,
"note": "Verbatim v1 corpus wording: the 'N of M chunks processed (X%)' heartbeat.",
},
{
"id": "d05_chunks_progress_mid",
"window": [
{"role": "assistant", "content": "Progress for task bnzbd1uay: 466 of 928 chunks processed "
"(50% complete), approximately 10,884 synthetic memories generated, "
"19 chunk failures, ETA about 31 minutes."},
],
"label": "drop",
"expect_type": None,
"note": "Second emission of d04. Differs only in the numbers -- the classic near-duplicate pair.",
},
{
"id": "d06_chunks_progress_late",
"window": [
{"role": "assistant", "content": "Progress for task bnzbd1uay: 902 of 928 chunks processed "
"(97% complete), approximately 21,330 synthetic memories generated, "
"24 chunk failures, ETA about 2 minutes."},
],
"label": "drop",
"expect_type": None,
"note": "Third emission. A gate that drops d04 but keeps this one has learned the numbers, not the shape.",
},
{
"id": "d07_markdown_ingest_pid",
"window": [
{"role": "assistant", "content": "markdown ingest still running (pid 48213, elapsed 00:14:52); "
"3,204 files indexed so far, 0 errors."},
],
"label": "drop",
"expect_type": None,
"note": "Markdown-ingest heartbeat with pid/elapsed -- second-largest duplicate cluster in the audit.",
},
{
"id": "d08_markdown_ingest_pid_repeat",
"window": [
{"role": "assistant", "content": "markdown ingest still running (pid 48213, elapsed 00:29:18); "
"6,771 files indexed so far, 2 errors."},
],
"label": "drop",
"expect_type": None,
"note": "Repeat of d07. Same pid, later elapsed.",
},
{
"id": "d09_ingest_complete_stats",
"window": [
{"role": "assistant", "content": "markdown ingest finished (pid 48213, elapsed 00:41:07): "
"9,118 files indexed, 2 errors, 0 skipped."},
],
"label": "drop",
"expect_type": None,
"note": "Terminal heartbeat. Completion counts are still telemetry, not knowledge.",
},
{
"id": "d10_monitor_status_line",
"window": [
{"role": "assistant", "content": "monitor: api p50 118ms p99 640ms | queue depth 3 | workers 8/8 "
"healthy | last deploy 41m ago"},
],
"label": "drop",
"expect_type": None,
"note": "Monitoring status line. True for one instant, useless in any future session.",
},
{
"id": "d11_monitor_all_green",
"window": [
{"role": "assistant", "content": "Health check at 09:14:03 - all green. Nothing to do."},
],
"label": "drop",
"expect_type": None,
"note": "Monitoring no-op turn.",
},
{
"id": "d12_tool_only_turn",
"window": [
{"role": "assistant", "content": "<tool_use name=\"Bash\">git status --porcelain</tool_use>"},
{"role": "user", "content": "<tool_result> M src/mem0_agent/api.py\n M tests/test_api.py</tool_result>"},
],
"label": "drop",
"expect_type": None,
"note": "Tool-only turn: no natural-language content from either party.",
},
{
"id": "d13_tool_only_test_output",
"window": [
{"role": "user", "content": "<tool_result>============ 412 passed, 3 skipped in 38.21s "
"============</tool_result>"},
],
"label": "drop",
"expect_type": None,
"note": "Raw tool output with no interpretation. The lesson, if any, comes in a later turn.",
},
{
"id": "d14_tool_only_ls",
"window": [
{"role": "assistant", "content": "<tool_use name=\"Bash\">ls -la integrations/mem0-agent</tool_use>"},
{"role": "user", "content": "<tool_result>total 0\ndrwxr-xr-x docs\ndrwxr-xr-x eval\ndrwxr-xr-x "
"hooks\ndrwxr-xr-x src\ndrwxr-xr-x tests</tool_result>"},
],
"label": "drop",
"expect_type": None,
"note": "Directory listing round-trip. Derivable from the repo at any time.",
},
{
"id": "d15_eta_only",
"window": [
{"role": "assistant", "content": "Next update in about 10 minutes."},
],
"label": "drop",
"expect_type": None,
"note": "The shortest heartbeat there is. v1 stored dozens of these.",
},
{
"id": "d16_percent_bar",
"window": [
{"role": "assistant", "content": "[####################........] 71% | 1,412/1,988 rows migrated | "
"eta 6m"},
],
"label": "drop",
"expect_type": None,
"note": "Progress bar render. Mechanical by construction.",
},
{
"id": "d17_still_running_ack",
"window": [
{"role": "assistant", "content": "Still running. 41% now."},
{"role": "user", "content": "ok"},
],
"label": "drop",
"expect_type": None,
"note": "Heartbeat plus bare acknowledgement. Nothing durable can be extracted from 'ok'.",
},
{
"id": "d18_backfill_job_notification",
"window": [
{"role": "assistant", "content": "Task notification (task-id q7z1m4d0c): backfill job 'memories_v3' "
"is 62% complete, 3.1M of 5.0M rows, ETA 22 minutes."},
],
"label": "drop",
"expect_type": None,
"note": "Job-notification wrapper, a different job than d01/d04 but the same shape.",
},
]
# ---------------------------------------------------------------------------
# EXCLUDE -- may be sent; the platform instructions must store nothing.
# ---------------------------------------------------------------------------
_EXCLUDE: list[dict] = [
{
"id": "x01_narration_browser_test",
"window": [
{"role": "assistant", "content": "Should I drive a browser test (log in, open the agent, send a "
"message), or would you rather click through the UI yourself?"},
{"role": "user", "content": "you do it"},
],
"label": "exclude",
"expect_type": None,
"note": "The v1 corpus turned this into 'user prefers the assistant to drive browser tests'. It is a "
"one-off directive plus assistant narration, not a preference.",
},
{
"id": "x02_narration_plan",
"window": [
{"role": "assistant", "content": "I'm going to start by reading api.py and settings.py, then sketch "
"the change, then run the tests before I touch anything else."},
],
"label": "exclude",
"expect_type": None,
"note": "Assistant stating its own plan mid-task. Attribution trap: this is not the user's preference.",
},
{
"id": "x03_narration_asked_whether",
"window": [
{"role": "assistant", "content": "I asked whether to keep the old endpoint around during the "
"migration and you said you would think about it."},
],
"label": "exclude",
"expect_type": None,
"note": "Narration of an unresolved exchange. No decision was reached, so nothing durable exists yet.",
},
{
"id": "x04_file_modification_list",
"window": [
{"role": "assistant", "content": "I modified VERSION, chat.py, agent.py, types.py, chunking.py, the "
"slack adapter, the router, the tests, and several web components "
"in this session."},
],
"label": "exclude",
"expect_type": None,
"note": "Verbatim v1 wording. Activity that git already records, in higher fidelity, forever.",
},
{
"id": "x05_commit_list",
"window": [
{"role": "assistant", "content": "Committed 4 changes: e102b21 foundation, b357a5a release bump, "
"d653b63 milvus guard, cc46715 cassandra filters."},
],
"label": "exclude",
"expect_type": None,
"note": "Commit log restated in prose.",
},
{
"id": "x06_pr_activity",
"window": [
{"role": "assistant", "content": "Opened PR #6589 against main and requested review from two "
"teammates; CI is running now."},
],
"label": "exclude",
"expect_type": None,
"note": "PR bookkeeping, derivable from the forge.",
},
{
"id": "x07_claude_md_excerpt",
"window": [
{"role": "user", "content": "## Coding Standards\n\n- Python source files: snake_case.py\n"
"- Test files: test_<module>.py\n- Ruff line length 120\n"
"(this is the contents of our CLAUDE.md)"},
],
"label": "exclude",
"expect_type": None,
"note": "Repo-file contents. This is the one exclude class the platform instructions alone do NOT "
"suppress -- it reads as genuine convention. The client must drop repo-file pastes locally.",
},
{
"id": "x08_readme_excerpt",
"window": [
{"role": "user", "content": "From the README:\n\n## Installation\n\n```bash\npip install mem0ai\n```\n"
"\n## Quickstart\n\n```python\nfrom mem0 import Memory\nm = Memory()\n```"},
],
"label": "exclude",
"expect_type": None,
"note": "README excerpt. Already in the repo; storing it duplicates a file that will drift.",
},
{
"id": "x09_config_file_excerpt",
"window": [
{"role": "user", "content": "Here is our pyproject:\n\n[tool.ruff]\nline-length = 120\n"
"target-version = \"py310\"\n\n[tool.pytest.ini_options]\n"
"testpaths = [\"tests\"]"},
],
"label": "exclude",
"expect_type": None,
"note": "Config file paste. Same class as x07/x08 -- the file is the source of truth, not memory.",
},
{
"id": "x10_one_off_you_do_it",
"window": [
{"role": "assistant", "content": "Do you want to run the migration, or should I?"},
{"role": "user", "content": "you do it"},
],
"label": "exclude",
"expect_type": None,
"note": "One-off task directive. v1 generalized these into standing preferences.",
},
{
"id": "x11_one_off_skip_tests",
"window": [
{"role": "user", "content": "skip tests for now, I just want to see if it compiles"},
],
"label": "exclude",
"expect_type": None,
"note": "Scoped to this moment. Storing it as a preference would suppress tests forever.",
},
{
"id": "x12_one_off_run_yourself",
"window": [
{"role": "user", "content": "run it yourself this time, I'm on a call"},
],
"label": "exclude",
"expect_type": None,
"note": "Explicitly a one-time instruction ('this time'), the exact wording the instructions call out.",
},
{
"id": "x13_session_status_time",
"window": [
{"role": "assistant", "content": "At 12:45:24 the job was wrapping up; I'll give you a final summary "
"when it completes."},
],
"label": "exclude",
"expect_type": None,
"note": "Verbatim v1 wording. Session-only status with a wall-clock timestamp.",
},
{
"id": "x14_session_only_step",
"window": [
{"role": "assistant", "content": "We're on step 3 of 5 of the migration right now; the last two "
"steps are the index rebuild and the cutover."},
],
"label": "exclude",
"expect_type": None,
"note": "Where we are in this session. Session state has its own record and TTL; it is not knowledge.",
},
{
"id": "x15_assistant_self_attribution",
"window": [
{"role": "assistant", "content": "I prefer to read the whole file before editing so I don't miss "
"context, so that's what I did here."},
],
"label": "exclude",
"expect_type": None,
"note": "Attribution trap: the assistant's own habit is not the developer's preference.",
},
{
"id": "x16_credentials",
"window": [
{"role": "user", "content": "here's the staging key so you can test: "
"MEM0_API_KEY=m0-abc123FAKEnotreal456 and the DSN is "
"postgres://app:hunter2@staging-db:5432/app"},
],
"label": "exclude",
"expect_type": None,
"note": "Secrets must never be stored, however useful they look. Explicit exclusion in the instructions.",
},
]
# ---------------------------------------------------------------------------
# EXTRACT -- durable knowledge. At least one memory, ideally of expect_type.
# ---------------------------------------------------------------------------
_EXTRACT: list[dict] = [
{
"id": "e01_pref_test_output_first",
"window": [
{"role": "user", "content": "Stop dumping the whole diff at me every time. Show me the failing test "
"output first, then the fix. That's how I want it from now on."},
{"role": "assistant", "content": "Understood - failing test output first, then the fix."},
],
"label": "extract",
"expect_type": "preference",
"note": "Explicit standing preference ('from now on'). Baseline: captured.",
},
{
"id": "e02_pref_no_summary_tables",
"window": [
{"role": "user", "content": "In general, don't end your answers with a summary table. Just tell me "
"what changed in two sentences. Applies to every task, not just this one."},
],
"label": "extract",
"expect_type": "preference",
"note": "Communication preference, stated as a general rule -- the discriminator against x11/x12.",
},
{
"id": "e03_pref_ask_before_force_push",
"window": [
{"role": "user", "content": "Rule for me, always: never force-push a shared branch without asking "
"first. I've been burned by that twice."},
],
"label": "extract",
"expect_type": "preference",
"note": "Workflow guardrail with stated motivation.",
},
{
"id": "e04_pref_pnpm_only",
"window": [
{"role": "assistant", "content": "Should I use npm install here?"},
{"role": "user", "content": "No - I always use pnpm for anything TypeScript, in every repo. npm and "
"yarn produce lockfile churn I then have to clean up."},
],
"label": "extract",
"expect_type": "preference",
"note": "Tooling preference scoped to the developer, not the repo. Should land at user scope.",
},
{
"id": "e05_decision_pgvector",
"window": [
{"role": "user", "content": "Let's go with pgvector instead of Pinecone. I don't want a second "
"vendor to manage, and the latency is fine at our scale."},
{"role": "assistant", "content": "Going with pgvector then, for vendor consolidation."},
],
"label": "extract",
"expect_type": "decision",
"note": "Resolved choice plus reasoning. Baseline: captured (as two memories).",
},
{
"id": "e06_decision_arq_over_celery",
"window": [
{"role": "user", "content": "We're dropping Celery and moving the workers to arq. Celery's redis "
"broker config kept drifting between environments and arq is asyncio "
"native, which matches the rest of the service."},
],
"label": "extract",
"expect_type": "decision",
"note": "Migration decision with two stated reasons.",
},
{
"id": "e07_decision_metadata_over_categories",
"window": [
{"role": "assistant", "content": "We could filter reads on categories or on metadata.type."},
{"role": "user", "content": "Filter on metadata.type. Categories are assigned by a background job "
"hours later, so a memory written this session would be invisible to a "
"category filter."},
],
"label": "extract",
"expect_type": "decision",
"note": "The read-path decision this harness itself depends on.",
},
{
"id": "e08_decision_keep_worktrees",
"window": [
{"role": "user", "content": "We'll keep using git worktrees for parallel agent work rather than "
"branch switching - switching branches invalidates the build cache and "
"costs us four minutes every time."},
],
"label": "extract",
"expect_type": "decision",
"note": "Process decision with a measured justification.",
},
{
"id": "e09_convention_branch_naming",
"window": [
{"role": "assistant", "content": "The push was rejected: this repo's hook requires branch names in "
"the form user/<id>/<name>."},
{"role": "user", "content": "Right, that's the rule here - always name branches that way."},
],
"label": "extract",
"expect_type": "convention",
"note": "Team rule confirmed by the developer, enforced by tooling but not written down.",
},
{
"id": "e10_convention_conventional_commits",
"window": [
{"role": "user", "content": "Every commit message in this repo has to be a conventional commit with "
"the package scope, like fix(mem0-agent): .... The release router parses "
"the scope, and it isn't documented anywhere."},
],
"label": "extract",
"expect_type": "convention",
"note": "Undocumented team rule with the consequence of breaking it.",
},
{
"id": "e11_convention_no_core_deps",
"window": [
{"role": "user", "content": "Never add anything to the core dependencies list - new deps go in an "
"optional group. That's a hard rule on this team; core has to stay "
"installable with no extras."},
],
"label": "extract",
"expect_type": "convention",
"note": "Project rule stated as a hard constraint.",
},
{
"id": "e12_insight_pytest_needs_compose",
"window": [
{"role": "assistant", "content": "Root cause found: pytest in server/ fails with a misleading "
"postgres connection error unless `docker compose up` is running "
"first. The tests need the compose stack."},
{"role": "user", "content": "good catch"},
],
"label": "extract",
"expect_type": "insight",
"note": "Root-caused gotcha with a misleading symptom. Baseline: captured.",
},
{
"id": "e13_insight_project_id_body",
"window": [
{"role": "assistant", "content": "Found it. project_id and org_id have to go in the request body - "
"as query params the API silently ignores them and the write lands "
"in whatever project the API key defaults to. No error, no warning."},
],
"label": "extract",
"expect_type": "insight",
"note": "Silent-failure constraint. Exactly the class of thing that costs an hour when forgotten.",
},
{
"id": "e14_insight_not_takes_list",
"window": [
{"role": "assistant", "content": "The 400 was the filter shape: NOT takes a list of clauses, not a "
"single object. `{\"NOT\": {...}}` is rejected, `{\"NOT\": [{...}]}` "
"works."},
],
"label": "extract",
"expect_type": "insight",
"note": "Non-obvious API shape, generalized past the incident.",
},
{
"id": "e15_insight_latest_only",
"window": [
{"role": "user", "content": "So that's why we saw duplicates - reads default to returning superseded "
"memories next to the ones that replaced them. latest_only has to be set "
"on every read or the context pack is full of stale pairs."},
],
"label": "extract",
"expect_type": "insight",
"note": "Constraint discovered from a symptom, stated as the general lesson.",
},
{
"id": "e16_runbook_release",
"window": [
{"role": "assistant", "content": "Verified the release procedure end to end: bump VERSION, run "
"`make build`, tag with `cli-v<version>`, push the tag, then the "
"release router dispatches the package workflow. Confirmed working "
"on the last release."},
],
"label": "extract",
"expect_type": "runbook",
"note": "Multi-step procedure explicitly verified end to end. Baseline: captured.",
},
{
"id": "e17_runbook_local_stack",
"window": [
{"role": "assistant", "content": "Local stack bring-up works, confirmed twice: `docker compose up` in "
"server/, wait for neo4j to report healthy on 8474, then "
"`uvicorn main:app --reload` from openmemory/api, then seed with "
"`python scripts/seed.py --demo`. Starting uvicorn before neo4j is "
"healthy fails the first request every time."},
],
"label": "extract",
"expect_type": "runbook",
"note": "Ordered procedure with the failure mode of doing it out of order.",
},
{
"id": "e18_runbook_republish",
"window": [
{"role": "user", "content": "For a re-publish, don't delete and recreate the GitHub release - "
"dispatch the package workflow by hand instead: "
"`gh workflow run <package>-cd.yml --ref refs/tags/<tag> -f tag=<tag>`. "
"We did that last week and it worked."},
],
"label": "extract",
"expect_type": "runbook",
"note": "Verified recovery procedure including the thing not to do.",
},
{
"id": "e19_mixed_insight_bastion",
"window": [
{"role": "assistant", "content": "Training at epoch 1.2/3 (40%), ETA 38 minutes."},
{"role": "user", "content": "while that runs - remember that our staging DB only accepts connections "
"through the bastion host, direct psql always times out."},
{"role": "assistant", "content": "Noted. Still training, 41% now."},
],
"label": "extract",
"expect_type": "insight",
"note": "MIXED: durable fact buried between two heartbeats. The fact must survive, the progress must "
"not. Baseline: captured, with no heartbeat leakage.",
},
{
"id": "e20_mixed_decision_queue",
"window": [
{"role": "assistant", "content": "Progress for task bnzbd1uay: 301 of 928 chunks processed (32% "
"complete), ETA about 44 minutes."},
{"role": "user", "content": "One thing while we wait: we've decided the ingest queue stays at "
"concurrency 4. Anything higher and the embedding provider starts "
"rate-limiting us, which costs more time than it saves."},
{"role": "assistant", "content": "Progress for task bnzbd1uay: 318 of 928 chunks processed (34% "
"complete), ETA about 41 minutes."},
],
"label": "extract",
"expect_type": "decision",
"note": "MIXED: decision plus reasoning sandwiched between two identical-shape progress lines.",
},
{
"id": "e21_mixed_preference_monitoring",
"window": [
{"role": "assistant", "content": "monitor: api p50 121ms p99 655ms | queue depth 2 | workers 8/8 "
"healthy"},
{"role": "user", "content": "Going forward, when something breaks, show me the smallest repro before "
"you propose a fix. I don't want the fix until I've seen the repro."},
{"role": "assistant", "content": "monitor: api p50 119ms p99 640ms | queue depth 3 | workers 8/8 "
"healthy"},
],
"label": "extract",
"expect_type": "preference",
"note": "MIXED: standing preference between two monitoring lines.",
},
{
"id": "e22_mixed_convention_ingest",
"window": [
{"role": "assistant", "content": "markdown ingest still running (pid 48213, elapsed 00:22:40); "
"5,010 files indexed so far."},
{"role": "user", "content": "Also, house rule you should know: every new vector-store provider needs "
"a test directory under tests/vector_stores/ before the PR can merge. "
"Reviewers reject without it and it's not in the contributing guide."},
{"role": "assistant", "content": "markdown ingest still running (pid 48213, elapsed 00:24:11); "
"5,402 files indexed so far."},
],
"label": "extract",
"expect_type": "convention",
"note": "MIXED: undocumented team rule between two ingest heartbeats.",
},
]
FIXTURES: list[dict] = [*_DROP, *_EXCLUDE, *_EXTRACT]
def by_label(label: str) -> list[dict]:
"""All fixtures carrying `label`."""
return [f for f in FIXTURES if f["label"] == label]
def counts() -> dict[str, int]:
"""Fixture count per label, so the harness can report its own coverage."""
return {label: len(by_label(label)) for label in LABELS}
def counts_by_type() -> dict[str, int]:
"""Extract-fixture count per expected memory type."""
out: dict[str, int] = {}
for f in by_label("extract"):
t = f["expect_type"] or "unspecified"
out[t] = out.get(t, 0) + 1
return out
def get(fixture_id: str) -> dict | None:
return next((f for f in FIXTURES if f["id"] == fixture_id), None)
def coverage_line() -> str:
c = counts()
types = ", ".join(f"{k}={v}" for k, v in sorted(counts_by_type().items()))
return (f"{len(FIXTURES)} fixtures: drop={c['drop']} exclude={c['exclude']} extract={c['extract']} "
f"({types})")
if __name__ == "__main__": # `python3 eval/fixtures.py` prints coverage
print(coverage_line())