mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-05-31 17:53:59 +00:00
Compare commits
3 Commits
feat/pipel
...
532cb234fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
532cb234fa | ||
|
|
aa46cf2b4b | ||
|
|
fd6ba35f68 |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
277
pipeline/diagrams/future-possible-activity.puml
Normal file
277
pipeline/diagrams/future-possible-activity.puml
Normal file
@@ -0,0 +1,277 @@
|
||||
@startuml
|
||||
skinparam style strictuml
|
||||
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 SequenceLifeLineBorderColor #547461
|
||||
skinparam SequenceParticipantBorderColor #547461
|
||||
skinparam SequenceParticipantBackgroundColor #EAF0E8
|
||||
skinparam SequenceBoxBorderColor #547461
|
||||
skinparam NoteBackgroundColor #EAF0E8
|
||||
skinparam NoteBorderColor #547461
|
||||
skinparam SequenceDividerBackgroundColor #EAF0E8
|
||||
skinparam SequenceDividerBorderColor #547461
|
||||
|
||||
title The Biergarten Data Pipeline — Sequence Diagram v4 (Unified Orchestrator)
|
||||
|
||||
participant "main.cc" as main #F2F6F0
|
||||
participant "Orchestrator" as orch #EAF0E8
|
||||
participant "Thread U1\nUserProducer" as u1 #DCE8D8
|
||||
participant "Thread U2\nUserExportConsumer" as u2 #E0EAE0
|
||||
participant "Thread B1\nEnrichmentProducer" as b1 #DCE8D8
|
||||
participant "Thread B2\nBreweryGenerationConsumer" as b2 #E5EDE1
|
||||
participant "Thread B3\nBreweryExportConsumer" as b3 #E0EAE0
|
||||
participant "Thread R1\nBeerGenerationProducer" as r1 #DCE8D8
|
||||
participant "Thread R2\nBeerExportConsumer" as r2 #E0EAE0
|
||||
participant "Thread C1\nCheckinGenerationProducer" as c1 #DCE8D8
|
||||
participant "Thread C2\nCheckinExportConsumer" as c2 #E0EAE0
|
||||
participant "Thread G1\nRatingGenerationProducer" as g1 #DCE8D8
|
||||
participant "Thread G2\nRatingExportConsumer" as g2 #E0EAE0
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' STARTUP
|
||||
' ─────────────────────────────────────────────
|
||||
main -> main : ParseArguments(argc, argv)
|
||||
alt Invalid args
|
||||
main -> main : spdlog::error; stop
|
||||
end
|
||||
|
||||
main -> main : Init CurlGlobalState & LlamaBackendState
|
||||
main -> main : Build DI injector
|
||||
note right of main
|
||||
All dependencies bound with unique_ptr.
|
||||
LlamaConfig or RestConfig injected
|
||||
instead of ApplicationOptions.
|
||||
end note
|
||||
|
||||
main -> orch : exporter->Initialize()
|
||||
note right of orch
|
||||
Opens SQLite connection.
|
||||
Creates schema for all five fixture types
|
||||
in one DDL pass. Begins IMMEDIATE TRANSACTION.
|
||||
end note
|
||||
|
||||
main -> orch : JsonLoader::LoadLocations("locations.json")
|
||||
main -> orch : ISamplingStrategy::Sample(all_locations)
|
||||
main -> orch : BiergartenPipelineOrchestrator::Run()
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' PHASE 1 — USERS
|
||||
' ─────────────────────────────────────────────
|
||||
== Phase 1 — Users (no FK dependencies) ==
|
||||
|
||||
orch -> u1 : spawn
|
||||
orch -> u2 : spawn
|
||||
|
||||
loop For each Location
|
||||
u1 -> u1 : generator->GenerateUser(location)
|
||||
u1 -> u2 : user_channel_.Send(GeneratedUser)
|
||||
end
|
||||
u1 -> u2 : user_channel_.Close()
|
||||
|
||||
loop user_channel_.Receive()
|
||||
u2 -> u2 : exporter->ProcessUser(user) : sqlite3_int64
|
||||
note right of u2
|
||||
Returns committed row ID.
|
||||
Stored on GeneratedUser.user_id.
|
||||
end note
|
||||
u2 -> orch : Append to user_pool_
|
||||
end
|
||||
|
||||
orch -> orch : join(U1, U2)
|
||||
note right of orch
|
||||
── BARRIER 1 ──
|
||||
user_pool_ is now fully committed.
|
||||
Phase 2 begins.
|
||||
end note
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' PHASE 2 — BREWERIES
|
||||
' ─────────────────────────────────────────────
|
||||
== Phase 2 — Breweries (depends on locations only) ==
|
||||
|
||||
orch -> b1 : spawn
|
||||
orch -> b2 : spawn
|
||||
orch -> b3 : spawn
|
||||
|
||||
loop For each Location
|
||||
b1 -> b1 : BreweryContextStrategy::QueriesFor(location)
|
||||
b1 -> b1 : WikipediaService::GetLocationContext(location,\nbrewery_context_strategy_)
|
||||
alt failure
|
||||
b1 -> b1 : LocationContext{ Absent }
|
||||
else truncated
|
||||
b1 -> b1 : LocationContext{ Partial }
|
||||
else success
|
||||
b1 -> b1 : LocationContext{ Full }
|
||||
end
|
||||
b1 -> b2 : enrichment_channel_.Send(EnrichedCity)
|
||||
note right of b1
|
||||
Blocks if channel full.
|
||||
Back-pressure against GPU consumer.
|
||||
end note
|
||||
end
|
||||
b1 -> b2 : enrichment_channel_.Close()
|
||||
|
||||
loop enrichment_channel_.Receive()
|
||||
alt context.completeness == Absent
|
||||
b2 -> b2 : spdlog::warn — proceeding with minimal prompt
|
||||
end
|
||||
b2 -> b2 : generator->GenerateBrewery(location, context)
|
||||
b2 -> b3 : brewery_channel_.Send(GeneratedBrewery)
|
||||
end
|
||||
b2 -> b3 : brewery_channel_.Close()
|
||||
|
||||
loop brewery_channel_.Receive()
|
||||
b3 -> b3 : exporter->ProcessBrewery(brewery) : sqlite3_int64
|
||||
note right of b3
|
||||
Row ID stored on GeneratedBrewery.brewery_id.
|
||||
No brewery_cache_ needed — orchestrator
|
||||
threads the ID forward directly.
|
||||
end note
|
||||
b3 -> orch : Append to brewery_pool_
|
||||
end
|
||||
|
||||
orch -> orch : join(B1, B2, B3)
|
||||
note right of orch
|
||||
── BARRIER 2 ──
|
||||
brewery_pool_ is now fully committed
|
||||
with live brewery_id values.
|
||||
Phase 3 begins.
|
||||
end note
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' PHASE 3 — BEERS
|
||||
' ─────────────────────────────────────────────
|
||||
== Phase 3 — Beers (depends on brewery_pool_) ==
|
||||
|
||||
orch -> r1 : spawn
|
||||
orch -> r2 : spawn
|
||||
|
||||
loop For each GeneratedBrewery in brewery_pool_
|
||||
r1 -> r1 : BeerContextStrategy::QueriesFor(location)
|
||||
r1 -> r1 : WikipediaService::GetLocationContext(location,\nbeer_context_strategy_)
|
||||
r1 -> r1 : generator->GenerateBeer(brewery.brewery_id,\nlocation, context)
|
||||
r1 -> r2 : beer_channel_.Send(GeneratedBeer)
|
||||
end
|
||||
r1 -> r2 : beer_channel_.Close()
|
||||
|
||||
loop beer_channel_.Receive()
|
||||
r2 -> r2 : exporter->ProcessBeer(beer) : sqlite3_int64
|
||||
note right of r2
|
||||
Row ID stored on GeneratedBeer.beer_id.
|
||||
end note
|
||||
r2 -> orch : Append to beer_pool_
|
||||
end
|
||||
|
||||
orch -> orch : join(R1, R2)
|
||||
note right of orch
|
||||
── BARRIER 3 ──
|
||||
beer_pool_ is fully committed.
|
||||
All three upstream pools ready.
|
||||
end note
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' CHECKIN WEIGHT ASSIGNMENT
|
||||
' ─────────────────────────────────────────────
|
||||
== Checkin Weight Assignment ==
|
||||
|
||||
orch -> orch : ICheckinDistributionStrategy::\nAssignActivityWeights(user_pool_)
|
||||
note right of orch
|
||||
J-curve weights written onto
|
||||
GeneratedUser.user.activity_weight.
|
||||
Small cohort gets high weight;
|
||||
long tail gets low weight.
|
||||
Requires the full pool — this is why
|
||||
users were committed first.
|
||||
end note
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' PHASE 4 — CHECKINS
|
||||
' ─────────────────────────────────────────────
|
||||
== Phase 4 — Check-ins (depends on user_pool_ + brewery_pool_) ==
|
||||
|
||||
orch -> c1 : spawn
|
||||
orch -> c2 : spawn
|
||||
|
||||
loop For each GeneratedUser in user_pool_
|
||||
c1 -> c1 : strategy->CheckinsForUser(user,\nbrewery_pool_.size())
|
||||
loop For each checkin index
|
||||
c1 -> c1 : strategy->TimestampFor(user, index)
|
||||
note right of c1
|
||||
Bursty weekend / evening
|
||||
distribution applied here.
|
||||
end note
|
||||
c1 -> c1 : Select brewery from brewery_pool_\n(weighted random by activity_weight)
|
||||
c1 -> c1 : generator->GenerateCheckin(user, brewery, timestamp)
|
||||
c1 -> c2 : checkin_channel_.Send(GeneratedCheckin)
|
||||
end
|
||||
end
|
||||
c1 -> c2 : checkin_channel_.Close()
|
||||
|
||||
loop checkin_channel_.Receive()
|
||||
c2 -> c2 : exporter->ProcessCheckin(checkin) : sqlite3_int64
|
||||
note right of c2
|
||||
Row ID stored on GeneratedCheckin.checkin_id.
|
||||
end note
|
||||
c2 -> orch : Append to checkin_pool_
|
||||
end
|
||||
|
||||
orch -> orch : join(C1, C2)
|
||||
note right of orch
|
||||
── BARRIER 4 ──
|
||||
checkin_pool_ is fully committed.
|
||||
All FK dependencies for ratings satisfied.
|
||||
Phase 5 begins.
|
||||
end note
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' PHASE 5 — RATINGS
|
||||
' ─────────────────────────────────────────────
|
||||
== Phase 5 — Ratings (depends on user_pool_ + beer_pool_ + checkin_pool_) ==
|
||||
|
||||
orch -> g1 : spawn
|
||||
orch -> g2 : spawn
|
||||
|
||||
loop For each GeneratedCheckin in checkin_pool_
|
||||
g1 -> g1 : Resolve GeneratedUser from user_pool_\n(match user_id)
|
||||
g1 -> g1 : Resolve GeneratedBeer from beer_pool_\n(match brewery_id, select one)
|
||||
alt Beer found for this brewery
|
||||
g1 -> g1 : generator->GenerateRating(user, beer,\ncheckin.checkin_id)
|
||||
note right of g1
|
||||
Strong positive skew applied
|
||||
inside GenerateRating.
|
||||
end note
|
||||
g1 -> g2 : rating_channel_.Send(GeneratedRating)
|
||||
else No beer found
|
||||
g1 -> g1 : spdlog::warn — no beer for brewery,\nskipping rating
|
||||
end
|
||||
end
|
||||
g1 -> g2 : rating_channel_.Close()
|
||||
|
||||
loop rating_channel_.Receive()
|
||||
g2 -> g2 : exporter->ProcessRating(rating)
|
||||
end
|
||||
|
||||
orch -> orch : join(G1, G2)
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' TEARDOWN
|
||||
' ─────────────────────────────────────────────
|
||||
== Teardown ==
|
||||
|
||||
orch -> main : return
|
||||
main -> main : exporter->Finalize()
|
||||
note right of main
|
||||
Single COMMIT covers all five fixture types:
|
||||
users, breweries, beers, checkins, ratings.
|
||||
All-or-nothing consistency per run.
|
||||
end note
|
||||
main -> main : spdlog::info "Pipeline complete in X ms"
|
||||
main -> main : return 0
|
||||
|
||||
@enduml
|
||||
501
pipeline/diagrams/future-possible-architecture.puml
Normal file
501
pipeline/diagrams/future-possible-architecture.puml
Normal file
@@ -0,0 +1,501 @@
|
||||
@startuml
|
||||
skinparam style strictuml
|
||||
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 class {
|
||||
BackgroundColor #FAFCF9
|
||||
HeaderBackgroundColor #EAF0E8
|
||||
BorderColor #547461
|
||||
ArrowColor #628A5B
|
||||
FontColor #28342A
|
||||
}
|
||||
|
||||
skinparam note {
|
||||
BackgroundColor #EAF0E8
|
||||
BorderColor #547461
|
||||
FontColor #28342A
|
||||
}
|
||||
|
||||
skinparam package {
|
||||
BackgroundColor #F2F6F0
|
||||
BorderColor #547461
|
||||
FontColor #28342A
|
||||
}
|
||||
|
||||
title The Biergarten Data Pipeline — Architecture (Unified Orchestrator)
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' DOMAIN: VALUE OBJECTS
|
||||
' ─────────────────────────────────────────────
|
||||
package "Domain: Value Objects & Contracts" {
|
||||
|
||||
class Location {
|
||||
+ city : std::string
|
||||
+ state_province : std::string
|
||||
+ iso3166_2 : std::string
|
||||
+ country : std::string
|
||||
+ iso3166_1 : std::string
|
||||
+ local_languages : std::vector<std::string>
|
||||
+ latitude : double
|
||||
+ longitude : double
|
||||
}
|
||||
|
||||
class LocationContext {
|
||||
+ text : std::string
|
||||
+ completeness : Completeness
|
||||
+ char_count : size_t
|
||||
--
|
||||
<<enum>> Completeness
|
||||
Full
|
||||
Partial
|
||||
Absent
|
||||
}
|
||||
|
||||
class EnrichedCity {
|
||||
+ location : Location
|
||||
+ context : LocationContext
|
||||
}
|
||||
|
||||
class BreweryResult {
|
||||
+ name_en : std::string
|
||||
+ description_en : std::string
|
||||
+ name_local : std::string
|
||||
+ description_local : std::string
|
||||
}
|
||||
|
||||
class BeerResult {
|
||||
+ name_en : std::string
|
||||
+ description_en : std::string
|
||||
+ name_local : std::string
|
||||
+ description_local : std::string
|
||||
+ style : std::string
|
||||
+ abv : float
|
||||
+ ibu : int
|
||||
}
|
||||
|
||||
class UserResult {
|
||||
+ username : std::string
|
||||
+ 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
|
||||
+ note : std::string
|
||||
}
|
||||
|
||||
class RatingResult {
|
||||
+ score : float
|
||||
+ note : std::string
|
||||
}
|
||||
|
||||
class GeneratedBrewery {
|
||||
+ brewery_id : sqlite3_int64
|
||||
+ location : Location
|
||||
+ brewery : BreweryResult
|
||||
+ context_completeness : LocationContext::Completeness
|
||||
+ generated_at : std::string
|
||||
}
|
||||
|
||||
class GeneratedBeer {
|
||||
+ beer_id : sqlite3_int64
|
||||
+ brewery_id : sqlite3_int64
|
||||
+ location : Location
|
||||
+ beer : BeerResult
|
||||
+ generated_at : std::string
|
||||
}
|
||||
|
||||
class GeneratedUser {
|
||||
+ user_id : sqlite3_int64
|
||||
+ location : Location
|
||||
+ user : UserResult
|
||||
+ generated_at : std::string
|
||||
}
|
||||
|
||||
class GeneratedCheckin {
|
||||
+ checkin_id : sqlite3_int64
|
||||
+ user_id : sqlite3_int64
|
||||
+ brewery_id : sqlite3_int64
|
||||
+ checkin : CheckinResult
|
||||
+ generated_at : std::string
|
||||
}
|
||||
|
||||
class GeneratedRating {
|
||||
+ user_id : sqlite3_int64
|
||||
+ beer_id : sqlite3_int64
|
||||
+ checkin_id : sqlite3_int64
|
||||
+ rating : RatingResult
|
||||
+ generated_at : std::string
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' DOMAIN POLICY
|
||||
' ─────────────────────────────────────────────
|
||||
package "Domain Policy" {
|
||||
|
||||
interface IContextStrategy <<interface>> {
|
||||
+ QueriesFor(loc : const Location&) : std::vector<std::string>
|
||||
+ MaxContextChars() : size_t
|
||||
}
|
||||
|
||||
class BreweryContextStrategy {
|
||||
+ QueriesFor(loc : const Location&) : std::vector<std::string>
|
||||
+ MaxContextChars() : size_t
|
||||
}
|
||||
|
||||
class BeerContextStrategy {
|
||||
+ QueriesFor(loc : const Location&) : std::vector<std::string>
|
||||
+ MaxContextChars() : size_t
|
||||
}
|
||||
|
||||
interface ISamplingStrategy <<interface>> {
|
||||
+ Sample(locations : const std::vector<Location>&) : std::vector<Location>
|
||||
}
|
||||
|
||||
class UniformSamplingStrategy {
|
||||
- sample_size_ : size_t
|
||||
+ Sample(locations : const std::vector<Location>&) : std::vector<Location>
|
||||
}
|
||||
|
||||
interface ICheckinDistributionStrategy <<interface>> {
|
||||
+ AssignActivityWeights(users : std::vector<GeneratedUser>&) : void
|
||||
+ CheckinsForUser(user : const GeneratedUser&, brewery_count : size_t) : size_t
|
||||
+ TimestampFor(user : const GeneratedUser&, index : size_t) : std::string
|
||||
}
|
||||
note right of ICheckinDistributionStrategy
|
||||
Injected into the orchestrator.
|
||||
Owns all statistical policy:
|
||||
J-curve weight assignment,
|
||||
bursty weekend timestamps,
|
||||
per-user checkin volume.
|
||||
No mediator required to hold this —
|
||||
the orchestrator calls it directly
|
||||
before the checkin phase opens.
|
||||
end note
|
||||
|
||||
class JCurveCheckinStrategy {
|
||||
- rng_ : std::mt19937
|
||||
+ AssignActivityWeights(users : std::vector<GeneratedUser>&) : void
|
||||
+ CheckinsForUser(user : const GeneratedUser&, brewery_count : size_t) : size_t
|
||||
+ TimestampFor(user : const GeneratedUser&, index : size_t) : std::string
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' INFRASTRUCTURE: ENRICHMENT
|
||||
' ─────────────────────────────────────────────
|
||||
package "Infrastructure: Enrichment" {
|
||||
|
||||
interface IEnrichmentService <<interface>> {
|
||||
+ GetLocationContext(loc : const Location&, strategy : const IContextStrategy&) : LocationContext
|
||||
}
|
||||
|
||||
class WikipediaService {
|
||||
- client_ : std::unique_ptr<WebClient>
|
||||
- extract_cache_ : std::unordered_map<std::string, std::string>
|
||||
+ GetLocationContext(loc : const Location&, strategy : const IContextStrategy&) : LocationContext
|
||||
- FetchExtract(query : std::string_view) : std::string
|
||||
}
|
||||
|
||||
interface WebClient <<interface>> {
|
||||
+ Get(url : const std::string&) : std::string
|
||||
+ UrlEncode(value : const std::string&) : std::string
|
||||
}
|
||||
|
||||
class CURLWebClient {
|
||||
+ Get(url : const std::string&) : std::string
|
||||
+ UrlEncode(value : const std::string&) : std::string
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' INFRASTRUCTURE: GENERATION
|
||||
' ─────────────────────────────────────────────
|
||||
package "Infrastructure: Generation" {
|
||||
|
||||
interface DataGenerator <<interface>> {
|
||||
+ GenerateBrewery(location : const Location&, context : const LocationContext&) : BreweryResult
|
||||
+ GenerateBeer(brewery_id : sqlite3_int64, location : const Location&, context : const LocationContext&) : BeerResult
|
||||
+ GenerateUser(location : const Location&) : UserResult
|
||||
+ GenerateCheckin(user : const GeneratedUser&, brewery : const GeneratedBrewery&, timestamp : const std::string&) : CheckinResult
|
||||
+ GenerateRating(user : const GeneratedUser&, beer : const GeneratedBeer&, checkin_id : sqlite3_int64) : RatingResult
|
||||
}
|
||||
|
||||
class MockGenerator {
|
||||
+ GenerateBrewery(...) : BreweryResult
|
||||
+ GenerateBeer(...) : BeerResult
|
||||
+ GenerateUser(...) : UserResult
|
||||
+ GenerateCheckin(...) : CheckinResult
|
||||
+ GenerateRating(...) : RatingResult
|
||||
- DeterministicHash(location : const Location&) : size_t
|
||||
}
|
||||
|
||||
class LlamaGenerator {
|
||||
- model_ : ModelHandle
|
||||
- context_ : ContextHandle
|
||||
- prompt_formatter_ : std::unique_ptr<IPromptFormatter>
|
||||
- config_ : LlamaConfig
|
||||
- rng_ : std::mt19937
|
||||
+ GenerateBrewery(...) : BreweryResult
|
||||
+ GenerateBeer(...) : BeerResult
|
||||
+ GenerateUser(...) : UserResult
|
||||
+ GenerateCheckin(...) : CheckinResult
|
||||
+ GenerateRating(...) : RatingResult
|
||||
- Load(config : const LlamaConfig&) : void
|
||||
- Infer(system_prompt, user_prompt, max_tokens, grammar) : std::string
|
||||
- ValidateModelArchitecture() : void
|
||||
}
|
||||
|
||||
class RestGenerator {
|
||||
- config_ : RestConfig
|
||||
+ GenerateBrewery(...) : BreweryResult
|
||||
+ GenerateBeer(...) : BeerResult
|
||||
+ GenerateUser(...) : UserResult
|
||||
+ GenerateCheckin(...) : CheckinResult
|
||||
+ GenerateRating(...) : RatingResult
|
||||
}
|
||||
note right of RestGenerator
|
||||
Future REST-backed implementation.
|
||||
Slots in at the DI root with zero
|
||||
changes to orchestration logic.
|
||||
end note
|
||||
|
||||
interface IPromptFormatter <<interface>> {
|
||||
+ Format(system_prompt : std::string_view, user_prompt : std::string_view) : std::string
|
||||
+ ExpectedArchitecture() : std::string_view
|
||||
}
|
||||
|
||||
class Gemma4JinjaPromptFormatter {
|
||||
+ Format(...) : std::string
|
||||
+ ExpectedArchitecture() : std::string_view
|
||||
}
|
||||
|
||||
class LlamaConfig {
|
||||
+ model_path : std::string
|
||||
+ temperature : float
|
||||
+ top_p : float
|
||||
+ top_k : uint32_t
|
||||
+ n_ctx : uint32_t
|
||||
+ seed : int
|
||||
}
|
||||
|
||||
class RestConfig {
|
||||
+ endpoint : std::string
|
||||
+ api_key : std::string
|
||||
+ timeout : std::chrono::milliseconds
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' INFRASTRUCTURE: PIPELINE CHANNEL
|
||||
' ─────────────────────────────────────────────
|
||||
package "Infrastructure: Pipeline Channel" {
|
||||
|
||||
class "BoundedChannel<T>" as BoundedChannel {
|
||||
- queue_ : std::queue<T>
|
||||
- mutex_ : std::mutex
|
||||
- not_full_ : std::condition_variable
|
||||
- not_empty_ : std::condition_variable
|
||||
- capacity_ : size_t
|
||||
- closed_ : bool
|
||||
+ Send(item : T) : void
|
||||
+ Receive() : std::optional<T>
|
||||
+ Close() : void
|
||||
}
|
||||
note right of BoundedChannel
|
||||
Used within each phase to
|
||||
decouple production from export.
|
||||
Phase boundaries are explicit
|
||||
sequential barriers in the
|
||||
orchestrator's Run() method —
|
||||
not channel-mediated.
|
||||
end note
|
||||
|
||||
}
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' INFRASTRUCTURE: EXPORT
|
||||
' ─────────────────────────────────────────────
|
||||
package "Infrastructure: Export" {
|
||||
|
||||
interface IExportService <<interface>> {
|
||||
+ Initialize() : void
|
||||
+ ProcessBrewery(brewery : const GeneratedBrewery&) : sqlite3_int64
|
||||
+ ProcessBeer(beer : const GeneratedBeer&) : sqlite3_int64
|
||||
+ ProcessUser(user : const GeneratedUser&) : sqlite3_int64
|
||||
+ ProcessCheckin(checkin : const GeneratedCheckin&) : sqlite3_int64
|
||||
+ ProcessRating(rating : const GeneratedRating&) : void
|
||||
+ Finalize() : void
|
||||
}
|
||||
note right of IExportService
|
||||
Process* methods return
|
||||
sqlite3_int64 row IDs.
|
||||
Orchestrator uses these to
|
||||
populate FK fields on all
|
||||
downstream fixture types.
|
||||
end note
|
||||
|
||||
class SqliteExportService {
|
||||
- date_time_provider_ : std::unique_ptr<IDateTimeProvider>
|
||||
- db_handle_ : SqliteDatabaseHandle
|
||||
- insert_location_stmt_ : SqliteStatementHandle
|
||||
- insert_brewery_stmt_ : SqliteStatementHandle
|
||||
- insert_beer_stmt_ : SqliteStatementHandle
|
||||
- insert_user_stmt_ : SqliteStatementHandle
|
||||
- insert_checkin_stmt_ : SqliteStatementHandle
|
||||
- insert_rating_stmt_ : SqliteStatementHandle
|
||||
- transaction_open_ : bool
|
||||
- location_cache_ : std::unordered_map<std::string, sqlite3_int64>
|
||||
+ Initialize() : void
|
||||
+ ProcessBrewery(brewery : const GeneratedBrewery&) : sqlite3_int64
|
||||
+ ProcessBeer(beer : const GeneratedBeer&) : sqlite3_int64
|
||||
+ ProcessUser(user : const GeneratedUser&) : sqlite3_int64
|
||||
+ ProcessCheckin(checkin : const GeneratedCheckin&) : sqlite3_int64
|
||||
+ ProcessRating(rating : const GeneratedRating&) : void
|
||||
+ Finalize() : void
|
||||
- InitializeSchema() : void
|
||||
- PrepareStatements() : void
|
||||
- RollbackAndCloseNoThrow() : void
|
||||
- FinalizeStatements() : void
|
||||
}
|
||||
note right of SqliteExportService
|
||||
brewery_cache_ removed — row IDs
|
||||
are now carried on GeneratedBrewery
|
||||
and GeneratedBeer value objects
|
||||
and threaded through by the
|
||||
orchestrator directly.
|
||||
end note
|
||||
|
||||
interface IDateTimeProvider <<interface>> {
|
||||
+ GetUtcTimestamp() : std::string
|
||||
}
|
||||
|
||||
class SystemDateTimeProvider {
|
||||
+ GetUtcTimestamp() : std::string
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' ORCHESTRATION
|
||||
' ─────────────────────────────────────────────
|
||||
package "Orchestration" {
|
||||
|
||||
class BiergartenPipelineOrchestrator {
|
||||
- enrichment_service_ : std::unique_ptr<IEnrichmentService>
|
||||
- generator_ : std::unique_ptr<DataGenerator>
|
||||
- exporter_ : std::unique_ptr<IExportService>
|
||||
- brewery_context_strategy_ : std::unique_ptr<IContextStrategy>
|
||||
- beer_context_strategy_ : std::unique_ptr<IContextStrategy>
|
||||
- sampling_strategy_ : std::unique_ptr<ISamplingStrategy>
|
||||
- checkin_strategy_ : std::unique_ptr<ICheckinDistributionStrategy>
|
||||
--
|
||||
- user_pool_ : std::vector<GeneratedUser>
|
||||
- brewery_pool_ : std::vector<GeneratedBrewery>
|
||||
- beer_pool_ : std::vector<GeneratedBeer>
|
||||
- checkin_pool_ : std::vector<GeneratedCheckin>
|
||||
--
|
||||
+ Run() : bool
|
||||
- RunUserPhase(locations : const std::vector<Location>&) : void
|
||||
- RunBreweryPhase(locations : const std::vector<Location>&) : void
|
||||
- RunBeerPhase() : void
|
||||
- RunCheckinPhase() : void
|
||||
- RunRatingPhase() : void
|
||||
}
|
||||
note right of BiergartenPipelineOrchestrator
|
||||
Single component owns all
|
||||
sequencing. Run() reads as a
|
||||
linear narrative:
|
||||
1. RunUserPhase
|
||||
2. RunBreweryPhase
|
||||
3. RunBeerPhase
|
||||
4. checkin_strategy_->AssignActivityWeights
|
||||
5. RunCheckinPhase
|
||||
6. RunRatingPhase
|
||||
The checkin gate is an explicit
|
||||
sequential barrier between steps
|
||||
3 and 5 — not a hidden internal
|
||||
trigger in a separate object.
|
||||
Pools are members: each phase
|
||||
appends to them and the next
|
||||
phase reads from them directly.
|
||||
No mediator. No shared_ptr.
|
||||
Ownership is unambiguous.
|
||||
end note
|
||||
|
||||
class JsonLoader {
|
||||
+ {static} LoadLocations(filepath : const std::filesystem::path&) : std::vector<Location>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
' ─────────────────────────────────────────────
|
||||
' RELATIONSHIPS
|
||||
' ─────────────────────────────────────────────
|
||||
|
||||
' Orchestration
|
||||
BiergartenPipelineOrchestrator *-- IEnrichmentService : owns
|
||||
BiergartenPipelineOrchestrator *-- DataGenerator : owns
|
||||
BiergartenPipelineOrchestrator *-- IExportService : owns
|
||||
BiergartenPipelineOrchestrator *-- ICheckinDistributionStrategy : owns
|
||||
BiergartenPipelineOrchestrator *-- ISamplingStrategy : owns
|
||||
BiergartenPipelineOrchestrator ..> JsonLoader : uses
|
||||
|
||||
' Policy implementations
|
||||
IContextStrategy <|.. BreweryContextStrategy : implements
|
||||
IContextStrategy <|.. BeerContextStrategy : implements
|
||||
ISamplingStrategy <|.. UniformSamplingStrategy : implements
|
||||
ICheckinDistributionStrategy <|.. JCurveCheckinStrategy : implements
|
||||
|
||||
' Enrichment
|
||||
IEnrichmentService <|.. WikipediaService : implements
|
||||
WikipediaService *-- WebClient : owns
|
||||
WikipediaService ..> IContextStrategy : uses (parameter)
|
||||
WebClient <|.. CURLWebClient : implements
|
||||
|
||||
' Generation
|
||||
DataGenerator <|.. MockGenerator : implements
|
||||
DataGenerator <|.. LlamaGenerator : implements
|
||||
DataGenerator <|.. RestGenerator : implements
|
||||
LlamaGenerator *-- IPromptFormatter : owns
|
||||
LlamaGenerator ..> LlamaConfig : constructed with
|
||||
RestGenerator ..> RestConfig : constructed with
|
||||
IPromptFormatter <|.. Gemma4JinjaPromptFormatter : implements
|
||||
|
||||
' Export
|
||||
IExportService <|.. SqliteExportService : implements
|
||||
SqliteExportService *-- IDateTimeProvider : owns
|
||||
IDateTimeProvider <|.. SystemDateTimeProvider : implements
|
||||
|
||||
' Data flow
|
||||
EnrichedCity *-- Location : contains
|
||||
EnrichedCity *-- LocationContext : contains
|
||||
GeneratedBrewery *-- Location : contains
|
||||
GeneratedBrewery *-- BreweryResult : contains
|
||||
GeneratedBeer *-- Location : contains
|
||||
GeneratedBeer *-- BeerResult : contains
|
||||
GeneratedUser *-- Location : contains
|
||||
GeneratedUser *-- UserResult : contains
|
||||
GeneratedCheckin *-- CheckinResult : contains
|
||||
GeneratedRating *-- RatingResult : contains
|
||||
|
||||
@enduml
|
||||
Reference in New Issue
Block a user