Skip to content

feat(delphi): storage v2 P7c — umap 500s stage dual-write + verifier extension#2606

Open
jucor wants to merge 1 commit into
jc/delphi-storage-v2-p7bfrom
jc/delphi-storage-v2-p7c
Open

feat(delphi): storage v2 P7c — umap 500s stage dual-write + verifier extension#2606
jucor wants to merge 1 commit into
jc/delphi-storage-v2-p7bfrom
jc/delphi-storage-v2-p7c

Conversation

@jucor

@jucor jucor commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Stack 2 / P7c of the Storage V2 effort (design in #2597, §4.2 + §6.1 M1 + §7): the UMAP 500s stage-group dual-write — all seven stage outputs become v2 artifacts alongside the legacy tables, gated by DELPHI_WRITE_MODE, with the verifier extended to cover them.

Stacked on #2605 (jc/delphi-storage-v2-p7b).

The seam: a duck-typed wrapper, not seven edits

Unlike math (one funnel), the 500s stage writes through seven DynamoDBStorage method calls. The seam is DualWriteUmapStorage (umap_narrative/v2_artifacts.py): it duck-types exactly those seven methods, so all seven call sites in run_pipeline.py are untouched — the wrapper feeds the same already-materialized Pydantic model lists to the legacy tables and the v2 artifact store.

That makes the P7b serialize-once lesson hold by construction: the models carry baked datetime.now() fields (ConversationMeta.processed_date, LLMTopicName.created_at); re-instantiating them for a second write path would tag the two stores differently — the math_tick trap all over again.

Artifact keys per design §4.2: umap#meta, umap#embeddings#<chunk> (500/chunk — the fat vector rows), umap#graph#<chunk>, umap#assignments#<chunk> (5000/chunk), umap#keywords#<layer>, umap#features#<layer>, umap#topic#<layer>#<cluster> (the job-id-versioned LLM names; note the legacy model silently drops its job_id kwarg — only topic_key carries it, mirrored as-is).

  • make_umap_storage: old mode returns the plain legacy storage (zero overhead, byte-identical); both/v2 return the wrapper (v2-only runs with no legacy inner).
  • Failure policy: both → v2 failures logged, never raised; v2 → raised.
  • finalize() records stage_status.umap + a log line on the run manifest (best-effort for manifest-less standalone runs).
  • run_pipeline.py: write mode resolved up front (fail-fast — before process_comments and all writes, per the P7b lesson); the legacy client is constructed only when old writes are enabled.

Verifier extension

verify_umap_dual_write in scripts/verify_dual_write.py — independent reads of the seven conversation_id-keyed legacy tables vs the decoded umap# artifacts, numeric-tolerant. Same-run comparisons only: EVōC cluster ids are not stable across runs (unseeded until P8), but both writers consume the same in-memory arrays within a run, so parity holds by construction. CLI gains --stage math|umap|all.

Testing (TDD — 12 tests written first, RED on missing module)

Builders (chunking, per-layer grouping, per-cluster topic keys) from real Pydantic models; wrapper semantics (same-list dual writes, v2-only synthesis of the legacy result shape, both failure policies, manifest finalize); factory matrix; a source-level wiring pin on process_conversation; and an end-to-end parity test against real DynamoDB local — legacy write via the real DynamoDBStorage (tables created by the real create_evoc_tables), verify passes; corrupt umap#meta, verify catches it.

Full suite: 493 passed, 60 skipped, 58 xfailed (5 pre-existing DynamoDB-unavailable errors unchanged). Golden snapshots ran and passed — write-side only.

Stack

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Delphi Coverage Report

File Stmts Miss Cover
init.py 2 0 100%
benchmarks/bench_pca.py 76 76 0%
benchmarks/bench_repness.py 81 81 0%
benchmarks/bench_update_votes.py 38 38 0%
benchmarks/benchmark_utils.py 34 34 0%
components/init.py 1 0 100%
components/config.py 165 133 19%
conversation/init.py 2 0 100%
conversation/conversation.py 1062 301 72%
conversation/manager.py 131 42 68%
database/init.py 1 0 100%
database/dynamodb.py 388 225 42%
database/postgres.py 306 206 33%
database/v2_artifacts.py 60 0 100%
pca_kmeans_rep/init.py 5 0 100%
pca_kmeans_rep/clusters.py 257 22 91%
pca_kmeans_rep/corr.py 98 17 83%
pca_kmeans_rep/pca.py 52 16 69%
pca_kmeans_rep/repness.py 298 35 88%
regression/init.py 4 0 100%
regression/clojure_comparer.py 188 20 89%
regression/comparer.py 887 720 19%
regression/datasets.py 135 27 80%
regression/recorder.py 36 27 25%
regression/utils.py 138 94 32%
run_math_pipeline.py 311 83 73%
umap_narrative/500_generate_embedding_umap_cluster.py 210 109 48%
umap_narrative/501_calculate_comment_extremity.py 124 55 56%
umap_narrative/502_calculate_priorities.py 139 110 21%
umap_narrative/700_datamapplot_for_layer.py 503 469 7%
umap_narrative/701_static_datamapplot_for_layer.py 310 310 0%
umap_narrative/702_consensus_divisive_datamapplot.py 432 432 0%
umap_narrative/801_narrative_report_batch.py 802 740 8%
umap_narrative/802_process_batch_results.py 268 268 0%
umap_narrative/803_check_batch_status.py 183 183 0%
umap_narrative/llm_factory_constructor/init.py 2 0 100%
umap_narrative/llm_factory_constructor/model_provider.py 192 165 14%
umap_narrative/polismath_commentgraph/init.py 1 0 100%
umap_narrative/polismath_commentgraph/cli.py 270 270 0%
umap_narrative/polismath_commentgraph/core/init.py 3 3 0%
umap_narrative/polismath_commentgraph/core/clustering.py 108 108 0%
umap_narrative/polismath_commentgraph/core/embedding.py 104 104 0%
umap_narrative/polismath_commentgraph/lambda_handler.py 219 219 0%
umap_narrative/polismath_commentgraph/schemas/init.py 2 0 100%
umap_narrative/polismath_commentgraph/schemas/dynamo_models.py 160 4 98%
umap_narrative/polismath_commentgraph/tests/conftest.py 17 17 0%
umap_narrative/polismath_commentgraph/tests/test_clustering.py 74 74 0%
umap_narrative/polismath_commentgraph/tests/test_embedding.py 55 55 0%
umap_narrative/polismath_commentgraph/tests/test_storage.py 87 87 0%
umap_narrative/polismath_commentgraph/utils/init.py 3 0 100%
umap_narrative/polismath_commentgraph/utils/converter.py 283 226 20%
umap_narrative/polismath_commentgraph/utils/group_data.py 358 283 21%
umap_narrative/polismath_commentgraph/utils/storage.py 584 320 45%
umap_narrative/reset_conversation.py 165 50 70%
umap_narrative/run_pipeline.py 475 332 30%
umap_narrative/v2_artifacts.py 115 10 91%
utils/general.py 62 41 34%
Total 11066 7241 35%

…extension

Implements the UMAP stage-group of Stack 2 / P7 (docs/STORAGE_V2_DESIGN.md
§4.2, §6.1 M1, §7): the seven 500s outputs become v2 artifacts alongside the
legacy Delphi_* tables, gated by DELPHI_WRITE_MODE.

- umap_narrative/v2_artifacts.py — the dual-write seam is a DUCK-TYPED
  WRAPPER (DualWriteUmapStorage) over the legacy DynamoDBStorage:
  run_pipeline keeps calling the same seven write methods, and the wrapper
  feeds the SAME already-materialized Pydantic model lists to both stores.
  Serialize-once holds by construction — the models carry baked datetime
  fields (ConversationMeta.processed_date, LLMTopicName.created_at);
  re-instantiating them for a second path would be the math_tick trap again.
  Artifact keys per design §4.2: umap#meta, umap#embeddings#<chunk> (500/
  chunk — fat vector rows), umap#graph#<chunk>, umap#assignments#<chunk>
  (5000/chunk), umap#keywords#<layer>, umap#features#<layer>,
  umap#topic#<layer>#<cluster>. Failure policy: both → logged never raised;
  v2 → raised. finalize() records stage_status.umap + a log line on the
  manifest (best-effort for standalone runs).
- make_umap_storage — old mode returns the PLAIN legacy storage (zero
  overhead, byte-identical); both/v2 return the wrapper (v2-only with no
  legacy inner).
- run_pipeline.py — write mode resolved up front (fail-fast, before
  process_comments and all writes); the storage-init block constructs the
  legacy client only when old writes are enabled and routes everything
  through the factory; ALL SEVEN call sites are untouched. finalize() after
  layer processing.
- scripts/verify_dual_write.py — verify_umap_dual_write (independent reads
  of the seven conversation_id-keyed legacy tables vs decoded umap#
  artifacts; SAME-RUN comparisons only — EVōC cluster ids are not stable
  across runs); CLI gains --stage math|umap|all.

Tests (TDD; tests/test_umap_dual_write.py, RED first): 12 tests — builders
(chunking, per-layer grouping, per-cluster topic keys) from real Pydantic
models, wrapper semantics (same-list dual writes, v2-only synthesis,
failure policies, manifest finalize), factory matrix, run_pipeline wiring
pin, and an end-to-end parity test against real DynamoDB local (legacy
write via the REAL DynamoDBStorage, verify passes; corrupt umap#meta,
verify catches it).

Full suite: 493 passed, 60 skipped, 58 xfailed (5 pre-existing
DynamoDB-unavailable errors unchanged); golden snapshots ran and passed —
write-side only.

Stack: P1 #2597 … P7b #2605, P7c this commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jucor
jucor force-pushed the jc/delphi-storage-v2-p7c branch from 9073046 to 6e29d4e Compare July 10, 2026 11:40
@jucor
jucor force-pushed the jc/delphi-storage-v2-p7b branch from dcab2b1 to 381d2cf Compare July 10, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant