Repara criteriul de acceptare "dashboardurile citesc read models, nu interogheaza haotic toate modulele". - projection_processed_events (unique: name+version+event_id) = garantia de idempotency. Checkpointul e doar optimizare de scanare, nu corectitudine: rescanam cu un safety lag de 60s si sarim ce s-a aplicat deja, ca sa nu pierdem evenimente comise dupa unul cu created_at mai mare. - ProjectionRegistry cu ProjectionDefinition (name, version, subscribedEvents, rebuildStrategy, apply, rebuildTenant). - executive_dashboard foloseste rebuildStrategy 'canonical-tables': recalculeaza contorii din tabelele canonice, nu incrementeaza. Contorii incrementali pot deriva daca un eveniment se pierde sau se dubleaza; recalcularea e corecta prin constructie si idempotenta natural. Costul e o interogare per eveniment relevant -- ok la volumul actual. - GET /v1/dashboard/executive citeste proiectia. Cand proiectia inca n-a rulat pentru workspace, intoarce status 'building' explicit, NU zerouri care ar parea date reale. - POST /v1/dashboard/executive/rebuild -- owner/admin, auditat. - Serviciile de taskuri/organizatii/segmente emit acum evenimente in outbox (in aceeasi tranzactie cu scrierea). Fara ele proiectia era cod mort. - Campurile din spec care depind de module neconstruite (documents, transactions, approvals) raman 0 explicit, nu inventate.
47 lines
2.1 KiB
SQL
47 lines
2.1 KiB
SQL
-- Projection Kernel (Sprint 1). Read models: NU sunt sursa de adevar, pot fi
|
|
-- sterse si reconstruite oricand din tabelele canonice.
|
|
|
|
-- Garantia de idempotency: acelasi eveniment nu se aplica de doua ori.
|
|
CREATE TABLE IF NOT EXISTS "projection_processed_events" (
|
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
"projection_name" text NOT NULL,
|
|
"projection_version" integer NOT NULL,
|
|
"event_id" uuid NOT NULL,
|
|
"processed_at" timestamp DEFAULT now() NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "projection_processed_uq"
|
|
ON "projection_processed_events" ("projection_name","projection_version","event_id");
|
|
|
|
-- Checkpoint = optimizare (de unde reia scanarea), nu garantia de corectitudine.
|
|
CREATE TABLE IF NOT EXISTS "projection_checkpoints" (
|
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
"projection_name" text NOT NULL,
|
|
"projection_version" integer NOT NULL,
|
|
"last_event_at" timestamp,
|
|
"last_event_id" uuid,
|
|
"status" text DEFAULT 'active' NOT NULL,
|
|
"last_error" text,
|
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "projection_checkpoint_uq"
|
|
ON "projection_checkpoints" ("projection_name","projection_version");
|
|
|
|
CREATE TABLE IF NOT EXISTS "executive_dashboard_projection" (
|
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
"tenant_id" uuid NOT NULL,
|
|
"workspace_id" uuid NOT NULL,
|
|
"open_tasks_count" integer DEFAULT 0 NOT NULL,
|
|
"overdue_tasks_count" integer DEFAULT 0 NOT NULL,
|
|
"deadlines_next_7_days" integer DEFAULT 0 NOT NULL,
|
|
"organizations_count" integer DEFAULT 0 NOT NULL,
|
|
"segments_count" integer DEFAULT 0 NOT NULL,
|
|
"research_briefs_count" integer DEFAULT 0 NOT NULL,
|
|
"documents_missing_count" integer DEFAULT 0 NOT NULL,
|
|
"transactions_unclassified_count" integer DEFAULT 0 NOT NULL,
|
|
"pending_approvals_count" integer DEFAULT 0 NOT NULL,
|
|
"generated_at" timestamp DEFAULT now() NOT NULL,
|
|
"projection_version" integer DEFAULT 1 NOT NULL
|
|
);
|
|
-- Cheia de UPSERT (spec §5.3): o linie per tenant+workspace.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "executive_dashboard_tenant_ws_uq"
|
|
ON "executive_dashboard_projection" ("tenant_id","workspace_id");
|