diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 4af14c0..afb031d 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -138,8 +138,8 @@ | CSV-03 | Phase 1 | Complete | | CSV-04 | Phase 1 | Complete | | IMG-01 | Phase 1 | Complete | -| IMG-02 | Phase 4 | Pending | -| IMG-03 | Phase 4 | Pending | +| IMG-02 | Phase 4 | Complete | +| IMG-03 | Phase 4 | Complete | | IMG-04 | Phase 1 | Complete | | SWP-01 | Phase 3 | Complete | | SWP-02 | Phase 3 | Complete | @@ -167,4 +167,4 @@ --- *Requirements defined: 2026-03-07* -*Last updated: 2026-03-09 — Phase 3 requirements marked Complete* +*Last updated: 2026-03-09 — Phase 4 requirements marked Complete* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 0bace8b..bcd186f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -15,7 +15,7 @@ Decimal phases appear between their surrounding integers in numeric order. - [x] **Phase 1: Core Generation Pipeline** - Infrastruttura + pipeline calendario → LLM → CSV funzionante end-to-end - [x] **Phase 2: Prompt Control + Output Review** - Editor prompt via UI e anteprima caroselli prima dell'export - [x] **Phase 3: Organization Layer** - Swipe File e gestione storico campagne per workflow sostenibile -- [ ] **Phase 4: Enrichment** - Integrazione Unsplash, context injection da Swipe File, polish UI +- [x] **Phase 4: Enrichment** - Integrazione Unsplash, context injection da Swipe File, polish UI ## Phase Details @@ -82,8 +82,8 @@ Plans: **Plans**: 2 plans Plans: -- [ ] 04-01-PLAN.md — UnsplashService backend + Settings unsplash_api_key + integrazione pipeline/CSV con risoluzione keyword -> URL (Wave 1) -- [ ] 04-02-PLAN.md — Frontend: campo Unsplash in Settings, thumbnail cover in PostCard, hint OutputReview (Wave 2) +- [x] 04-01-PLAN.md — UnsplashService backend + Settings unsplash_api_key + integrazione pipeline/CSV con risoluzione keyword -> URL (Wave 1) +- [x] 04-02-PLAN.md — Frontend: campo Unsplash in Settings, thumbnail cover in PostCard, hint OutputReview (Wave 2) --- @@ -97,4 +97,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 | 1. Core Generation Pipeline | 4/4 | Complete | 2026-03-08 | | 2. Prompt Control + Output Review | 2/2 | Complete | 2026-03-08 | | 3. Organization Layer | 2/2 | Complete | 2026-03-09 | -| 4. Enrichment | 0/2 | Not started | - | +| 4. Enrichment | 2/2 | Complete | 2026-03-09 | diff --git a/.planning/phases/04-enrichment/04-VERIFICATION.md b/.planning/phases/04-enrichment/04-VERIFICATION.md new file mode 100644 index 0000000..e9c8789 --- /dev/null +++ b/.planning/phases/04-enrichment/04-VERIFICATION.md @@ -0,0 +1,112 @@ +--- +phase: 04-enrichment +verified: 2026-03-09T08:30:00Z +status: passed +score: 5/5 must-haves verified +--- + +# Phase 4: Enrichment Verification Report + +**Phase Goal:** URL immagini reali nel CSV quando API key Unsplash configurata. +**Verified:** 2026-03-09T08:30:00Z +**Status:** PASSED +**Re-verification:** No - initial verification + +## Note on Phase Goal vs. Implementation Scope + +The ROADMAP.md phase goal mentions two features: Unsplash integration AND Swipe File context injection. The CONTEXT.md for phase 4 explicitly resolves this: context injection from Swipe File is already covered by topic_overrides in Phase 3 and is deferred to v2. The ROADMAP success criteria (3 items) cover ONLY Unsplash. Requirements mapped to this phase are IMG-02 and IMG-03 only. + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | Se API key Unsplash configurata, il CSV contiene URL immagini reali | VERIFIED | _resolve_image() in CSVBuilder applica image_url_map su cover, slides s2-s7 e CTA. _resolve_unsplash_keywords() legge settings, crea UnsplashService, risolve keyword uniche e passa image_url_map a build_csv() | +| 2 | Se Unsplash non configurato o rate limit, il CSV usa keyword testuali senza errori | VERIFIED | unsplash_api_key None: ritorna None subito. _resolve_image() ritorna keyword originale quando image_url_map e None. Rate limit: flag _rate_limited=True quando X-Ratelimit-Remaining < 5, keyword restanti usano fallback testuale | +| 3 | Cache locale evita chiamate duplicate per keyword identiche | VERIFIED | resolve_keywords() controlla self._cache in-memory prima di ogni API call. Cache disco in data/unsplash_cache.json caricata all-init, salvata dopo ogni batch con nuove entries | +| 4 | Frontend mostra campo per configurare API key Unsplash | VERIFIED | Settings.tsx: sezione Immagini con input unsplash_api_key, toggle visibilita separato, helper text condizionale, delete-if-empty nel submit | +| 5 | PostCard mostra thumbnail quando cover_image_keyword e URL reale | VERIFIED | PostCard.tsx riga 185: startsWith(http) guard, img w-20 h-14 object-cover loading=lazy onError-fallback | + +**Score:** 5/5 truths verified + +### Required Artifacts + +| Artifact | Lines | Status | Details | +|----------|-------|--------|---------| +| backend/services/unsplash_service.py | 333 | VERIFIED | search_photo(), resolve_keywords(), _load_cache(), _save_cache(), close(). Dizionario IT->EN 30+ keyword B2B. 1 retry su errori rete. Rate limit via X-Ratelimit-Remaining header. | +| backend/schemas/settings.py | 54 | VERIFIED | unsplash_api_key: Optional[str] Field a riga 51 | +| backend/routers/settings.py | 172 | VERIFIED | unsplash_api_key_masked in SettingsResponse (riga 50), unsplash_api_key_configured in SettingsStatusResponse (riga 38), None-preserving merge nel PUT (righe 157-158) | +| backend/services/csv_builder.py | 214 | VERIFIED | _resolve_image() a riga 120. Applicato su cover_image_keyword (188), label_image_keyword per slides (197), cta_image_keyword (207). build_csv() e build_csv_content() accettano image_url_map opzionale. | +| backend/services/generation_pipeline.py | 685 | VERIFIED | image_url_map in JobStatus dataclass (riga 74), _resolve_unsplash_keywords() dopo il loop slot (riga 382), serializzato in _save_job_to_disk() (riga 628), deserializzato in _load_job_from_disk() (riga 672) | +| backend/routers/export.py | 161 | VERIFIED | Riga 117: image_url_map = job_data.get(image_url_map), passata a build_csv_content() a riga 136 | +| frontend/src/types.ts | 215 | VERIFIED | unsplash_api_key in Settings (riga 149), unsplash_api_key_configured: boolean in SettingsStatus (riga 155) | +| frontend/src/pages/Settings.tsx | 289 | VERIFIED | Sezione Immagini righe 151-179, state showUnsplashKey separato, helper text condizionale, delete-if-empty nel submit | +| frontend/src/components/PostCard.tsx | 299 | VERIFIED | Righe 185-195: startsWith(http) guard, img w-20 h-14 object-cover rounded-md loading=lazy, onError hide | +| frontend/src/pages/OutputReview.tsx | 215 | VERIFIED | Righe 161-172: useSettingsStatus() importato e usato, hint condizionale su \!unsplash_api_key_configured con Link to=/impostazioni | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| generation_pipeline.py | unsplash_service.py | UnsplashService.resolve_keywords() | WIRED | Import riga 34, istanza creata e usata in _resolve_unsplash_keywords() righe 587-601 | +| generation_pipeline.py | csv_builder.py | image_url_map passata a build_csv() | WIRED | Riga 400: image_url_map=image_url_map passata a self._csv.build_csv() | +| csv_builder.py | image_url_map | _resolve_image() su cover/slides/cta | WIRED | 3 call sites verificate: righe 188, 197, 207 | +| export.py | image_url_map dal job JSON | job_data.get(image_url_map) -> build_csv_content() | WIRED | Riga 117 recupera, riga 136 passa al builder | +| Settings.tsx | unsplash_api_key nel form | Input controlled, delete-if-empty nel submit | WIRED | Righe 160-161 input, 72-74 logica submit difensiva | +| OutputReview.tsx | useSettingsStatus | unsplash_api_key_configured per hint | WIRED | Riga 16 import, 24 hook, 161 conditional render | +| PostCard.tsx | cover_image_keyword | startsWith(http) per thumbnail condizionale | WIRED | Riga 185: conditional render basato su valore della prop | +| routers/settings.py | unsplash_api_key in Settings | GET/PUT con campo mascherato e None-preserving merge | WIRED | Righe 108, 131, 157-158, 171 | + +### Requirements Coverage + +| Requirement | Description | Status | Evidence | +|-------------|-------------|--------|----------| +| IMG-02 | Fetch immagini Unsplash, attivo solo se API key configurata | SATISFIED | UnsplashService con httpx.AsyncClient, attivato solo quando unsplash_api_key non None | +| IMG-03 | Cache locale per evitare hit ripetuti (50 req/h limite free tier) | SATISFIED | Cache in-memory self._cache + file JSON su disco data/unsplash_cache.json, persistente tra riavvii container | + +### Anti-Patterns Found + +Nessun anti-pattern bloccante rilevato. + +| File | Pattern | Severity | Impact | +|------|---------|----------|--------| +| Nessuno | - | - | - | + +### Human Verification Required + +**1. Risoluzione Unsplash end-to-end** + +Test: Configurare una API key Unsplash valida in Impostazioni, avviare generazione bulk, scaricare il CSV. +Expected: Le colonne *_image_keyword nel CSV contengono URL https://images.unsplash.com/... invece di keyword testuali. +Why human: Richiede API key Unsplash reale e connessione di rete verso api.unsplash.com. + +**2. Thumbnail visibile in OutputReview** + +Test: Dopo una generazione con Unsplash configurato, aprire OutputReview. +Expected: PostCard mostrano thumbnail 80x56px sotto il cover_title. Se URL non carica, img si nasconde silenziosamente. +Why human: Richiede URL Unsplash reali per verificare il rendering nel browser. + +**3. Hint scompare dopo configurazione** + +Test: Aprire OutputReview senza Unsplash configurato (hint visibile), configurare la key, tornare in OutputReview. +Expected: L-hint suggerimento scompare automaticamente. +Why human: Richiede interazione multi-step con la UI live. + +**4. Cache disco sopravvive al riavvio container** + +Test: Generazione con Unsplash, riavvio container Docker, seconda generazione con stesse keyword. +Expected: Log mostrano Cache Unsplash caricata all-avvio e Cache hit durante la seconda risoluzione. +Why human: Richiede accesso ai log del container Docker su VPS. + +## Gaps Summary + +Nessun gap trovato. Tutti gli artifact della fase 4 esistono, sono sostanziali e correttamente collegati. + +La seconda parte del goal ROADMAP (Swipe File context injection) e stata esplicitamente esclusa dallo scope dalla decisione documentata in 04-CONTEXT.md e classificata come Deferred v2. I 3 success criteria del ROADMAP per Phase 4 coprono esclusivamente l-integrazione Unsplash, completamente implementata. + +--- + +_Verified: 2026-03-09T08:30:00Z_ +_Verifier: Claude Sonnet 4.6 (gsd-verifier)_ +