--- phase: 04-enrichment plan: 01 subsystem: api tags: [unsplash, httpx, image-resolution, cache, csv, settings] # Dependency graph requires: - phase: 01-core-generation-pipeline provides: CSVBuilder con colonne _image_keyword, Settings schema, GenerationPipeline con JobStatus - phase: 02-prompt-control-output-review provides: Export router con download CSV con edits provides: - UnsplashService con search, cache disco, traduzione IT->EN, retry, rate limit awareness - Campo unsplash_api_key in Settings schema e router (mascherato, None-preserving) - CSVBuilder con image_url_map opzionale per risoluzione keyword -> URL Unsplash - GenerationPipeline integra UnsplashService dopo batch LLM e salva image_url_map nel job JSON - Export router riutilizza image_url_map dal job originale per CSV con edits affects: [04-enrichment] # Tech tracking tech-stack: added: [httpx (async HTTP client per Unsplash API)] patterns: - Fallback trasparente: keyword non risolvibili restano testuali senza bloccare l'export - Cache in-memory + disco con persistenza tra riavvii (unsplash_cache.json) - Risoluzione batch post-LLM: Unsplash chiamato UNA SOLA VOLTA dopo il batch completo - image_url_map salvato nel job JSON per riuso in export con edits (no re-chiamata Unsplash) - None-preserving merge per nuovi campi API key (stesso pattern di api_key esistente) key-files: created: - backend/services/unsplash_service.py modified: - backend/schemas/settings.py - backend/routers/settings.py - backend/services/csv_builder.py - backend/services/generation_pipeline.py - backend/routers/export.py key-decisions: - "Risoluzione Unsplash avviene UNA SOLA VOLTA dopo il batch LLM, non ad ogni download CSV" - "image_url_map salvato nel job JSON: riusato da export con edits senza re-chiamare Unsplash" - "generate_single NON risolve Unsplash: velocita' e riuso map del job originale" - "Dizionario statico IT->EN con ~30 keyword B2B per traduzione (no API translation)" - "Fallback trasparente: keyword non risolte restano testuali, nessun errore bloccante" - "Rate limit: se X-Ratelimit-Remaining < 5, stop batch corrente con keyword restanti non risolte" - "No retry su 401/403 (API key invalida), 1 retry su errori di rete" patterns-established: - "UnsplashService chiuso con close() nel finally block dopo ogni risoluzione batch" - "_resolve_image() come metodo privato CSVBuilder per separare logica di risoluzione" - "Optional[dict[str, str]] come tipo per image_url_map in tutto il sistema" # Metrics duration: 5min completed: 2026-03-09 --- # Phase 4 Plan 01: Unsplash Integration Summary **UnsplashService con cache disco e traduzione IT->EN integrato nella pipeline: keyword immagine CSV diventano URL Unsplash reali (~1080px landscape) quando API key configurata, con fallback trasparente a keyword testuali** ## Performance - **Duration:** 5 min - **Started:** 2026-03-09T07:05:03Z - **Completed:** 2026-03-09T07:10:25Z - **Tasks:** 2 - **Files modified:** 6 ## Accomplishments - UnsplashService con search async, cache in-memory + disco, dizionario traduzione IT->EN (~30 keyword B2B), retry su errori rete, rate limit awareness via header - Settings schema e router aggiornati con unsplash_api_key (mascherata, None-preserving merge nel PUT) - CSVBuilder aggiornato con image_url_map opzionale: _resolve_image() applica URL Unsplash su cover, slides s2-s7 e CTA, con fallback a keyword testuale - GenerationPipeline integra _resolve_unsplash_keywords() dopo il batch LLM: carica settings, crea UnsplashService, risolve keyword uniche, salva image_url_map nel job JSON - Export router recupera image_url_map dal job JSON e la passa a build_csv_content() per CSV con edits ## Task Commits 1. **Task 1: UnsplashService + Settings unsplash_api_key** - `afba4c5` (feat) 2. **Task 2: Integrazione pipeline + CSV con risoluzione Unsplash** - `9e7205e` (feat) ## Files Created/Modified - `backend/services/unsplash_service.py` - UnsplashService con search, cache, traduzione IT->EN, retry, rate limit - `backend/schemas/settings.py` - Campo unsplash_api_key Optional[str] aggiunto a Settings - `backend/routers/settings.py` - unsplash_api_key_masked in SettingsResponse, unsplash_api_key_configured in SettingsStatusResponse, merge None-preserving nel PUT - `backend/services/csv_builder.py` - image_url_map opzionale in build_csv/build_csv_content/_build_rows, metodo _resolve_image() - `backend/services/generation_pipeline.py` - image_url_map in JobStatus (dataclass + serializzazione JSON), metodo _resolve_unsplash_keywords(), import UnsplashService - `backend/routers/export.py` - Recupera image_url_map dal job JSON e passa a build_csv_content() ## Decisions Made - **Risoluzione UNA SOLA VOLTA**: Unsplash chiamato dopo il batch LLM completo, image_url_map salvata nel job JSON per riuso in export con edits senza re-chiamata API - **generate_single non risolve Unsplash**: La rigenerazione singola e' veloce e deve restare tale; le keyword nuove useranno il fallback testuale nel CSV - **Dizionario statico IT->EN**: ~30 keyword B2B comuni tradotte; parole non trovate restano invariate (molte keyword di contesto sono gia' in inglese per Unsplash) - **Fallback trasparente**: keyword non risolvibili (errori, rate limit, nessun risultato) non compaiono nel dizionario; il caller usa la keyword originale senza eccezioni - **Rate limit awareness**: se X-Ratelimit-Remaining < 5, flag self._rate_limited = True e stop per il batch corrente ## Deviations from Plan None - piano eseguito esattamente come scritto. ## Issues Encountered None. ## User Setup Required Per usare l'integrazione Unsplash: 1. Creare un account sviluppatore su https://unsplash.com/developers 2. Creare un'applicazione e copiare il Client-ID (Access Key) 3. Inserire il Client-ID nel campo "Chiave API Unsplash" nelle Impostazioni del backend Nessuna configurazione del server richiesta — la funzionalita' e' opt-in e il sistema funziona normalmente senza la chiave. ## Next Phase Readiness - Integrazione Unsplash backend completa e pronta per deploy su VPS - Il frontend non e' ancora aggiornato: le Impostazioni non mostrano il campo Unsplash API key (necessario per Phase 4 Plan 02 o aggiornamento standalone) - Il CSV con URL Unsplash funziona end-to-end: generazione batch → risoluzione keyword → CSV con URL → export con edits riutilizza gli URL - Cache disco (unsplash_cache.json) pronta: il volume Docker nel VPS deve includere DATA_PATH per persistenza --- *Phase: 04-enrichment* *Completed: 2026-03-09*