synops/docs/features/podcast_statistikk.md
vegard 00bf5d27ce Arkitekturbeslutninger: noder er sentrum, edges definerer alt
Grunnleggende arkitekturbeslutninger tatt og dokumentert:

- Alt er noder (brukere, team, innhold, mediefiler, samlings-noder)
- Edges definerer hva en node er (freeform typer, metadata i JSONB)
- Materialisert tilgangsmatrise (node_access) erstatter workspace-RLS
- Visibility (hidden/discoverable/readable/open) på noder
- Aliaser via usynlige system-edges
- Maskinrommet eier all skriving (SpacetimeDB først, PG asynk)
- SpacetimeDB holder hele grafen, PG er persistent backup
- Node- og edge-skjema spesifisert (docs/primitiver/)

Fjernet workspace-konseptet fra hele dokumentasjonen (~40 filer).
Fem retninger besluttet, én åpen (rom, ikke forum).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:29:54 +01:00

2.9 KiB

Feature Spec: Podcast-Statistikk

Filsti: docs/features/podcast_statistikk.md

1. Konsept

IAB-kompatibel lytterstatistikk bygget fra bunnen av. Vi fanger all rådata via Caddy, og bruker asynkron batch-prosessering for å bygge grafer og tall uten å belaste webserveren eller databasen med sanntids-skriving.

2. Arkitektur & Dataflyt

  1. Rådata (Caddy): Caddy konfigureres til å skrive access-logs for stien /media/podcast/*.mp3 til en formatert JSON-fil (f.eks. /srv/synops/logs/caddy/podcast_access.log).
  2. Logrotate: Standard Linux logrotate arkiverer loggene nattlig.
  3. Rust Batch Processor (Jobbkø): Statistikkparseren kjøres som en stats_parse-jobb i den felles jobbkøen (se docs/infra/jobbkø.md), med scheduled_for satt 1 time frem for periodisk kjøring. Workeren re-enqueuer seg selv ved fullføring.
    • Steg A (Filtrering): Leser JSON-loggen. Fjerner treff fra kjente bots ved å krysjekke User-Agent mot OPAWG (Open Podcast Analytics Working Group) sine åpne bot-lister.
    • Steg B (Deduplisering): Slår sammen byte-range forespørsler. Hvis samme IP og User-Agent har lastet ned deler av samme fil innenfor et 24-timers vindu, telles det som KUN én (1) nedlasting.
    • Steg C (Geografi/Klient): Mapper User-Agent til Podcast-klient (Spotify, Apple) basert på OPAWG-regler.
  4. Lagring (PostgreSQL): Rust-programmet skriver det aggregerte resultatet inn i PostgreSQL (episode_stats tabell med felter for tenant_id, date, episode_id, client_name, unique_downloads). Tenant-tilhørighet utledes fra filstien i loggen (/media/{tenant_slug}/...).

3. Personvern og GDPR

Caddy access-logger inneholder IP-adresser og User-Agent — dette er personopplysninger under GDPR.

Tiltak:

  • IP-anonymisering: Rust-workeren hasher IP-adresser (SHA-256 med daglig roterende salt) før de lagres i episode_stats. Rå IP-er lagres aldri i PostgreSQL.
  • Loggretensjon: Caddy-logger roteres og slettes etter 90 dager (kategori 4, flyktig). Etter at statistikk-workeren har prosessert en loggfil, inneholder PG kun aggregerte tall — ingen identifiserbar data.
  • Ingen sporing på tvers: Vi bruker ikke cookies, fingerprinting eller tredjepartstracking. Deduplisering basert på IP + User-Agent i 24-timers vindu er IAB-standard og minimumsdata.
  • Dokumentasjon: Podcastens RSS-feed bør lenke til en personvernerklæring som forklarer at anonymisert nedlastingsstatistikk samles inn.

4. Instruks for Claude Code

  • Bruk Rust-biblioteket serde_json for rask parsing av Caddy-loggene.
  • Dette programmet må skrives robust med tanke på at filer kan være låst av Caddy. Det bør tåle å avbrytes, og må holde styr på hvilken linje i loggfilen det prosesserte sist (f.eks. via en liten cursor-fil).
  • Rålogger skal ALDRI lagres i PostgreSQL.
  • Statistikk er tenant-scopet. episode_stats merkes med tenant_id. Admin-visningen filtrerer per tenant.