4.9 KiB
4.9 KiB
Logboek
Bijgehouden tijdens de bouw. Per blok: wat ik deed, waarom, wat ik heb laten vallen.
Dag 1 - korte sessie in de avond
Opzet
Wat ik deed:
- Afspraken vastgelegd in AGENTS.md (architectuur, naamgeving, wanneer commentaar, wat de linters afdwingen).
- Backend-skelet: Python 3.13 + FastAPI via uv, mappenstructuur volgens ports-and-adapters (domain / ports / adapters / pipeline / api), config met alleen de app-mode (demo of live), health-endpoint met een smoke test.
- Frontend-skelet: Vue 3 + TypeScript via Vite, met eslint, prettier en vue-tsc.
- docker-compose die 1 image bouwt: frontend-build en API samen op dezelfde poort. De poort ligt vast omdat de geregistreerde Spotify redirect-URI moet kloppen.
- CI op elke push: ruff, mypy, pytest voor de backend; typecheck, lint en build voor de frontend. Groen vanaf de eerste commit.
- Repo op eigen Forgejo met een mirror naar GitHub.
Waarom:
- Structuur eerst, dan blijft elke volgende stap klein en controleerbaar.
docker compose upis het opleverbare, dus dat werkt vanaf het begin van development.- Alles Engels behalve dit logboek; ik volg de taal van de opdracht.
Wat ik heb laten vallen of uitgesteld:
- Domainmodellen, protocollen, prompts, pipeline-instellingen en het frontend-typecontract bewust nog niet neergezet. Die ontstaan in de stap waar ze horen. Ik probeer op die manier bewust vroeg drift en dode code te voorkomen.
- Geen apart beslisdocument. De motivering staat in de README en hier.
Dag 2
Spotify-koppeling
Wat ik deed:
- Login via Spotify met PKCE en cookie-sessies: tokens blijven server-side, de browser krijgt alleen een opaque HttpOnly cookie.
- Dunne async client op de Spotify Web API met per-endpoint retrybeleid: leesacties herhalen maximaal 1 keer en alleen binnen een grens (Retry-After), schrijfacties op playlists nooit.
- Token-refresh is single-flight: parallelle requests delen 1 refresh in plaats van er allemaal zelf een te starten.
- Mapping van Spotify-JSON naar eigen modellen op 1 plek; kapotte items vallen weg in plaats van dat ze de app breken.
- Getypeerde fouten en 12 transport- en routetests op een mock transport.
- Na een eerste review de login-flow uit de routes getrokken naar een eigen module (routes zijn nu dunne doorgeefluiken) en het retrybeleid herschreven naar een lineaire keten van losse regels in plaats van een loop met flags; foutdetails uit de Spotify-body worden meegenomen in de getypeerde fouten en quota-uitputting wordt apart herkend en nooit opnieuw geprobeerd.
- Daarna het API-contract vastgelegd: request-schema en de gestreamde events (metadata / track / warning / error / done), gespiegeld in TypeScript.
Waarom:
- Schrijfacties blind herhalen kan dubbele playlist-items opleveren; dat risico sluit ik structureel uit in het retrybeleid.
- Het contract eerst bevriezen maakt parallel werken aan frontend en pipeline mogelijk zonder elkaar te breken.
Wat ik heb laten vallen of uitgesteld:
- De naam-naar-ID cache en de port-interfaces doorgeschoven naar de pipeline-stap; daar bestaat het ontwerp pas echt.
- OpenAPI-codegen voor het contract overwogen en afgewezen: de kern van dit contract is de event-stream en die modelleert OpenAPI niet.
Pipeline
Wat ik deed:
- Domeinbasis: titel/artiest-matching (normalisatie, met tolerantie voor Spotify's versie-suffixen zoals "- Remaster 2023"), compressie van het Spotify-smaakprofiel naar prompttekst plus een set bekende track-ids, prompts als data in een eigen module, en alle instelbare waarden gesectioneerd in de config met per waarde het waarom.
- Twee LLM-aanroepen achter een eigen interface: aanroep 1 interpreteert de vraag (stemming, activiteit, taal, bekendheid) en stelt 30-40 echte nummers voor als gestructureerde output; aanroep 2 herordent uitsluitend geverifieerde nummers en streamt per nummer een eerlijke onderbouwing. Elke output wordt gevalideerd, met hooguit 1 herstelpoging.
- Grounding: begrensde parallelle zoekslag met vroege stop, een deadline, een naam-naar-id cache en twee aparte metrieken: niet gevonden versus wel gevonden maar afgekeurd door de controle. Een track-id dat niet in de geverifieerde pool zit kan nooit bij de gebruiker terechtkomen.
Waarom:
- De LLM is hier de aanbeveler, maar mag alleen creatief zijn tussen twee deterministische muren: alles wat hij ziet is echte data, alles wat de gebruiker ziet is geverifieerd op Spotify. Een verzonnen nummer valt stilletjes af en verschijnt nooit.
- Zoeken geeft maximaal 10 resultaten per aanroep, dus resolutie is per definitie een fan-out; liever een kandidaat laten vallen dan het verkeerde nummer aanbevelen.
Wat ik heb laten vallen of uitgesteld:
- Verfijnvragen doen geen nieuwe zoekslag: turn 2 herordent de bestaande geverifieerde pool. Sneller en consistent, maar een verfijning haalt geen nieuwe nummers op. Dit is een bewuste afweging, mocht er tijd over zijn is dit 1 van de uitbreidingen die ik op zou kunnen pakken.