synops/migrations/012_ab_testing.sql
vegard 1425a82cdd Fullfører oppgave 14.17: A/B-testing for presentasjonselementer
Implementerer automatisk A/B-testing for forside-varianter:

- PG-migrasjon 012: ab_events-tabell for impression/klikk-logging
  med hour_of_week (0-167) for tidspunkt-normalisering
- Variant-rotasjon: ab_select() velger tilfeldig blant testing-varianter
  ved forside-rendering, winner prioriteres, retired filtreres bort
- Impression-logging: asynkron fire-and-forget ved forside-serve
  (både cache-hit og -miss), lagres i ab_events
- Klikk-attribusjon: artikkelbesøk sjekker forside-cache for aktive
  AB-varianter og logger klikk. Eksplisitt tracking via
  GET /pub/{slug}/t/{article_id}?v={edge_id}
- Periodisk evaluator (300s intervall): z-test for proporsjoner
  (p < 0.05), minimum 100 impressions per variant, oppdaterer
  edge-metadata (ab_status, impressions, clicks, ctr)
- Redaktør-overstyring: POST /intentions/ab_override markerer
  valgt variant som winner, andre som retired (krever owner/admin)
- Auto-initialisering: maybe_start_ab_test() setter ab_status=testing
  automatisk når >1 variant av samme type opprettes

Alle 42 tester passerer inkludert 3 nye z-test-tester.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 03:13:39 +00:00

37 lines
1.6 KiB
SQL

-- A/B-testing: impression/click-logging for presentasjonselement-varianter.
--
-- Når en artikkel har flere varianter av tittel/summary/og_image-noder,
-- roterer maskinrommet mellom dem på forsiden og logger visninger/klikk.
-- Etter statistisk signifikans markeres vinneren i edge-metadata.
--
-- Ref: docs/concepts/publisering.md § "Automatisk A/B-testing"
-- Oppgave: 14.17
-- Event-logg: hvert impression/klikk logges individuelt for tid-normalisering.
CREATE TABLE ab_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
edge_id UUID NOT NULL,
article_id UUID NOT NULL,
collection_id UUID NOT NULL,
event_type VARCHAR(16) NOT NULL CHECK (event_type IN ('impression', 'click')),
-- Timens posisjon i uken (0-167) for tidspunkt-normalisering.
-- Mandag 00:00 = 0, søndag 23:00 = 167.
hour_of_week SMALLINT NOT NULL DEFAULT (
EXTRACT(ISODOW FROM now())::int * 24
- 24
+ EXTRACT(HOUR FROM now())::int
),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Primær-oppslag: alle events for en bestemt edge
CREATE INDEX idx_ab_events_edge ON ab_events(edge_id, event_type);
-- Opprydning/aggregering per samling
CREATE INDEX idx_ab_events_collection ON ab_events(collection_id, created_at);
-- For periodisk evaluering: finn alle aktive tester per samling
CREATE INDEX idx_ab_events_article ON ab_events(article_id, edge_id);
COMMENT ON TABLE ab_events IS 'A/B-test impression/klikk-logg for presentasjonselement-varianter';
COMMENT ON COLUMN ab_events.hour_of_week IS 'Timens posisjon i uken (0-167) for CTR-normalisering mot tidspunkt-baseline';