@startuml BiergartenPipeline title Biergarten Pipeline - Class and Composition Diagram left to right direction skinparam shadowing false skinparam classAttributeIconSize 0 skinparam packageStyle rectangle package "Composition root" { class Main <> { +main(argc: int, argv: char**): int } class CurlGlobalState { +CurlGlobalState() +~CurlGlobalState() } note right of Main Binds with Boost.DI: - WebClient -> CURLWebClient - IEnrichmentService -> WikipediaService - DataGenerator -> MockGenerator or LlamaGenerator - LlamaGenerator receives ApplicationOptions and model_path directly end note } package "Core orchestration" { class ApplicationOptions <> { +model_path: std::string +use_mocked: bool +temperature: float +top_p: float +top_k: uint32_t +n_ctx: uint32_t +seed: int } class BiergartenDataGenerator { -context_service_: std::shared_ptr -generator_: std::unique_ptr +BiergartenDataGenerator(context_service: std::shared_ptr, generator: std::unique_ptr) +Run(): bool -QueryCitiesWithCountries(): std::vector -GenerateBreweries(cities: std::vector): void -LogResults(): void } class EnrichedCity <> { +location: Location +region_context: std::string } } package "Shared models" { class BreweryLocation <> { +city_name: std::string_view +country_name: std::string_view } class Location class BreweryResult <> { +name: std::string +description: std::string } class UserResult <> { +username: std::string +bio: std::string } } package "Generation" { interface DataGenerator { +GenerateBrewery(location: BreweryLocation, region_context: std::string): BreweryResult +GenerateUser(locale: std::string): UserResult } class MockGenerator { +GenerateBrewery(location: BreweryLocation, region_context: std::string): BreweryResult +GenerateUser(locale: std::string): UserResult } class LlamaGenerator { +LlamaGenerator(options: ApplicationOptions, model_path: std::string) +GenerateBrewery(location: BreweryLocation, region_context: std::string): BreweryResult +GenerateUser(locale: std::string): UserResult } } package "HTTP" { interface WebClient { +DownloadToFile(url: std::string, file_path: std::string): void +Get(url: std::string): std::string +UrlEncode(value: std::string): std::string } class CURLWebClient { +CURLWebClient() +~CURLWebClient() +DownloadToFile(url: std::string, file_path: std::string): void +Get(url: std::string): std::string +UrlEncode(value: std::string): std::string } } package "Wikipedia" { interface IEnrichmentService { +GetLocationContext(loc: Location): std::string } class WikipediaService { +WikipediaService(client: std::shared_ptr) +GetLocationContext(loc: Location): std::string } class JsonLoader { {static} +LoadLocations(filepath: std::string): std::vector } } Main --> CurlGlobalState Main --> ApplicationOptions Main --> BiergartenDataGenerator Main ..> IEnrichmentService : DI binding Main ..> DataGenerator : DI factory Main ..> CURLWebClient : DI binding BiergartenDataGenerator *-- EnrichedCity BiergartenDataGenerator ..> JsonLoader : LoadLocations() BiergartenDataGenerator --> IEnrichmentService : context lookup BiergartenDataGenerator --> DataGenerator : brewery generation BiergartenDataGenerator ..> Location BiergartenDataGenerator ..> BreweryResult DataGenerator <|.. MockGenerator DataGenerator <|.. LlamaGenerator WebClient <|.. CURLWebClient IEnrichmentService <|.. WikipediaService WikipediaService --> WebClient : shared_ptr note right of BiergartenDataGenerator Current behavior: samples up to four locations per run. Enrichment runs once per sampled city. If a lookup throws, that city is skipped. Empty context is retained and still passed to the generator. end note @enduml