diff --git a/pipeline/diagrams/future-activity-diagram.puml b/pipeline/diagrams/future-activity-diagram.puml index 51184a3..d66fe99 100644 --- a/pipeline/diagrams/future-activity-diagram.puml +++ b/pipeline/diagrams/future-activity-diagram.puml @@ -1,18 +1,26 @@ -@startuml future_possible_activity +@startuml biergarten_activity skinparam defaultFontName "DM Sans" skinparam defaultFontSize 13 skinparam titleFontName "Volkhov" skinparam titleFontSize 20 -skinparam backgroundColor #FAFCF9 -skinparam defaultFontColor #28342A -skinparam titleFontColor #28342A -skinparam ArrowColor #628A5B -skinparam ActivityBackgroundColor #EAF0E8 -skinparam ActivityBorderColor #547461 -skinparam ActivityDiamondBackgroundColor #DCE8D8 -skinparam ActivityDiamondBorderColor #547461 -skinparam NoteBackgroundColor #EAF0E8 -skinparam NoteBorderColor #547461 +skinparam backgroundColor #FCFCF7 +skinparam defaultFontColor #14180C +skinparam titleFontColor #14180C +skinparam ArrowColor #656F33 +skinparam activityStartColor #EBECE3 +skinparam activityEndColor #4A5837 +skinparam activityStopColor #4A5837 +skinparam ActivityBackgroundColor #EBECE3 +skinparam ActivityBorderColor #4A5837 +skinparam ActivityDiamondBackgroundColor #CBD2B5 +skinparam ActivityDiamondBorderColor #4A5837 +skinparam NoteBackgroundColor #DBEEDD +skinparam NoteFontColor #14180C +skinparam NoteBorderColor #4A5837 +skinparam SwimlaneBorderColor #4A5837 +skinparam SwimlaneBorderThickness 1 +skinparam monochrome reverse + title The Biergarten Data Pipeline — Activity Diagram @@ -27,6 +35,25 @@ endif :Init CurlGlobalState & LlamaBackendState; :Build DI injector; + +:Initialize SqliteExportService; +note right + Opens SQLite connection. + Begins a single transaction + covering all five fixture types. +end note + +:Create BoundedChannel log_ch; +:Spawn Log Worker thread; +note right + Log worker drains log_ch for the + entire pipeline lifetime. + All workers emit LogEntry structs + via PipelineLogger — never spdlog directly. +end note + +:BiergartenPipelineOrchestrator::Run(); +|BiergartenPipelineOrchestrator::Run()| :JsonLoader::LoadLocations("locations.json"); :JsonLoader::LoadBeerStyles("beer-styles.json"); :JsonLoader::LoadPersonas("personas.json"); @@ -42,17 +69,10 @@ end note :EnrichmentService::PreWarmPersonaCache(personas); note right Persona descriptions do not need location context. - All persona Wikipedia/description lookups are - resolved and cached globally at startup. + All persona lookups are resolved and cached + globally at startup. end note -:Initialize SqliteExportService; -note right - Opens SQLite connection. - Begins a single transaction - covering all five fixture types. -end note -:BiergartenPipelineOrchestrator::Run(); ' ═══════════════════════════════════════════ ' PHASE 0 — USER GENERATION @@ -73,24 +93,27 @@ fork again :IPersonaSelectionStrategy::SelectPersona(\n personas_palette_); note right Guaranteed cache hit from startup. - Returns a Persona struct with style_affinities, - abv_range, ibu_preference, checkin_weight. + Returns a Persona struct carrying + style_affinities, abv_range, + ibu_preference, checkin_weight. end note :NamesByCountry::SampleName(\n location.iso3166_1); note right Deterministic lookup — no LLM involved. - Name is selected from a pre-keyed table + Name selected from pre-keyed table and passed into the generation prompt. end note :GenerateUser(location, persona, sampled_name)\nvia DataGenerator; note right - LLM receives: Location fields + persona description - + sampled name. Generates bio and preference - signals grounded in both. + LLM receives: Location fields + persona + description + sampled name. Generates + bio and preference signals grounded + in locale and persona. end note + :PipelineLogger::Log(Info, UserGeneration,\n city, user_id, "llm"); :Send GeneratedUser → llm_ch; endwhile (no) :Close llm_ch; @@ -99,6 +122,7 @@ fork again while (llm_ch has items?) is (yes) :Receive GeneratedUser; :ProcessUser(user) → sqlite3_int64; + :PipelineLogger::Log(Info, UserGeneration,\n city, user_id, "sqlite"); :Append → user_pool_; endwhile (no) end fork @@ -108,7 +132,6 @@ end fork ' ═══════════════════════════════════════════ ' PHASE 1 — BREWERY & BEER GENERATION -' Combined into a single dependent unit of work. ' ═══════════════════════════════════════════ :RunBreweryAndBeerPhase(sampled_locations); :Create BoundedChannels\n(loc_ch, llm_ch, exp_ch); @@ -122,6 +145,7 @@ fork again while (loc_ch has items?) is (yes) :Receive Location; :GetLocationContext(location,\nBreweryContextStrategy); + :PipelineLogger::Log(Info,\n BreweryAndBeerGeneration,\n city, nullopt, "enrichment"); :Send EnrichedCity → llm_ch; endwhile (no) |Orchestrator| @@ -145,12 +169,8 @@ fork again :Attach GeneratedBeer to Brewery bundle; endwhile (done) + :PipelineLogger::Log(Info,\n BreweryAndBeerGeneration,\n city, brewery_id, "llm"); :Send BreweryWithBeers Bundle → exp_ch; - note right - The next generation of a brewery is - entirely dependent on the current - brewery and its beers completing. - end note endwhile (no) :Close exp_ch; fork again @@ -165,6 +185,8 @@ fork again :ProcessBeer(beer) → sqlite3_int64; :Append → beer_pool_; endwhile (done) + + :PipelineLogger::Log(Info,\n BreweryAndBeerGeneration,\n city, brewery_id, "sqlite"); endwhile (no) end fork @@ -177,16 +199,13 @@ end note ' ═══════════════════════════════════════════ ' PHASE 2 — CHECKIN GENERATION -' Sequential now that Breweries/Beers are done. ' ═══════════════════════════════════════════ :RunCheckinPhase(); :ICheckinDistributionStrategy::\nAssignActivityWeights(user_pool_); note right - Weights are seeded from each user's - persona.checkin_weight — high-activity - personas (craft enthusiasts) check in more, - casual personas less. J-curve profile - emerges from the persona distribution. + Weights seeded from each user's + persona.checkin_weight. J-curve profile + emerges from persona distribution. end note while (For each GeneratedUser in user_pool_?) is (remaining) @@ -196,6 +215,7 @@ while (For each GeneratedUser in user_pool_?) is (remaining) :Select brewery from brewery_pool_; :GenerateCheckin(user, brewery, timestamp)\nvia DataGenerator; :ProcessCheckin(checkin) → sqlite3_int64; + :PipelineLogger::Log(Info, CheckinGeneration,\n nullopt, checkin_id, "sqlite"); :Append → checkin_pool_; endwhile (done) endwhile (done) @@ -205,11 +225,9 @@ endwhile (done) ' ═══════════════════════════════════════════ :RunRatingPhase(); note right - Beer selection during rating is biased by - user.persona.style_affinities and abv_range — - users are more likely to rate beers matching - their persona profile. Rating skew (positive - with long tail) is also modulated per persona. + Beer selection biased by + user.persona.style_affinities and abv_range. + Rating skew modulated per persona. end note while (For each GeneratedCheckin in checkin_pool_?) is (remaining) @@ -217,7 +235,9 @@ while (For each GeneratedCheckin in checkin_pool_?) is (remaining) if (Beer exists for brewery?) then (yes) :GenerateRating(user, beer, checkin_id)\nvia DataGenerator; :ProcessRating(rating); + :PipelineLogger::Log(Info, RatingGeneration,\n nullopt, rating_id, "sqlite"); else (no) + :PipelineLogger::Log(Warn, RatingGeneration,\n nullopt, brewery_id, "sqlite"); :Skip — brewery has no beers; endif endwhile (done) @@ -230,6 +250,12 @@ endwhile (done) note right COMMIT covers all five fixture types. end note +:Close log_ch; +:Join Log Worker; +note right + Drain guarantees no LogEntry is + dropped at shutdown. +end note :spdlog::info "Pipeline complete in X ms"; stop diff --git a/pipeline/diagrams/future-class-diagram.puml b/pipeline/diagrams/future-class-diagram.puml index 076749b..1716c82 100644 --- a/pipeline/diagrams/future-class-diagram.puml +++ b/pipeline/diagrams/future-class-diagram.puml @@ -1,40 +1,52 @@ @startuml future_possible_architecture -skinparam style strictuml + +' ========================================== +' CONFIGURATION & STYLING +' ========================================== +left to right direction +skinparam linetype ortho + +' --- Typography --- skinparam defaultFontName "DM Sans" skinparam defaultFontSize 14 skinparam titleFontName "Volkhov" skinparam titleFontSize 20 -skinparam backgroundColor #FAFCF9 -skinparam defaultFontColor #28342A -skinparam titleFontColor #28342A -skinparam ArrowColor #628A5B -skinparam linetype ortho -skinparam class { - BackgroundColor #FAFCF9 - HeaderBackgroundColor #EAF0E8 - BorderColor #547461 - ArrowColor #628A5B - FontColor #28342A -} -skinparam note { - BackgroundColor #EAF0E8 - BorderColor #547461 - FontColor #28342A +' --- Global Colors --- +skinparam backgroundColor #FCFCF7 +skinparam defaultFontColor #14180C +skinparam titleFontColor #14180C +skinparam ArrowColor #656F33 + +skinparam class { + BackgroundColor #EBECE3 + HeaderBackgroundColor #CBD2B5 + BorderColor #4A5837 + ArrowColor #656F33 + FontColor #14180C } skinparam package { - BackgroundColor #F2F6F0 - BorderColor #547461 - FontColor #28342A + BackgroundColor #DBEEDD + BorderColor #4A5837 + FontColor #14180C } +skinparam note { + BackgroundColor #DBEEDD + BorderColor #4A5837 + FontColor #14180C +} + +skinparam monochrome reverse + title The Biergarten Data Pipeline — Planned Architecture -left to right direction - - +' ========================================== +' DOMAIN MODELS +' ========================================== package "Domain Models" { + class Location { + city : std::string + state_province : std::string @@ -70,15 +82,6 @@ package "Domain Models" { + min_ibu : int + max_ibu : int } - note right of BeerStyle - Loaded once at startup from - beer-styles.json via JsonLoader. - Passed as std::span - to IBeerSelectionStrategy. - Generator receives the selected - style as a parameter — it never - reads the palette directly. - end note class BreweryResult { + name_en : std::string @@ -102,13 +105,6 @@ package "Domain Models" { + bio : std::string + activity_weight : float } - note right of UserResult - activity_weight assigned by - ICheckinDistributionStrategy - after the full user pool is - committed. Drives J-curve - checkin volume per user. - end note class CheckinResult { + checked_in_at : std::string @@ -143,11 +139,6 @@ package "Domain Models" { + user : UserResult + generated_at : std::string } - note right of GeneratedUser - user_id populated after SQLite - insert. Live FK carried in pool - for checkin and rating references. - end note class GeneratedCheckin { + checkin_id : sqlite3_int64 @@ -172,38 +163,93 @@ package "Domain Models" { + n_ctx : uint32_t = 8192 + seed : int = -1 } - note right of SamplingOptions - Ignored when GeneratorOptions:: - use_mocked = true. - end note class GeneratorOptions { - + model_path : std::string + + model_path : std::filesystem::path + use_mocked : bool = false + sampling : SamplingOptions } class PipelineOptions { + + output_path : std::filesystem::path + + log_path : std::filesystem::path } - note right of PipelineOptions - Reserved for future config: - n_locations, concurrency, - output_path, etc. - end note class ApplicationOptions { + generator : GeneratorOptions + pipeline : PipelineOptions } + ' --- Domain Model Relationships --- ApplicationOptions *-- GeneratorOptions ApplicationOptions *-- PipelineOptions - GeneratorOptions *-- SamplingOptions + GeneratorOptions *-- SamplingOptions + LocationContext *-- Completeness } + +' ========================================== +' LOGGING +' ========================================== +package "Logging" { + + enum LogLevel { + Debug + Info + Warn + Error + } + + enum PipelinePhase { + Startup + UserGeneration + BreweryAndBeerGeneration + CheckinGeneration + RatingGeneration + Teardown + } + + class LogEntry { + + timestamp : std::chrono::system_clock::time_point + + level : LogLevel + + phase : PipelinePhase + + message : std::string + + city : std::optional + + entity_id : std::optional + + worker : std::optional + } + + interface Logger <> { + + Log(level, phase, message,\n city, entity_id, worker) : void + } + + class PipelineLogger { + - log_ch_ : BoundedChannel& + + Log(level, phase, message,\n city, entity_id, worker) : void + } + + class LogWorker { + - log_ch_ : BoundedChannel& + + Run() : void + - FormatTimestamp(tp) : std::string + - ToSpdlogLevel(level) : spdlog::level::level_enum + - ToString(phase) : std::string + } + + ' --- Logging Relationships --- + LogEntry *-- LogLevel + LogEntry *-- PipelinePhase + PipelineLogger ..> LogEntry : emits + LogWorker ..> LogEntry : consumes +} + + +' ========================================== +' DOMAIN POLICY +' ========================================== package "Domain Policy" { - interface IContextStrategy <> { + interface ContextStrategy <> { + QueriesFor(loc : const Location&) : std::vector + MaxContextChars() : size_t } @@ -218,7 +264,7 @@ package "Domain Policy" { + MaxContextChars() : size_t } - interface ISamplingStrategy <> { + interface SamplingStrategy <> { + Sample(locations : const std::vector&) : std::vector } @@ -227,16 +273,9 @@ package "Domain Policy" { + Sample(locations : const std::vector&) : std::vector } - interface IBeerSelectionStrategy <> { + interface BeerSelectionStrategy <> { + SelectStyles(brewery : const GeneratedBrewery&,\n palette : std::span) : std::vector } - note right of IBeerSelectionStrategy - Decides how many beers a brewery - gets and which styles are selected. - Count distribution and style - deduplication logic live here, - not in the orchestrator or generator. - end note class RandomBeerSelectionStrategy { - rng_ : std::mt19937 @@ -244,24 +283,12 @@ package "Domain Policy" { - max_beers_ : size_t + SelectStyles(brewery : const GeneratedBrewery&,\n palette : std::span) : std::vector } - note right of RandomBeerSelectionStrategy - Draws a random count in [min, max]. - Samples without replacement from - palette to avoid duplicate styles - per brewery. - end note - interface ICheckinDistributionStrategy <> { + interface CheckinDistributionStrategy <> { + AssignActivityWeights(users : std::vector&) : void + CheckinsForUser(user : const GeneratedUser&,\n brewery_count : size_t) : size_t + TimestampFor(user : const GeneratedUser&,\n index : size_t) : std::string } - note right of ICheckinDistributionStrategy - Owns all statistical policy: - J-curve weight assignment, - bursty weekend timestamps, - per-user checkin volume. - end note class JCurveCheckinStrategy { - rng_ : std::mt19937 @@ -273,17 +300,28 @@ package "Domain Policy" { } - +' ========================================== +' ORCHESTRATION +' ========================================== package "Orchestration" { + interface DataPreloader <> { + + LoadLocations(filepath : const std::filesystem::path&) : std::vector + + LoadBeerStyles(filepath : const std::filesystem::path&) : std::vector + + LoadPersonas(filepath : const std::filesystem::path&) : std::vector + + LoadNamesByCountry(filepath : const std::filesystem::path&) : NamesByCountry + } + class BiergartenPipelineOrchestrator { - - enrichment_service_ : std::unique_ptr + - preloader_ : std::unique_ptr + - enrichment_service_ : std::unique_ptr - generator_ : std::unique_ptr - - exporter_ : std::unique_ptr - - brewery_context_strategy_ : std::unique_ptr - - sampling_strategy_ : std::unique_ptr - - beer_selection_strategy_ : std::unique_ptr - - checkin_strategy_ : std::unique_ptr + - logger_ : std::unique_ptr + - exporter_ : std::unique_ptr + - brewery_context_strategy_ : std::unique_ptr + - sampling_strategy_ : std::unique_ptr + - beer_selection_strategy_ : std::unique_ptr + - checkin_strategy_ : std::unique_ptr - beer_style_palette_ : std::vector - options_ : ApplicationOptions -- @@ -298,33 +336,39 @@ package "Orchestration" { - RunCheckinPhase() : void - RunRatingPhase() : void } - - class JsonLoader { - + {static} LoadLocations(filepath : const std::filesystem::path&) : std::vector - + {static} LoadBeerStyles(filepath : const std::filesystem::path&) : std::vector - + {static} LoadPersonas(filepath : const std::filesystem::path&) : std::vector - + {static} LoadNamesByCountry(filepath : const std::filesystem::path&) : NamesByCountry - } } + +' ========================================== +' INFRASTRUCTURE: PRELOADING +' ========================================== +package "Infrastructure: Preloading" { + + class JsonLoader { + + LoadLocations(filepath : const std::filesystem::path&) : std::vector + + LoadBeerStyles(filepath : const std::filesystem::path&) : std::vector + + LoadPersonas(filepath : const std::filesystem::path&) : std::vector + + LoadNamesByCountry(filepath : const std::filesystem::path&) : NamesByCountry + } + +} + + +' ========================================== +' INFRASTRUCTURE: ENRICHMENT +' ========================================== package "Infrastructure: Enrichment" { - interface IEnrichmentService <> { - + GetLocationContext(loc : const Location&,\n strategy : const IContextStrategy&) : LocationContext + interface EnrichmentService <> { + + GetLocationContext(loc : const Location&,\n strategy : const ContextStrategy&) : LocationContext } class WikipediaService { - client_ : std::unique_ptr - extract_cache_ : std::unordered_map - + GetLocationContext(loc : const Location&,\n strategy : const IContextStrategy&) : LocationContext + + GetLocationContext(loc : const Location&,\n strategy : const ContextStrategy&) : LocationContext - FetchExtract(query : std::string_view) : std::string } - note right of WikipediaService - extract_cache_ keyed by query string. - Beer pass gets near-100% cache hits - since locations were already fetched - during the brewery pass. - end note interface WebClient <> { + Get(url : const std::string&) : std::string @@ -338,6 +382,10 @@ package "Infrastructure: Enrichment" { } + +' ========================================== +' INFRASTRUCTURE: GENERATION +' ========================================== package "Infrastructure: Generation" { interface DataGenerator <> { @@ -347,12 +395,6 @@ package "Infrastructure: Generation" { + GenerateCheckin(user : const GeneratedUser&,\n brewery : const GeneratedBrewery&,\n timestamp : const std::string&) : CheckinResult + GenerateRating(user : const GeneratedUser&,\n beer : const GeneratedBeer&,\n checkin_id : sqlite3_int64) : RatingResult } - note right of DataGenerator - GenerateBeer receives BeerStyle - as a parameter. Style selection - and count decisions live in - IBeerSelectionStrategy, not here. - end note class MockGenerator { + GenerateBrewery(...) : BreweryResult @@ -366,7 +408,7 @@ package "Infrastructure: Generation" { class LlamaGenerator { - model_ : ModelHandle - context_ : ContextHandle - - prompt_formatter_ : std::unique_ptr + - prompt_formatter_ : std::unique_ptr - rng_ : std::mt19937 + GenerateBrewery(...) : BreweryResult + GenerateBeer(...) : BeerResult @@ -377,15 +419,8 @@ package "Infrastructure: Generation" { - Infer(system_prompt, user_prompt,\n max_tokens, grammar) : std::string - ValidateModelArchitecture() : void } - note right of LlamaGenerator - Constructed from GeneratorOptions. - SamplingOptions fields are applied - during Load(). LlamaConfig removed — - GeneratorOptions is the sole - configuration surface. - end note - interface IPromptFormatter <> { + interface PromptFormatter <> { + Format(system_prompt : std::string_view,\n user_prompt : std::string_view) : std::string + ExpectedArchitecture() : std::string_view } @@ -397,6 +432,10 @@ package "Infrastructure: Generation" { } + +' ========================================== +' INFRASTRUCTURE: PIPELINE CHANNEL +' ========================================== package "Infrastructure: Pipeline Channel" { class "BoundedChannel" as BoundedChannel { @@ -410,19 +449,16 @@ package "Infrastructure: Pipeline Channel" { + Receive() : std::optional + Close() : void } - note right of BoundedChannel - Back-pressure via capacity_ bound. - Stalls fast producers (enrichment ×N) - when the LLM worker cannot keep up. - Close() is the termination signal — - workers drain remaining items then exit. - end note } + +' ========================================== +' INFRASTRUCTURE: EXPORT +' ========================================== package "Infrastructure: Export" { - interface IExportService <> { + interface ExportService <> { + Initialize() : void + ProcessBrewery(brewery : const GeneratedBrewery&) : sqlite3_int64 + ProcessBeer(beer : const GeneratedBeer&) : sqlite3_int64 @@ -433,7 +469,7 @@ package "Infrastructure: Export" { } class SqliteExportService { - - date_time_provider_ : std::unique_ptr + - date_time_provider_ : std::unique_ptr - db_handle_ : SqliteDatabaseHandle - insert_location_stmt_ : SqliteStatementHandle - insert_brewery_stmt_ : SqliteStatementHandle @@ -456,15 +492,8 @@ package "Infrastructure: Export" { - RollbackAndCloseNoThrow() : void - FinalizeStatements() : void } - note right of SqliteExportService - Single writer — no lock contention. - location_cache_ deduplicates city rows. - brewery_cache_ resolves beer FK without - re-querying. Single long-running - transaction committed in Finalize(). - end note - interface IDateTimeProvider <> { + interface DateTimeProvider <> { + GetUtcTimestamp() : std::string } @@ -475,53 +504,65 @@ package "Infrastructure: Export" { } +' ========================================== +' GLOBAL RELATIONSHIPS +' ========================================== -' Orchestration -BiergartenPipelineOrchestrator *-- IEnrichmentService -BiergartenPipelineOrchestrator *-- DataGenerator -BiergartenPipelineOrchestrator *-- IExportService -BiergartenPipelineOrchestrator *-- ICheckinDistributionStrategy -BiergartenPipelineOrchestrator *-- ISamplingStrategy -BiergartenPipelineOrchestrator *-- IBeerSelectionStrategy -BiergartenPipelineOrchestrator *-- ApplicationOptions -BiergartenPipelineOrchestrator ..> JsonLoader +' --- Orchestration Aggregations (Services & Strategies) --- +BiergartenPipelineOrchestrator *-- DataPreloader +BiergartenPipelineOrchestrator *-- EnrichmentService +BiergartenPipelineOrchestrator *-- DataGenerator +BiergartenPipelineOrchestrator *-- ExportService +BiergartenPipelineOrchestrator *-- CheckinDistributionStrategy +BiergartenPipelineOrchestrator *-- SamplingStrategy +BiergartenPipelineOrchestrator *-- BeerSelectionStrategy +BiergartenPipelineOrchestrator *-- ApplicationOptions +BiergartenPipelineOrchestrator *-- Logger -' Policy implementations -IContextStrategy <|.. BreweryContextStrategy -IContextStrategy <|.. BeerContextStrategy -ISamplingStrategy <|.. UniformSamplingStrategy -IBeerSelectionStrategy <|.. RandomBeerSelectionStrategy -ICheckinDistributionStrategy <|.. JCurveCheckinStrategy +' --- Orchestration Aggregations (Data Pools) --- +BiergartenPipelineOrchestrator *-- "0..*" GeneratedUser : user_pool_ +BiergartenPipelineOrchestrator *-- "0..*" GeneratedBrewery : brewery_pool_ +BiergartenPipelineOrchestrator *-- "0..*" GeneratedBeer : beer_pool_ +BiergartenPipelineOrchestrator *-- "0..*" GeneratedCheckin : checkin_pool_ -' Enrichment -IEnrichmentService <|.. WikipediaService -WikipediaService *-- WebClient -WikipediaService ..> IContextStrategy -WebClient <|.. CURLWebClient +' --- Interfaces & Implementations --- +DataPreloader <|.. JsonLoader +Logger <|.. PipelineLogger +ContextStrategy <|.. BreweryContextStrategy +ContextStrategy <|.. BeerContextStrategy +SamplingStrategy <|.. UniformSamplingStrategy +BeerSelectionStrategy <|.. RandomBeerSelectionStrategy +CheckinDistributionStrategy <|.. JCurveCheckinStrategy +EnrichmentService <|.. WikipediaService +WebClient <|.. CURLWebClient +DataGenerator <|.. MockGenerator +DataGenerator <|.. LlamaGenerator +PromptFormatter <|.. Gemma4JinjaPromptFormatter +ExportService <|.. SqliteExportService +DateTimeProvider <|.. SystemDateTimeProvider -' Generation -DataGenerator <|.. MockGenerator -DataGenerator <|.. LlamaGenerator -LlamaGenerator *-- IPromptFormatter -LlamaGenerator ..> GeneratorOptions -IPromptFormatter <|.. Gemma4JinjaPromptFormatter +' --- Service Compositions & Dependencies --- +WikipediaService *-- WebClient +WikipediaService ..> ContextStrategy +LlamaGenerator *-- PromptFormatter +LlamaGenerator ..> GeneratorOptions +SqliteExportService *-- DateTimeProvider -' Export -IExportService <|.. SqliteExportService -SqliteExportService *-- IDateTimeProvider -IDateTimeProvider <|.. SystemDateTimeProvider +' --- Cross-Component Aggregations (Held References) --- +PipelineLogger o-- BoundedChannel : logs to +LogWorker o-- BoundedChannel : drains from -' Domain containment -EnrichedCity *-- Location -EnrichedCity *-- LocationContext -GeneratedBrewery *-- Location -GeneratedBrewery *-- BreweryResult -GeneratedBeer *-- Location -GeneratedBeer *-- BeerStyle -GeneratedBeer *-- BeerResult -GeneratedUser *-- Location -GeneratedUser *-- UserResult -GeneratedCheckin *-- CheckinResult -GeneratedRating *-- RatingResult +' --- Domain Containment --- +EnrichedCity *-- Location +EnrichedCity *-- LocationContext +GeneratedBrewery *-- Location +GeneratedBrewery *-- BreweryResult +GeneratedBeer *-- Location +GeneratedBeer *-- BeerStyle +GeneratedBeer *-- BeerResult +GeneratedUser *-- Location +GeneratedUser *-- UserResult +GeneratedCheckin *-- CheckinResult +GeneratedRating *-- RatingResult @enduml