9.9 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12-tender-notifications | 01 | api |
|
|
|
|
|
|
|
|
|
|
35min | 2026-07-22 | complete |
Phase 12 Plan 01: Schema + Delta-Only Matching Engine Summary
TenderMatch/TenderNotificationPref schema (single notifiedAt eligibility gate) plus a delta-only TenderMatchingService wired into pollDueSources — structurally prevents any backfill-flood on new saved-search profiles.
Performance
- Duration: ~35 min
- Completed: 2026-07-22T09:06:00Z
- Tasks: 3 completed (Task 2 followed RED→GREEN TDD)
- Files modified: 7 (3 created, 4 modified)
Accomplishments
- Added
TenderMatch(one row per tender × savedSearch pair,@@unique([tenderId, savedSearchId]), single nullablenotifiedAtas the matched-vs-notified invariant gate) andTenderNotificationPref(per-user digest interval, defaultdaily) models; addedTenderSavedSearch.instantAlert Boolean @default(false). - Hand-written migration
20260722100000_add_tender_notificationsapplied to the local dev DB (tesseradatabase, containertessera-ctl-db-1) and recorded in_prisma_migrations;prisma generaterun so the client knows the new models. - Implemented
TenderMatchingService.matchDelta(newTenderIds): for every activeTenderSavedSearch, reusesbuildTenderWhere(profile.filters)AND-ed with{ id: { in: newTenderIds } }, and upsertsTenderMatchidempotently (update: {}preserves any already-setnotifiedAt). - Extended
TenderIngestionService.pollDueSources: an indexeddedupKeypre-check now distinguishes genuinely-new tender rows from changed/re-seen ones; only the new IDs are handed tomatchDeltaat the end of the tick (delta-only boundary, D-07 — no historical rescan, no backfill/suppression table). - Registered
TenderMatchingServiceas a provider inTendersModuleand injected it intoTenderIngestionService.
Task Commits
Each task was committed atomically:
- Task 1: Schema models + hand-written migration + local DB apply -
6c3e110(feat) - Task 2: TenderMatchingService.matchDelta (TDD) -
57d22a0(test, RED) →92d4c96(feat, GREEN) - Task 3: Wire matchDelta into pollDueSources + provider registration -
b45047f(feat)
Plan metadata: commit pending (this SUMMARY + STATE/ROADMAP update)
Files Created/Modified
apps/api/prisma/schema.prisma- AddedTenderMatch,TenderNotificationPrefmodels;TenderSavedSearch.instantAlert+matchesrelation;Tender.matchesrelationapps/api/prisma/migrations/20260722100000_add_tender_notifications/migration.sql- Hand-written migration, applied to local dev DBapps/api/src/tenders/tender-matching.service.ts-matchDelta(newTenderIds): delta-only matching engineapps/api/src/tenders/tender-matching.service.spec.ts- 6 unit tests (delta-only, match creation, idempotency, empty-delta)apps/api/src/tenders/tender-ingestion.service.ts- Collects genuinely-new tender IDs; callsmatchDeltaat tick endapps/api/src/tenders/tender-ingestion.service.spec.ts- 2 new tests asserting delta-only invocation ofmatchDeltaapps/api/src/tenders/tenders.module.ts-TenderMatchingServiceregistered as provider
Decisions Made
- Single
notifiedAtfield (not two per-channel timestamps) — matches RESEARCH.md's explicit recommendation; simpler invariant, no risk of implying double-send is allowed per channel. - Delta-only matching (no backfill/suppression table) as the structural mechanism for D-07 — verified:
matchDelta([])performs zero DB access, and a large simulated pre-existing catalog (2188 IDs) that would all match a profile's filters still yields zero matches when none of those IDs are passed asnewTenderIds. - Added per-profile try/catch inside
matchDelta(Rule 2 — matches the establishedpollDueSourcescatch-and-log convention; not explicitly specified in the plan's action text, but the plan's own<done>criteria and RESEARCH.md's "Don't Hand-Roll"/pitfalls make single-profile robustness an implicit correctness requirement, since a malformedfiltersJSON on one profile must not silently prevent matching for all other profiles in the same tick).
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Plan's verify command referenced the wrong local DB name
- Found during: Task 1 verification
- Issue: The plan's
<verify>block and12-VALIDATION.mdreferencedocker compose exec ... -d tessera_dev, butdocker-compose.ymlactually provisionsPOSTGRES_DB: tessera(confirmed viadocker exec tessera-ctl-db-1 psql -U tessera -d tessera -c "\dt", which lists all existing Tender/TenderSavedSearch/TenderTriage tables).tessera_devis the DB password, not the DB name. - Fix: Applied the migration and ran all verification (
to_regclass,\d,prisma validate/generate) against-d tesserainstead. No schema/code change — purely a corrected command target. - Verification:
SELECT to_regclass('public."TenderMatch"'), to_regclass('public."TenderNotificationPref"')returned both table OIDs (non-null);\d "TenderSavedSearch"showsinstantAlert boolean not null default false. - Committed in:
6c3e110(Task 1 commit)
Total deviations: 1 auto-fixed (1 bug — wrong DB name in verify command, not a code defect) Impact on plan: No scope creep; purely a corrected verification target. All three tasks executed exactly as specified otherwise.
Issues Encountered
None beyond the DB-name correction above. gen_random_uuid() was available on the DB for the manual _prisma_migrations bookkeeping insert (Postgres 16 built-in via pgcrypto/native function).
User Setup Required
None - no external service configuration required. Migration was applied directly to the local dev DB per the environment constraints (no Docker rebuild, no test/prod server touched).
Next Phase Readiness
TenderMatchrows are now created (withnotifiedAt=NULL) for every genuinely-new tender that matches an active saved search — ready for Plan 12-02 (digest send) and 12-03 (instant alerts) to consume viaWHERE notifiedAt IS NULL.TenderNotificationPrefandTenderSavedSearch.instantAlertare in place for Plan 12-04's settings UI.- No blockers.
pnpm --filter api test(full suite) is green: 172/172 tests across 15 files.npx tsc --noEmitclean.
Phase: 12-tender-notifications Completed: 2026-07-22
Self-Check: PASSED
All created files verified present on disk; all 4 task commit hashes (6c3e110, 57d22a0, 92d4c96, b45047f) verified present in git log.