Compare commits
18 Commits
seeding-up
...
98f97b8e2c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98f97b8e2c | ||
|
|
2adc1eae38 | ||
|
|
9d2ce67a78 | ||
|
|
d1ad1eb397 | ||
|
|
9137f4bddb | ||
|
|
6883b05824 | ||
|
|
b631c0f5db | ||
|
|
9a9ffd59e6 | ||
|
|
35a5c0785c | ||
|
|
e590cf2543 | ||
|
|
3b32766a83 | ||
|
|
8d6b83673b | ||
|
|
22b3f2b3f9 | ||
|
|
ac701d661a | ||
|
|
3f978f4de4 | ||
|
|
ad97b0ea6c | ||
|
|
4de0ea6638 | ||
|
|
51b40a39c9 |
@@ -109,13 +109,13 @@ gender from the source data is preserved through to the sampled `Name`
|
|||||||
persona/bio generation later.
|
persona/bio generation later.
|
||||||
|
|
||||||
The full multinational dataset is kept as-is (106 countries for forenames,
|
The full multinational dataset is kept as-is (106 countries for forenames,
|
||||||
75 for surnames) rather than trimmed to `cities.json`'s current country
|
75 for surnames) rather than trimmed to `locations.json`'s current country
|
||||||
list, so it doesn't need re-sourcing if more countries are added later.
|
list, so it doesn't need re-sourcing if more countries are added later.
|
||||||
`SampleName()` (a free helper in `generate_users.cc`) returns no result for
|
`NamesByCountry::SampleName()` returns no result for a country present in
|
||||||
a country present in neither file; of the countries in `cities.json`,
|
neither file; of the countries in `locations.json`, that's currently `KE`,
|
||||||
that's currently `KE`, `SE`, `SG`, `TH`, `VN`, and `ZA` — `GenerateUsers`
|
`SE`, `SG`, `TH`, `VN`, and `ZA` — `GenerateUsers` skips cities in those
|
||||||
skips cities in those countries the same way brewery generation skips
|
countries the same way brewery generation skips cities whose enrichment
|
||||||
cities whose enrichment lookup fails.
|
lookup fails.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ Output sample:
|
|||||||
a location's code is unlisted, skip `description_local` generation and fall
|
a location's code is unlisted, skip `description_local` generation and fall
|
||||||
back to English.
|
back to English.
|
||||||
- **Upstream sanitization:** strip known low-resource language codes from the
|
- **Upstream sanitization:** strip known low-resource language codes from the
|
||||||
`cities.json` payload before generation.
|
`locations.json` payload before generation.
|
||||||
- **Downstream flagging:** add a `description_local_confidence` column to the
|
- **Downstream flagging:** add a `description_local_confidence` column to the
|
||||||
SQLite schema so downstream applications can filter or flag potentially
|
SQLite schema so downstream applications can filter or flag potentially
|
||||||
hallucinated text by language tier.
|
hallucinated text by language tier.
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# Biergarten Pipeline
|
# Biergarten Pipeline
|
||||||
|
|
||||||
A C++20 command-line pipeline that samples city records from local JSON,
|
A C++20 command-line pipeline that samples city records from local JSON,
|
||||||
enriches each with Wikipedia context, and generates bilingual brewery names
|
enriches each with Wikipedia context, and generates bilingual brewery names and
|
||||||
and descriptions plus locale-grounded user profiles via a local GGUF model or
|
descriptions via a local GGUF model or a deterministic mock.
|
||||||
a deterministic mock.
|
|
||||||
|
|
||||||
> **This pipeline produces AI-generated data.** It is not a source of truth for
|
> **This pipeline produces AI-generated data.** It is not a source of truth for
|
||||||
> brewing techniques, cultural representation, or local-language accuracy. See
|
> brewing techniques, cultural representation, or local-language accuracy. See
|
||||||
@@ -46,7 +45,6 @@ step.
|
|||||||
| Beer reviews and ratings | Stable brewery fixtures with enough context to anchor review pages |
|
| Beer reviews and ratings | Stable brewery fixtures with enough context to anchor review pages |
|
||||||
| Social follow relationships | Repeatable brewery entities for feeds, follows, and saved lists |
|
| Social follow relationships | Repeatable brewery entities for feeds, follows, and saved lists |
|
||||||
| Geospatial brewery experiences | Latitude, longitude, and country-level metadata |
|
| Geospatial brewery experiences | Latitude, longitude, and country-level metadata |
|
||||||
| User accounts and profiles | Locale-grounded names, bios, and an auth-ready email/date-of-birth pair for seeding real accounts |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -86,7 +84,7 @@ curl -L \
|
|||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
|
||||||
Run from `build/` so the copied `cities.json` and `prompts/` are available.
|
Run from `build/` so the copied `locations.json` and `prompts/` are available.
|
||||||
Each run writes a fresh dated SQLite file such as
|
Each run writes a fresh dated SQLite file such as
|
||||||
`biergarten_seed_2026-04-19T15-30-45.123456Z.sqlite` into the working directory.
|
`biergarten_seed_2026-04-19T15-30-45.123456Z.sqlite` into the working directory.
|
||||||
|
|
||||||
@@ -109,7 +107,7 @@ Each run writes a fresh dated SQLite file such as
|
|||||||
| `--prompt-dir` | Directory containing prompt files (e.g. `BREWERY_GENERATION.md`). Required unless `--mocked` is set. |
|
| `--prompt-dir` | Directory containing prompt files (e.g. `BREWERY_GENERATION.md`). Required unless `--mocked` is set. |
|
||||||
| `--output, -o` | Directory for generated SQLite artifacts. Default: `output`. |
|
| `--output, -o` | Directory for generated SQLite artifacts. Default: `output`. |
|
||||||
| `--log-path` | Path for application logs. Default: `pipeline.log`. |
|
| `--log-path` | Path for application logs. Default: `pipeline.log`. |
|
||||||
| `--location-count` | Number of cities to sample from `cities.json` per run. Default: `10`. |
|
| `--location-count` | Number of cities to sample from `locations.json` per run. Default: `10`. |
|
||||||
| `--temperature` | Sampling temperature. Default: `1.0`. |
|
| `--temperature` | Sampling temperature. Default: `1.0`. |
|
||||||
| `--top-p` | Nucleus sampling. Default: `0.95`. |
|
| `--top-p` | Nucleus sampling. Default: `0.95`. |
|
||||||
| `--top-k` | Top-k sampling. Default: `64`. |
|
| `--top-k` | Top-k sampling. Default: `64`. |
|
||||||
@@ -220,61 +218,35 @@ environment variables listed above to match your run.
|
|||||||
|
|
||||||
### Pipeline Stages
|
### Pipeline Stages
|
||||||
|
|
||||||
| Stage | Implementation |
|
| Stage | Implementation |
|
||||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Load | `ICuratedDataService` (`CuratedJsonDataService`) reads `cities.json`, `personas.json`, `forenames-by-country.json`, and `surnames-by-country.json` (paths supplied via a `CuratedDataFilePaths` DTO at construction) into typed records, caching each after its first load. `--mocked` runs use `MockCuratedDataService`'s fixed in-memory dataset instead. |
|
| Load | `JsonLoader::LoadLocations()` reads `locations.json` into typed `Location` records. |
|
||||||
| Sample | `BiergartenPipelineOrchestrator::QueryCitiesWithCountries()` samples `--location-count` locations per run (default `10`). |
|
| Sample | `BiergartenPipelineOrchestrator::QueryCitiesWithCountries()` samples `--location-count` locations per run (default `10`). |
|
||||||
| Enrich | `WikipediaEnrichmentService` fetches brewing and beer-related context. Keeps going when a lookup fails. `--mocked` runs use `MockEnrichmentService` instead and skip Wikipedia entirely. |
|
| Enrich | `WikipediaEnrichmentService` fetches brewing and beer-related context. Keeps going when a lookup fails. `--mocked` runs use `MockEnrichmentService` instead and skip Wikipedia entirely. |
|
||||||
| Generate Users | `GenerateUsers()` samples a persona and a forename/surname pair per enriched city (skipping countries with no name data), then `MockGenerator` or `LlamaGenerator` produces a username, bio, and activity weight around the sampled name. |
|
| Generate | `MockGenerator` or `LlamaGenerator` produces brewery names and descriptions in English and the local language. |
|
||||||
| Generate Breweries | `MockGenerator` or `LlamaGenerator` produces brewery names and descriptions in English and the local language. |
|
| Store | `SqliteExportService` writes each successful brewery into a fresh dated `.sqlite` database with normalized location and brewery tables. |
|
||||||
| Store | `SqliteExportService` writes each successful user and brewery into a fresh dated `.sqlite` database with normalized `cities`, `users`, and `breweries` tables. |
|
| Log | `spdlog` writes results and warnings to the console. |
|
||||||
| Log | `spdlog` writes results and warnings to the console. |
|
|
||||||
|
|
||||||
If name sampling, enrichment, or generation fails for a city, that city is
|
If enrichment or generation fails for a city, that city is skipped and the
|
||||||
skipped and the pipeline continues. `GenerateUsers()` runs before
|
pipeline continues.
|
||||||
`GenerateBreweries()` in `BiergartenPipelineOrchestrator::Run()`.
|
|
||||||
|
|
||||||
### Key Components
|
### Key Components
|
||||||
|
|
||||||
- `src/main.cc` — argument parsing and Boost.DI composition root.
|
- `src/main.cc` — argument parsing and Boost.DI composition root.
|
||||||
- `CuratedJsonDataService` — implements `ICuratedDataService`; takes a
|
- `JsonLoader` — validates curated location input.
|
||||||
`CuratedDataFilePaths` DTO (locations/personas/forenames/surnames paths) in
|
|
||||||
its constructor, then parses and validates curated location, persona, and
|
|
||||||
forename/surname JSON, memoizing each result after its first load on a
|
|
||||||
given instance. Each `cities.json` entry's `postal_code.city_regex` and
|
|
||||||
`postal_code.examples` are parsed into `City::postal_regex` and
|
|
||||||
`City::postal_code_examples`. `MockCuratedDataService` is the in-memory
|
|
||||||
substitute (4 fixed locations, 3 personas, and name data for
|
|
||||||
`US`/`DE`/`FR`/`BE`) used in `--mocked` runs, and carries matching
|
|
||||||
`postal_regex`/`postal_code_examples` values for its 4 locations.
|
|
||||||
- `WikipediaEnrichmentService` — queries Wikipedia extracts, caches results,
|
- `WikipediaEnrichmentService` — queries Wikipedia extracts, caches results,
|
||||||
returns empty context on failure. `MockEnrichmentService` is the no-op
|
returns empty context on failure. `MockEnrichmentService` is the no-op
|
||||||
substitute used in `--mocked` runs.
|
substitute used in `--mocked` runs.
|
||||||
- `IPostalCodeService` — generates a postal code for a `City`, consumed by
|
- `LlamaGenerator` — formats prompts for Gemma 4, validates JSON output, retries
|
||||||
`GenerateBreweries()` and stored on `BreweryRecord::address` (an `Address`
|
malformed responses up to three times with corrective feedback in the
|
||||||
struct, currently just `postal_code`, mirroring the web backend's
|
retry prompt. The token budget is fixed across attempts; it is not raised
|
||||||
`BreweryPostLocation`). Only `MockPostalCodeService` exists today, which
|
automatically on truncation.
|
||||||
ignores `postal_regex` and returns `postal_code_examples.front()` — it's
|
- `MockGenerator` — stable hash-based output so the same city input always
|
||||||
wired into the Boost.DI graph unconditionally (no `--mocked` branch yet,
|
produces the same brewery.
|
||||||
since there's no real implementation to switch to). A real implementation
|
- `SqliteExportService` — creates a dated SQLite file per run and persists each
|
||||||
still needs a **xeger**-style generator — turning a `postal_regex` pattern
|
successful brewery into normalized tables.
|
||||||
into a random matching string — instead of always replaying a fixed
|
|
||||||
example; see [ROADMAP.md §9](./ROADMAP.md#9-postal-code-generation).
|
|
||||||
Street-address generation (`Address::address_line1`) has no fixture data or
|
|
||||||
service yet and remains future work.
|
|
||||||
- `LlamaGenerator` — formats prompts for Gemma 4, validates JSON output for
|
|
||||||
both `GenerateBrewery` and `GenerateUser`, retries malformed responses up
|
|
||||||
to three times with corrective feedback in the retry prompt. The token
|
|
||||||
budget is fixed across attempts; it is not raised automatically on
|
|
||||||
truncation.
|
|
||||||
- `MockGenerator` — stable hash-based output so the same city/persona/name
|
|
||||||
input always produces the same brewery or user.
|
|
||||||
- `SqliteExportService` — creates a dated SQLite file per run and persists
|
|
||||||
each successful user and brewery into normalized tables.
|
|
||||||
- Brewery payloads include English and local-language name and description
|
- Brewery payloads include English and local-language name and description
|
||||||
fields. User payloads carry a sampled first/last name and gender, an
|
fields.
|
||||||
LLM-generated username/bio/activity weight, and a programmatically
|
|
||||||
generated (not LLM-authored) unique email and date of birth.
|
|
||||||
|
|
||||||
### Runtime Behaviour
|
### Runtime Behaviour
|
||||||
|
|
||||||
@@ -294,18 +266,6 @@ valid output on the first pass; the retry path is kept for resilience.
|
|||||||
`MockGenerator` uses stable hashes for repeatable output in demos and Storybook
|
`MockGenerator` uses stable hashes for repeatable output in demos and Storybook
|
||||||
runs.
|
runs.
|
||||||
|
|
||||||
`CuratedJsonDataService` memoizes each of `LoadCities()`, `LoadPersonas()`,
|
|
||||||
`LoadForenamesByCountry()`, and `LoadSurnamesByCountry()` independently the
|
|
||||||
first time each is called, since `BiergartenPipelineOrchestrator` owns a
|
|
||||||
single `ICuratedDataService` instance for the whole run — later calls return
|
|
||||||
the cached result instead of re-parsing.
|
|
||||||
|
|
||||||
`GenerateUsers()` samples a forename/surname pair per city via `SampleName()`,
|
|
||||||
keyed by the city's ISO 3166-1 code. Countries present in `cities.json`
|
|
||||||
but absent from either name fixture (currently `KE`, `SE`, `SG`, `TH`, `VN`,
|
|
||||||
`ZA`) are skipped, the same way a failed enrichment or generation call skips
|
|
||||||
a city — see ETHICS-AND-KNOWN-ISSUES.md's Names-by-Country Dataset section.
|
|
||||||
|
|
||||||
### Process Flow - Activity Diagram
|
### Process Flow - Activity Diagram
|
||||||
|
|
||||||

|

|
||||||
@@ -318,10 +278,9 @@ a city — see ETHICS-AND-KNOWN-ISSUES.md's Names-by-Country Dataset section.
|
|||||||
|
|
||||||
## Generated Output
|
## Generated Output
|
||||||
|
|
||||||
Each successful run stores a `BreweryRecord` (source `City`, an `Address`,
|
Each successful run stores a `GeneratedBrewery` pair with the source location
|
||||||
and a `BreweryResult` payload), and a `UserRecord` pair with the source
|
and a `BreweryResult` payload. The same generated records are also written to a
|
||||||
`City` and a `UserResult` payload. The same generated records are also
|
fresh SQLite export file named with the current UTC timestamp.
|
||||||
written to a fresh SQLite export file named with the current UTC timestamp.
|
|
||||||
|
|
||||||
| Field | Meaning |
|
| Field | Meaning |
|
||||||
| ------------------- | ------------------------------------------ |
|
| ------------------- | ------------------------------------------ |
|
||||||
@@ -329,18 +288,6 @@ written to a fresh SQLite export file named with the current UTC timestamp.
|
|||||||
| `description_en` | Brewery description in English. |
|
| `description_en` | Brewery description in English. |
|
||||||
| `name_local` | Brewery name in the local language. |
|
| `name_local` | Brewery name in the local language. |
|
||||||
| `description_local` | Brewery description in the local language. |
|
| `description_local` | Brewery description in the local language. |
|
||||||
| `postal_code` | Postal code generated for the brewery's city (see `IPostalCodeService`, above). |
|
|
||||||
|
|
||||||
| Field | Meaning |
|
|
||||||
| ----------------- | ----------------------------------------------------------------- |
|
|
||||||
| `first_name` | Sampled forename, copied from the curated name data (not LLM-invented). |
|
|
||||||
| `last_name` | Sampled surname, copied from the curated name data (not LLM-invented). |
|
|
||||||
| `gender` | Gender associated with the sampled forename in the source dataset. |
|
|
||||||
| `username` | LLM-generated handle. |
|
|
||||||
| `bio` | LLM-generated short biography. |
|
|
||||||
| `activity_weight` | Relative check-in/activity weight, reserved for a future J-curve activity profile. |
|
|
||||||
| `email` | Unique `@thebiergarten.app` address, generated programmatically from the sampled name. |
|
|
||||||
| `date_of_birth` | Randomized date of birth (age 19-48), generated programmatically. |
|
|
||||||
|
|
||||||
The log dump also includes city, country, state or province, ISO subdivision
|
The log dump also includes city, country, state or province, ISO subdivision
|
||||||
code, latitude, and longitude for each entry.
|
code, latitude, and longitude for each entry.
|
||||||
@@ -355,7 +302,6 @@ code, latitude, and longitude for each entry.
|
|||||||
| `local_languages` | Locale-aware copy selection |
|
| `local_languages` | Locale-aware copy selection |
|
||||||
| `name_en`, `description_en` | Default English display content |
|
| `name_en`, `description_en` | Default English display content |
|
||||||
| `name_local`, `description_local` | Local-language display content |
|
| `name_local`, `description_local` | Local-language display content |
|
||||||
| `postal_code` | Brewery address matching, mirrors the web backend's `BreweryPostLocation.PostalCode` |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -421,17 +367,11 @@ Silicon; CUDA or HIP/ROCm is detected on Linux when the toolkit is present.
|
|||||||
## Fixture Strategy
|
## Fixture Strategy
|
||||||
|
|
||||||
- `--mocked` for stable fixtures, repeatable screenshots, and Storybook runs.
|
- `--mocked` for stable fixtures, repeatable screenshots, and Storybook runs.
|
||||||
`MockCuratedDataService` swaps in for `CuratedJsonDataService`, so no
|
|
||||||
fixture files need to be present on disk.
|
|
||||||
- `--model` when geographically grounded content matters for demos.
|
- `--model` when geographically grounded content matters for demos.
|
||||||
- Keep `cities.json` structured enough to support discovery and future
|
- Keep `locations.json` structured enough to support discovery and future
|
||||||
filtering.
|
filtering.
|
||||||
- `personas.json`, `forenames-by-country.json`, and
|
- Treat SQLite output as seed material for the app's brewery domain, not
|
||||||
`surnames-by-country.json` are curated/vendored fixture data, not
|
production data.
|
||||||
LLM-generated — see ETHICS-AND-KNOWN-ISSUES.md's Names-by-Country Dataset
|
|
||||||
section for provenance.
|
|
||||||
- Treat SQLite output as seed material for the app's brewery and user
|
|
||||||
domains, not production data.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -441,10 +381,7 @@ Silicon; CUDA or HIP/ROCm is detected on Linux when the toolkit is present.
|
|||||||
| -------------------------------------- | -------------------------------------------------- |
|
| -------------------------------------- | -------------------------------------------------- |
|
||||||
| `tooling/pipeline/includes/` | Public headers and shared models. |
|
| `tooling/pipeline/includes/` | Public headers and shared models. |
|
||||||
| `tooling/pipeline/src/` | Implementation files. |
|
| `tooling/pipeline/src/` | Implementation files. |
|
||||||
| `tooling/pipeline/cities.json` | Curated city input copied into the build tree. |
|
| `tooling/pipeline/locations.json` | Curated city input copied into the build tree. |
|
||||||
| `tooling/pipeline/personas.json` | Curated user persona archetypes copied into the build tree. |
|
|
||||||
| `tooling/pipeline/forenames-by-country.json` | Vendored (CC0) forename data by ISO 3166-1 country code. |
|
|
||||||
| `tooling/pipeline/surnames-by-country.json` | Vendored (CC0) surname data by ISO 3166-1 country code. |
|
|
||||||
| `tooling/pipeline/prompts/` | System prompts used by the model-backed path. |
|
| `tooling/pipeline/prompts/` | System prompts used by the model-backed path. |
|
||||||
| `tooling/pipeline/runpod/` | Dockerfile, launcher, and RunPod pod template. |
|
| `tooling/pipeline/runpod/` | Dockerfile, launcher, and RunPod pod template. |
|
||||||
| `docs/pipeline/diagrams/` | Architecture and pipeline diagrams. |
|
| `docs/pipeline/diagrams/` | Architecture and pipeline diagrams. |
|
||||||
@@ -459,14 +396,7 @@ Paths below are relative to `tooling/pipeline/`.
|
|||||||
- `src/main.cc` — argument parsing and DI composition root.
|
- `src/main.cc` — argument parsing and DI composition root.
|
||||||
- `src/biergarten_pipeline_orchestrator/` — orchestration, sampling, logging,
|
- `src/biergarten_pipeline_orchestrator/` — orchestration, sampling, logging,
|
||||||
and export.
|
and export.
|
||||||
- `src/services/curated_data/` — `CuratedJsonDataService`, the file-backed
|
|
||||||
`ICuratedDataService`, and `MockCuratedDataService`, the in-memory
|
|
||||||
`ICuratedDataService` used in `--mocked` runs.
|
|
||||||
- `src/services/enrichment/wikipedia/` — enrichment service and cache.
|
- `src/services/enrichment/wikipedia/` — enrichment service and cache.
|
||||||
- `includes/services/postal_code/` — `IPostalCodeService` and
|
|
||||||
`MockPostalCodeService` (header-only), consumed by `GenerateBreweries()`.
|
|
||||||
The real xeger-based implementation and its `--mocked`-aware DI wiring are
|
|
||||||
still to come.
|
|
||||||
- `src/services/sqlite/` — SQLite export implementation.
|
- `src/services/sqlite/` — SQLite export implementation.
|
||||||
- `src/data_generation/llama/` — local inference, prompt loading, output
|
- `src/data_generation/llama/` — local inference, prompt loading, output
|
||||||
validation.
|
validation.
|
||||||
@@ -477,12 +407,11 @@ Paths below are relative to `tooling/pipeline/`.
|
|||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
The pipeline currently produces city-aware brewery and user records and
|
The pipeline currently produces city-aware brewery records and dated SQLite
|
||||||
dated SQLite exports. The next passes add additional fixture types so the
|
exports. The next passes add additional fixture types so the app can exercise
|
||||||
app can exercise the full brewery and social domains without live data. For
|
the full brewery domain without live data. For the detailed engineering
|
||||||
the detailed engineering breakdown of what's needed to reach the
|
breakdown of what's needed to reach the architecture in
|
||||||
architecture in [`diagrams/planned/`](./diagrams/planned/), see
|
[`diagrams/planned/`](./diagrams/planned/), see [ROADMAP.md](./ROADMAP.md).
|
||||||
[ROADMAP.md](./ROADMAP.md).
|
|
||||||
|
|
||||||
### Testing — Very High Priority
|
### Testing — Very High Priority
|
||||||
|
|
||||||
@@ -504,6 +433,12 @@ Generate catalog entries with style, ABV, IBU, color, aroma notes, and food
|
|||||||
pairing hints. Link beers back to breweries and cities. Keep style coverage wide
|
pairing hints. Link beers back to breweries and cities. Keep style coverage wide
|
||||||
enough to exercise search, sort, and category filters.
|
enough to exercise search, sort, and category filters.
|
||||||
|
|
||||||
|
### User Generation
|
||||||
|
|
||||||
|
Generate user profiles with stable names, bios, locale hints, and preference
|
||||||
|
signals. Include stable IDs for downstream fixture joins. Keep output
|
||||||
|
deterministic for screenshots while allowing larger randomized batches.
|
||||||
|
|
||||||
### Check-In System
|
### Check-In System
|
||||||
|
|
||||||
Produce timestamped check-in events between users and breweries. Use a J-curve
|
Produce timestamped check-in events between users and breweries. Use a J-curve
|
||||||
|
|||||||
@@ -39,119 +39,54 @@ architecture needs.
|
|||||||
not LLM-invented.
|
not LLM-invented.
|
||||||
- [x] Add `Name` (`first_name`, `last_name`, `gender`) — the sampled result
|
- [x] Add `Name` (`first_name`, `last_name`, `gender`) — the sampled result
|
||||||
handed to `DataGenerator::GenerateUser`. Add `ForenameEntry` (`name`,
|
handed to `DataGenerator::GenerateUser`. Add `ForenameEntry` (`name`,
|
||||||
`gender`). Landed as a flatter shape than originally planned here: no
|
`gender`) and a `NamesByCountry` class (not a bare typedef) that owns
|
||||||
`NamesByCountry` wrapper class. `curated_data_service.h` instead
|
two maps — `std::unordered_map<std::string, std::vector<ForenameEntry>>`
|
||||||
declares `ForenameList = std::vector<ForenameEntry>` and
|
and `std::unordered_map<std::string, std::vector<std::string>>`, both
|
||||||
`SurnameList = std::vector<std::string>`, and
|
keyed by ISO 3166-1 code — and exposes `SampleName(iso3166_1, rng)`.
|
||||||
`ICuratedDataService` exposes two maps keyed by ISO 3166-1 code —
|
|
||||||
`ForenamesByCountryMap = unordered_map<string, ForenameList>` and
|
|
||||||
`SurnamesByCountryMap = unordered_map<string, SurnameList>` —
|
|
||||||
directly (see §2). Sampling is
|
|
||||||
a free function, `SampleName(forenames_by_country,
|
|
||||||
surnames_by_country, iso3166_1, rng)`, in
|
|
||||||
`generate_users.cc`'s anonymous namespace, not a method on a class.
|
|
||||||
Pairing a forename with a surname happens at sample time so gender
|
Pairing a forename with a surname happens at sample time so gender
|
||||||
(present per-forename in the source data) is never discarded the way a
|
(present per-forename in the source data) is never discarded the way a
|
||||||
pre-flattened `{first_name, last_name}` list would lose it; see §2.
|
pre-flattened `{first_name, last_name}` list would lose it; see
|
||||||
|
`LoadNamesByCountry()` below.
|
||||||
- [ ] Extend `GeneratedBrewery` with `brewery_id`, `context_completeness`,
|
- [ ] Extend `GeneratedBrewery` with `brewery_id`, `context_completeness`,
|
||||||
`metadata` (currently `{location, address, brewery}` — see the
|
`metadata` (currently just `{location, brewery}`).
|
||||||
`Location`→`City` rename and `Address` struct added in the
|
|
||||||
`postal_regex`/§9 entries below).
|
|
||||||
- [ ] Add `GeneratedBeer`, `GeneratedCheckin`, `GeneratedRating`,
|
- [ ] Add `GeneratedBeer`, `GeneratedCheckin`, `GeneratedRating`,
|
||||||
`GeneratedFollow` aggregate structs.
|
`GeneratedFollow` aggregate structs.
|
||||||
- [x] Add `UserRecord` (`location`, `user : UserResult`, `email`,
|
- [x] Add `GeneratedUser` aggregate struct. Carries `email`, `date_of_birth`,
|
||||||
`date_of_birth`) as the current stand-in for the planned
|
and `password` — programmatically generated by the orchestrator, never
|
||||||
`GeneratedUser` — `email` and `date_of_birth` are programmatically
|
LLM-authored — so a downstream auth-account seeding consumer (outside
|
||||||
generated by the orchestrator, never LLM-authored, so a downstream
|
this repo tree's docs) can register real accounts from the pipeline's
|
||||||
auth-account seeding consumer can register real accounts from the
|
SQLite export. Skips `user_id`/`metadata` for now, matching
|
||||||
pipeline's SQLite export. No `password`, `user_id`, or `metadata`
|
`GeneratedBrewery`'s current (pre-`brewery_id`/`metadata`) shape. Not
|
||||||
field yet, matching `BreweryRecord`'s equally pre-`metadata` shape.
|
yet wired into `IExportService`/`SqliteExportService` — `GenerateUsers`
|
||||||
Already wired into `IExportService`/`SqliteExportService`: a `users`
|
currently only logs and holds `generated_users_` in memory; no `users`
|
||||||
table exists and `GenerateUsers()` exports each successful record via
|
table exists in the SQLite export yet (see §5).
|
||||||
`ProcessRecord(const UserRecord&)` (see §5) in addition to logging and
|
|
||||||
holding `generated_users_` in memory. Still missing vs. the planned
|
|
||||||
`GeneratedUser`: `user_id`, `password`, and `metadata`.
|
|
||||||
- [x] Add `UserPersona` (`name`, `description`, `style_affinities`).
|
- [x] Add `UserPersona` (`name`, `description`, `style_affinities`).
|
||||||
- [x] Add `postal_regex` (`std::vector<std::string>`) and
|
|
||||||
`postal_code_examples` (`std::vector<std::string>`) to `Location`
|
|
||||||
(now `City` — see below), sourced from each `cities.json` entry's
|
|
||||||
`postal_code.city_regex` / `postal_code.examples`. Not part of the
|
|
||||||
originally planned `Location` shape in `diagrams/planned/class.puml` —
|
|
||||||
added ahead of postal code generation work; see §9.
|
|
||||||
- [x] Renamed `Location` → `City` throughout the pipeline (struct, all
|
|
||||||
parameter/return types, `LocationsList`→`CityList`,
|
|
||||||
`LoadLocations()`→`LoadCities()`) so the domain vocabulary matches the
|
|
||||||
web backend's `City`/`CityId`. Field/variable names that merely hold a
|
|
||||||
`City` instance (e.g. `EnrichedCity::location`, `BreweryRecord::location`)
|
|
||||||
were left as-is — `City` itself has a `city` field (the city name
|
|
||||||
string), so renaming the container field too would read as
|
|
||||||
`enriched_city.city.city`. The SQLite export layer's own vocabulary
|
|
||||||
(`locations` table, `location_id` column, `location_cache_`, etc.) was
|
|
||||||
renamed to `cities`/`city_id` to match, since it's this pipeline's own
|
|
||||||
throwaway export, not the backend's SQL Server schema (see §5).
|
|
||||||
- [x] Added a new `Address` struct (`postal_code` only, for now) and a
|
|
||||||
`BreweryRecord::address` member, mirroring the web backend's
|
|
||||||
`BreweryPostLocation` (`AddressLine1`, `AddressLine2`, `PostalCode`,
|
|
||||||
`CityId`). Only `postal_code` is populated — `address_line1`/
|
|
||||||
`address_line2` generation has no fixture data or service yet. Users
|
|
||||||
do not get an `Address`: the backend's `UserAccount` has no address
|
|
||||||
fields, only `BreweryPostLocation` does.
|
|
||||||
|
|
||||||
## 2. Data Preloading
|
## 2. Data Preloading
|
||||||
|
|
||||||
`tooling/pipeline/includes/services/curated_data/`, fixture files.
|
`tooling/pipeline/includes/json_handling/`, fixture files.
|
||||||
|
|
||||||
- [x] Extract an interface; have `CuratedJsonDataService` implement it.
|
- [ ] Extract a `DataPreloader` interface; have `JsonLoader` implement it.
|
||||||
Landed as `ICuratedDataService`
|
`JsonLoader` now also exposes static `LoadPersonas()` and
|
||||||
(`services/curated_data/curated_data_service.h`), not `DataPreloader`
|
`LoadNamesByCountry()` (see below), but as additional static methods,
|
||||||
— `LoadCities()`, `LoadPersonas()`, `LoadForenamesByCountry()`, and
|
not yet behind a formal interface.
|
||||||
`LoadSurnamesByCountry()` are all virtual methods returning `const&`
|
|
||||||
and take no arguments. `CuratedJsonDataService`
|
|
||||||
(`services/curated_data/curated_json_data_service.h`, originally
|
|
||||||
landed as `JsonLoader` under `json_handling/` before being renamed and
|
|
||||||
moved) takes a `CuratedDataFilePaths` DTO (`locations_path`,
|
|
||||||
`personas_path`, `forenames_path`, `surnames_path`) in its
|
|
||||||
constructor rather than a path per `Load*()` call, and memoizes each
|
|
||||||
result in a private `cache` struct (`locations`, `personas`,
|
|
||||||
`forenames_by_country`, `surnames_by_country`), so a second call on
|
|
||||||
the same instance returns the cached result instead of re-parsing —
|
|
||||||
safe because `CuratedJsonDataService` outlives every call site (owned
|
|
||||||
by `BiergartenPipelineOrchestrator` via
|
|
||||||
`unique_ptr<ICuratedDataService>` for the whole run). `main.cc`'s DI
|
|
||||||
injector also gained a `MockCuratedDataService` (fixed in-memory
|
|
||||||
data: 4 locations across `US`/`DE`/`FR`/`BE`, 3 personas, and
|
|
||||||
matching forename/surname sets for those four countries), bound in
|
|
||||||
place of `CuratedJsonDataService` under `--mocked`, mirroring the
|
|
||||||
existing `MockEnrichmentService`/`MockGenerator` pattern.
|
|
||||||
- [ ] Add `LoadBeerStyles()`. `beer-styles.json` already exists in the repo
|
- [ ] Add `LoadBeerStyles()`. `beer-styles.json` already exists in the repo
|
||||||
and is already copied into the Docker image
|
and is already copied into the Docker image
|
||||||
(`runpod/Dockerfile`), but no loader reads it yet, and the native
|
(`runpod/Dockerfile`), but no loader reads it yet, and the native
|
||||||
CMake build doesn't copy it into `build/` at all — `CMakeLists.txt`'s
|
CMake build doesn't copy it into `build/` at all — `CMakeLists.txt`'s
|
||||||
"Runtime Assets" step only copies `cities.json` and `prompts/`.
|
"Runtime Assets" step only copies `locations.json` and `prompts/`.
|
||||||
- [x] Add `LoadPersonas()` and author `personas.json` (doesn't exist yet).
|
- [x] Add `LoadPersonas()` and author `personas.json` (doesn't exist yet).
|
||||||
- [x] Add name-by-country loading, parsing
|
- [x] Add `LoadNamesByCountry(forenames_filepath, surnames_filepath)`,
|
||||||
`tooling/pipeline/forenames-by-country.json` and
|
parsing `tooling/pipeline/forenames-by-country.json` and
|
||||||
`surnames-by-country.json`. Landed as two separate methods —
|
`surnames-by-country.json` into a `NamesByCountry`. Both files are
|
||||||
`LoadForenamesByCountry()` and `LoadSurnamesByCountry()` — each
|
vendored verbatim (unmodified, full multinational coverage) from
|
||||||
returning a flat `ForenamesByCountryMap` / `SurnamesByCountryMap`
|
|
||||||
(aliases for `unordered_map<string, ForenameList>` /
|
|
||||||
`unordered_map<string, SurnameList>`) keyed by ISO 3166-1 code, rather
|
|
||||||
than one combined `LoadNamesByCountry() : NamesByCountry` call. Both
|
|
||||||
files are vendored verbatim (unmodified,
|
|
||||||
full multinational coverage) from
|
|
||||||
`sigpwned/popular-names-by-country-dataset` (CC0) — see
|
`sigpwned/popular-names-by-country-dataset` (CC0) — see
|
||||||
ETHICS-AND-KNOWN-ISSUES.md's Names-by-Country Dataset section for
|
ETHICS-AND-KNOWN-ISSUES.md's Names-by-Country Dataset section for
|
||||||
provenance. Deliberately not pre-paired or filtered down to just the
|
provenance. Deliberately not pre-paired or filtered down to just the
|
||||||
countries in `cities.json`: keeping the source shape (including
|
countries in `locations.json`: keeping the source shape (including
|
||||||
per-forename gender) intact means the loader can support more
|
per-forename gender) intact means the loader can support more
|
||||||
countries later, or gender-aware persona/bio generation, without
|
countries later, or gender-aware persona/bio generation, without
|
||||||
re-sourcing data.
|
re-sourcing data.
|
||||||
- [x] Parse `postal_code.city_regex` and `postal_code.examples` out of each
|
|
||||||
`cities.json` entry's nested `postal_code` object in
|
|
||||||
`CuratedJsonDataService::LoadCities()`, into `City::postal_regex`
|
|
||||||
/ `City::postal_code_examples` (see §1). `MockCuratedDataService`'s
|
|
||||||
4 fixed locations carry matching values pulled from the corresponding
|
|
||||||
`cities.json` entries.
|
|
||||||
|
|
||||||
## 3. Policy / Strategy Layer
|
## 3. Policy / Strategy Layer
|
||||||
|
|
||||||
@@ -201,25 +136,15 @@ Entirely new — no `includes/policy/` (or equivalent) directory exists today.
|
|||||||
|
|
||||||
`tooling/pipeline/includes/services/database/`, `src/services/sqlite/`.
|
`tooling/pipeline/includes/services/database/`, `src/services/sqlite/`.
|
||||||
|
|
||||||
- [x] `IExportService::ProcessRecord(const UserRecord&)` and
|
- [ ] Extend `IExportService` with `ProcessBeer`, `ProcessUser`,
|
||||||
`SqliteExportService`'s `users` table (see
|
`ProcessCheckin`, `ProcessRating`, `ProcessFollow` (today only
|
||||||
`kCreateUsersTableSql` / `kInsertUserSql` in
|
`ProcessRecord(const GeneratedBrewery&)` exists).
|
||||||
`includes/services/database/sqlite_statement_helpers.h`) are
|
- [ ] Add `beers`, `users`, `checkins`, `ratings`, `follows` tables to
|
||||||
implemented — `GenerateUsers()` exports every successful user
|
`SqliteExportService::InitializeSchema()` — see
|
||||||
alongside its resolved `city_id`.
|
`kCreateLocationsTableSql` / `kCreateBreweriesTableSql` in
|
||||||
- [x] `ProcessRecord(const BreweryRecord&)` now also binds
|
|
||||||
`BreweryRecord::address.postal_code` into a `postal_code` column on
|
|
||||||
`breweries` (see §9).
|
|
||||||
- [ ] Extend `IExportService` with `ProcessBeer`, `ProcessCheckin`,
|
|
||||||
`ProcessRating`, `ProcessFollow` (today `ProcessRecord(const
|
|
||||||
BreweryRecord&)` and `ProcessRecord(const UserRecord&)` exist).
|
|
||||||
- [ ] Add `beers`, `checkins`, `ratings`, `follows` tables to
|
|
||||||
`SqliteExportService::InitializeSchema()` (`cities`, `breweries`,
|
|
||||||
and `users` already exist) — see `kCreateCitiesTableSql` /
|
|
||||||
`kCreateBreweriesTableSql` / `kCreateUsersTableSql` in
|
|
||||||
`includes/services/database/sqlite_statement_helpers.h` for the
|
`includes/services/database/sqlite_statement_helpers.h` for the
|
||||||
existing pattern to follow.
|
existing pattern to follow.
|
||||||
- [ ] Add a `brewery_cache_` alongside the existing `city_cache_`, and
|
- [ ] Add a `brewery_cache_` alongside the existing `location_cache_`, and
|
||||||
move from one open transaction for the whole run to per-phase batched
|
move from one open transaction for the whole run to per-phase batched
|
||||||
commits (`BEGIN` / `COMMIT & BEGIN` on a batch-size threshold), as
|
commits (`BEGIN` / `COMMIT & BEGIN` on a batch-size threshold), as
|
||||||
shown in the planned activity diagram.
|
shown in the planned activity diagram.
|
||||||
@@ -262,42 +187,11 @@ Entirely new — no `includes/policy/` (or equivalent) directory exists today.
|
|||||||
|
|
||||||
## 8. Fixtures / Build
|
## 8. Fixtures / Build
|
||||||
|
|
||||||
- [x] Author `personas.json`, `forenames-by-country.json`, and
|
- [ ] Author `personas.json` and `names-by-country.json` (see §2).
|
||||||
`surnames-by-country.json` (see §2).
|
|
||||||
- [ ] Fix the `beer-styles.json` build-tree gap: add it to the "Runtime
|
- [ ] Fix the `beer-styles.json` build-tree gap: add it to the "Runtime
|
||||||
Assets" `configure_file` step in `CMakeLists.txt` so native builds and
|
Assets" `configure_file` step in `CMakeLists.txt` so native builds and
|
||||||
the Docker image agree on what's available at runtime.
|
the Docker image agree on what's available at runtime.
|
||||||
|
|
||||||
## 9. Postal Code Generation
|
|
||||||
|
|
||||||
`tooling/pipeline/includes/services/postal_code/`. Not part of the original
|
|
||||||
`diagrams/planned/class.puml` sketch — added once `Location` (now `City`)
|
|
||||||
gained `postal_regex`/`postal_code_examples` (§1, §2).
|
|
||||||
|
|
||||||
- [x] Add the `IPostalCodeService` interface —
|
|
||||||
`GeneratePostalCode(location : const City&) : std::string`.
|
|
||||||
- [x] Add `MockPostalCodeService`: ignores `location.postal_regex` entirely
|
|
||||||
and returns `location.postal_code_examples.front()`.
|
|
||||||
- [ ] **Implement a real, xeger-based `IPostalCodeService`.** A xeger
|
|
||||||
generator turns a regex pattern (here, one of
|
|
||||||
`City::postal_regex`) into a random string that matches it — the
|
|
||||||
inverse of matching a string against a regex. Without it, every
|
|
||||||
generated postal code is either a hardcoded mock value or absent
|
|
||||||
entirely; a real implementation is required before postal codes can
|
|
||||||
vary per generated brewery instead of always reusing the same
|
|
||||||
curated example.
|
|
||||||
- [x] Wire `IPostalCodeService` into `main.cc`'s Boost.DI injector — bound
|
|
||||||
unconditionally to `MockPostalCodeService` (no `--mocked` branch yet,
|
|
||||||
since a real implementation doesn't exist to switch to; add one once
|
|
||||||
the xeger-based generator above lands, mirroring `IEnrichmentService`)
|
|
||||||
— and into `BiergartenPipelineOrchestrator::GenerateBreweries()`,
|
|
||||||
which calls `GeneratePostalCode(location)` per brewery and stores the
|
|
||||||
result on `BreweryRecord::address` (a new `Address` struct — see §1),
|
|
||||||
exported to the `breweries.postal_code` SQLite column (§5).
|
|
||||||
Postal-code generation is per-brewery, not per-city or per-user: users
|
|
||||||
don't have an address concept in the web backend, only breweries do
|
|
||||||
(`BreweryPostLocation`), so `UserRecord` was deliberately not extended.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Suggested build order
|
## Suggested build order
|
||||||
@@ -315,7 +209,3 @@ populated. Phase 1b may begin.", etc.) imply a dependency order. Roughly:
|
|||||||
planned diagrams.
|
planned diagrams.
|
||||||
5. Enrichment cache pre-warming and the city/region query decision (§7) —
|
5. Enrichment cache pre-warming and the city/region query decision (§7) —
|
||||||
useful at any point, but most valuable once phases run concurrently.
|
useful at any point, but most valuable once phases run concurrently.
|
||||||
6. Postal code generation (§9) is independent of the phases above — the
|
|
||||||
xeger-based generator just needs `City::postal_regex`, already in
|
|
||||||
place. Wiring the mock into brewery output has already landed (§9); the
|
|
||||||
real generator can be swapped in once built.
|
|
||||||
|
|||||||
@@ -69,14 +69,14 @@ end note
|
|||||||
:QueryCitiesWithCountries();
|
:QueryCitiesWithCountries();
|
||||||
|
|
||||||
|#E2EBDC|JsonLoader|
|
|#E2EBDC|JsonLoader|
|
||||||
:JsonLoader::LoadCities("cities.json");
|
:JsonLoader::LoadLocations("locations.json");
|
||||||
:std::ranges::sample(all_locations, --location-count);
|
:std::ranges::sample(all_locations, --location-count);
|
||||||
note right
|
note right
|
||||||
--location-count defaults to 10.
|
--location-count defaults to 10.
|
||||||
end note
|
end note
|
||||||
|
|
||||||
|#EAF0E8|BiergartenPipelineOrchestrator|
|
|#EAF0E8|BiergartenPipelineOrchestrator|
|
||||||
while (For each sampled City?) is (Remaining cities)
|
while (For each sampled Location?) is (Remaining cities)
|
||||||
|#DCE8D8|IEnrichmentService|
|
|#DCE8D8|IEnrichmentService|
|
||||||
:GetLocationContext(loc);
|
:GetLocationContext(loc);
|
||||||
note right
|
note right
|
||||||
@@ -89,78 +89,7 @@ while (For each sampled City?) is (Remaining cities)
|
|||||||
if (Lookup failed?) then (yes)
|
if (Lookup failed?) then (yes)
|
||||||
:Log warning, skip city;
|
:Log warning, skip city;
|
||||||
else (no)
|
else (no)
|
||||||
:Store EnrichedCity{City, region_context};
|
:Store EnrichedCity{Location, region_context};
|
||||||
endif
|
|
||||||
endwhile (Done)
|
|
||||||
|
|
||||||
|#EAF0E8|BiergartenPipelineOrchestrator|
|
|
||||||
:GenerateUsers(enriched_cities);
|
|
||||||
|
|
||||||
|#E2EBDC|JsonLoader|
|
|
||||||
:LoadPersonas("personas.json");
|
|
||||||
:LoadForenamesByCountry("forenames-by-country.json");
|
|
||||||
:LoadSurnamesByCountry("surnames-by-country.json");
|
|
||||||
note right
|
|
||||||
Each call is cached after its first parse.
|
|
||||||
MockCuratedDataService (--mocked) returns a
|
|
||||||
fixed in-memory dataset instead and skips
|
|
||||||
these three JSON files entirely.
|
|
||||||
end note
|
|
||||||
|
|
||||||
|#EAF0E8|BiergartenPipelineOrchestrator|
|
|
||||||
while (For each EnrichedCity?) is (Remaining cities)
|
|
||||||
:SampleName(forenames_by_country,\n surnames_by_country, location.iso3166_1);
|
|
||||||
if (Names available for country?) then (no)
|
|
||||||
:Log warning, skip city;
|
|
||||||
else (yes)
|
|
||||||
:Sample UserPersona (uniform random);
|
|
||||||
|
|
||||||
|#E5EDE1|DataGenerator|
|
|
||||||
if (Generator Mode) then (MockGenerator)
|
|
||||||
:DeterministicHash & Format;
|
|
||||||
else (LlamaGenerator)
|
|
||||||
:prompt_directory_->Load("USER_GENERATION");
|
|
||||||
note right
|
|
||||||
Resolves to USER_GENERATION.md inside --prompt-dir.
|
|
||||||
end note
|
|
||||||
repeat
|
|
||||||
:Infer(system_prompt, user_prompt, max_tokens, kUserJsonGrammar);
|
|
||||||
:ValidateUserJson(raw, user);
|
|
||||||
if (Is JSON Valid?) then (yes)
|
|
||||||
break
|
|
||||||
else (no)
|
|
||||||
:Attempt++;
|
|
||||||
:Append validation error to retry prompt;
|
|
||||||
endif
|
|
||||||
repeat while (Attempt < 3?) is (yes)
|
|
||||||
endif
|
|
||||||
|
|
||||||
|#EAF0E8|BiergartenPipelineOrchestrator|
|
|
||||||
if (Generation successful?) then (yes)
|
|
||||||
:BuildEmail(name, used_email_local_parts);
|
|
||||||
:GenerateDateOfBirth(rng);
|
|
||||||
note right
|
|
||||||
email and date_of_birth are generated
|
|
||||||
programmatically, never LLM-authored.
|
|
||||||
end note
|
|
||||||
|
|
||||||
|#E0EAE0|SqliteExportService|
|
|
||||||
:ProcessRecord(UserRecord);
|
|
||||||
if (City in cache?) then (yes)
|
|
||||||
:Reuse city_id;
|
|
||||||
else (no)
|
|
||||||
:Insert City & Cache ID;
|
|
||||||
endif
|
|
||||||
:Insert User (FK: city_id);
|
|
||||||
|
|
||||||
if (Exception caught during insert?) then (yes)
|
|
||||||
|#EAF0E8|BiergartenPipelineOrchestrator|
|
|
||||||
:Log warning "SQLite export failed";
|
|
||||||
else (no)
|
|
||||||
endif
|
|
||||||
else (no)
|
|
||||||
:Log warning "Generation failed, skipping...";
|
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
endwhile (Done)
|
endwhile (Done)
|
||||||
|
|
||||||
@@ -197,12 +126,12 @@ while (For each EnrichedCity?) is (Remaining cities)
|
|||||||
if (Generation successful?) then (yes)
|
if (Generation successful?) then (yes)
|
||||||
|#E0EAE0|SqliteExportService|
|
|#E0EAE0|SqliteExportService|
|
||||||
:ProcessRecord(GeneratedBrewery);
|
:ProcessRecord(GeneratedBrewery);
|
||||||
if (City in cache?) then (yes)
|
if (Location in cache?) then (yes)
|
||||||
:Reuse city_id;
|
:Reuse location_id;
|
||||||
else (no)
|
else (no)
|
||||||
:Insert City & Cache ID;
|
:Insert Location & Cache ID;
|
||||||
endif
|
endif
|
||||||
:Insert Brewery (FK: city_id);
|
:Insert Brewery (FK: location_id);
|
||||||
|
|
||||||
if (Exception caught during insert?) then (yes)
|
if (Exception caught during insert?) then (yes)
|
||||||
|#EAF0E8|BiergartenPipelineOrchestrator|
|
|#EAF0E8|BiergartenPipelineOrchestrator|
|
||||||
|
|||||||
@@ -30,15 +30,11 @@ class BiergartenPipelineOrchestrator {
|
|||||||
- context_service_ : std::unique_ptr<IEnrichmentService>
|
- context_service_ : std::unique_ptr<IEnrichmentService>
|
||||||
- generator_ : std::unique_ptr<DataGenerator>
|
- generator_ : std::unique_ptr<DataGenerator>
|
||||||
- exporter_ : std::unique_ptr<IExportService>
|
- exporter_ : std::unique_ptr<IExportService>
|
||||||
- curated_data_service_ : std::unique_ptr<ICuratedDataService>
|
|
||||||
- postal_code_service_ : std::unique_ptr<IPostalCodeService>
|
|
||||||
- application_options_ : ApplicationOptions
|
- application_options_ : ApplicationOptions
|
||||||
- generated_breweries_ : std::vector<BreweryRecord>
|
- generated_breweries_ : std::vector<GeneratedBrewery>
|
||||||
- generated_users_ : std::vector<UserRecord>
|
|
||||||
+ Run() : bool
|
+ Run() : bool
|
||||||
- QueryCitiesWithCountries() : std::vector<City>
|
- QueryCitiesWithCountries() : std::vector<Location>
|
||||||
- GenerateBreweries(cities : std::span<const EnrichedCity>) : void
|
- GenerateBreweries(cities : std::span<const EnrichedCity>) : void
|
||||||
- GenerateUsers(cities : std::span<const EnrichedCity>) : void
|
|
||||||
- LogResults() : void
|
- LogResults() : void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,17 +87,17 @@ class LogDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IEnrichmentService <<interface>> {
|
interface IEnrichmentService <<interface>> {
|
||||||
+ GetLocationContext(loc : const City&) : std::string
|
+ GetLocationContext(loc : const Location&) : std::string
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockEnrichmentService {
|
class MockEnrichmentService {
|
||||||
+ GetLocationContext(loc : const City&) : std::string
|
+ GetLocationContext(loc : const Location&) : std::string
|
||||||
}
|
}
|
||||||
|
|
||||||
class WikipediaEnrichmentService {
|
class WikipediaEnrichmentService {
|
||||||
- client_ : std::unique_ptr<WebClient>
|
- client_ : std::unique_ptr<WebClient>
|
||||||
- extract_cache_ : std::unordered_map<std::string, std::string>
|
- extract_cache_ : std::unordered_map<std::string, std::string>
|
||||||
+ GetLocationContext(loc : const City&) : std::string
|
+ GetLocationContext(loc : const Location&) : std::string
|
||||||
- FetchExtract(query : std::string_view) : std::string
|
- FetchExtract(query : std::string_view) : std::string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,15 +112,14 @@ class HttpWebClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface DataGenerator <<interface>> {
|
interface DataGenerator <<interface>> {
|
||||||
+ GenerateBrewery(location : const City&, region_context : const std::string&) : BreweryResult
|
+ GenerateBrewery(location : const Location&, region_context : const std::string&) : BreweryResult
|
||||||
+ GenerateUser(city : const EnrichedCity&, persona : const UserPersona&, name : const Name&) : UserResult
|
+ GenerateUser(locale : const std::string&) : UserResult
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockGenerator {
|
class MockGenerator {
|
||||||
+ GenerateBrewery(location : const City&, region_context : const std::string&) : BreweryResult
|
+ GenerateBrewery(...) : BreweryResult
|
||||||
+ GenerateUser(city : const EnrichedCity&, persona : const UserPersona&, name : const Name&) : UserResult
|
+ GenerateUser(...) : UserResult
|
||||||
- DeterministicHash(location : const City&) : size_t
|
- DeterministicHash(location : const Location&) : size_t
|
||||||
- DeterministicHash(location : const City&, persona : const UserPersona&, name : const Name&) : size_t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LlamaGenerator {
|
class LlamaGenerator {
|
||||||
@@ -158,70 +153,13 @@ class PromptDirectory {
|
|||||||
+ Load(key : std::string_view) : std::string
|
+ Load(key : std::string_view) : std::string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICuratedDataService <<interface>> {
|
|
||||||
+ LoadCities(filepath : const std::filesystem::path&) : const std::vector<City>&
|
|
||||||
+ LoadPersonas(filepath : const std::filesystem::path&) : const std::vector<UserPersona>&
|
|
||||||
+ LoadForenamesByCountry(filepath : const std::filesystem::path&) : const std::unordered_map<std::string, forename_list>&
|
|
||||||
+ LoadSurnamesByCountry(filepath : const std::filesystem::path&) : const std::unordered_map<std::string, surname_list>&
|
|
||||||
}
|
|
||||||
|
|
||||||
class JsonLoader {
|
class JsonLoader {
|
||||||
- cache_ : cache
|
+ {static} LoadLocations(filepath : const std::filesystem::path&) : std::vector<Location>
|
||||||
+ LoadCities(filepath : const std::filesystem::path&) : const std::vector<City>&
|
|
||||||
+ LoadPersonas(filepath : const std::filesystem::path&) : const std::vector<UserPersona>&
|
|
||||||
+ LoadForenamesByCountry(filepath : const std::filesystem::path&) : const std::unordered_map<std::string, forename_list>&
|
|
||||||
+ LoadSurnamesByCountry(filepath : const std::filesystem::path&) : const std::unordered_map<std::string, surname_list>&
|
|
||||||
}
|
}
|
||||||
|
|
||||||
note right of JsonLoader
|
|
||||||
Each Load* method memoizes its result in
|
|
||||||
cache_ on first call; later calls on the
|
|
||||||
same instance return the cached value
|
|
||||||
without re-parsing.
|
|
||||||
end note
|
|
||||||
|
|
||||||
class MockCuratedDataService {
|
|
||||||
- locations_ : std::vector<City>
|
|
||||||
- personas_ : std::vector<UserPersona>
|
|
||||||
- forenames_by_country_ : std::unordered_map<std::string, forename_list>
|
|
||||||
- surnames_by_country_ : std::unordered_map<std::string, surname_list>
|
|
||||||
+ LoadCities(filepath : const std::filesystem::path&) : const std::vector<City>&
|
|
||||||
+ LoadPersonas(filepath : const std::filesystem::path&) : const std::vector<UserPersona>&
|
|
||||||
+ LoadForenamesByCountry(filepath : const std::filesystem::path&) : const std::unordered_map<std::string, forename_list>&
|
|
||||||
+ LoadSurnamesByCountry(filepath : const std::filesystem::path&) : const std::unordered_map<std::string, surname_list>&
|
|
||||||
}
|
|
||||||
|
|
||||||
note right of MockCuratedDataService
|
|
||||||
Fixed 4-location / 3-persona dataset
|
|
||||||
(US, DE, FR, BE) used in --mocked runs;
|
|
||||||
filepath arguments are ignored.
|
|
||||||
end note
|
|
||||||
|
|
||||||
interface IPostalCodeService <<interface>> {
|
|
||||||
+ GeneratePostalCode(location : const City&) : std::string
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockPostalCodeService {
|
|
||||||
+ GeneratePostalCode(location : const City&) : std::string
|
|
||||||
}
|
|
||||||
|
|
||||||
note right of MockPostalCodeService
|
|
||||||
Ignores location.postal_regex and returns
|
|
||||||
location.postal_code_examples.front(). Bound
|
|
||||||
unconditionally in main.cc's DI graph (no
|
|
||||||
--mocked branch yet -- no real implementation
|
|
||||||
exists to switch to) and called from
|
|
||||||
GenerateBreweries() per brewery, stored on
|
|
||||||
BreweryRecord::address. A real implementation
|
|
||||||
needs a xeger-style generator to turn
|
|
||||||
postal_regex into a synthesized matching
|
|
||||||
string. See ROADMAP.md §9.
|
|
||||||
end note
|
|
||||||
|
|
||||||
interface IExportService <<interface>> {
|
interface IExportService <<interface>> {
|
||||||
+ Initialize() : void
|
+ Initialize() : void
|
||||||
+ ProcessRecord(brewery : const BreweryRecord&) : uint64_t
|
+ ProcessRecord(brewery : const GeneratedBrewery&) : void
|
||||||
+ ProcessRecord(user : const UserRecord&) : uint64_t
|
|
||||||
+ Finalize() : void
|
+ Finalize() : void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,18 +167,15 @@ class SqliteExportService {
|
|||||||
- date_time_provider_ : std::unique_ptr<IDateTimeProvider>
|
- date_time_provider_ : std::unique_ptr<IDateTimeProvider>
|
||||||
- run_timestamp_utc_ : std::string
|
- run_timestamp_utc_ : std::string
|
||||||
- database_path_ : std::filesystem::path
|
- database_path_ : std::filesystem::path
|
||||||
- db_handle_ : SqliteDatabaseHandle
|
- db_handle_ : sqlite3*
|
||||||
- insert_city_stmt_ : SqliteStatementHandle
|
- insert_location_stmt_ : sqlite3_stmt*
|
||||||
- insert_brewery_stmt_ : SqliteStatementHandle
|
- insert_brewery_stmt_ : sqlite3_stmt*
|
||||||
- insert_user_stmt_ : SqliteStatementHandle
|
|
||||||
- transaction_open_ : bool
|
- transaction_open_ : bool
|
||||||
- city_cache_ : std::unordered_map<std::string, sqlite3_int64>
|
- location_cache_ : std::unordered_map<std::string, sqlite3_int64>
|
||||||
+ Initialize() : void
|
+ Initialize() : void
|
||||||
+ ProcessRecord(brewery : const BreweryRecord&) : uint64_t
|
+ ProcessRecord(brewery : const GeneratedBrewery&) : void
|
||||||
+ ProcessRecord(user : const UserRecord&) : uint64_t
|
|
||||||
+ Finalize() : void
|
+ Finalize() : void
|
||||||
- InitializeSchema() : void
|
- InitializeSchema() : void
|
||||||
- ResolveCityId(location : const City&) : sqlite3_int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IDateTimeProvider <<interface>> {
|
interface IDateTimeProvider <<interface>> {
|
||||||
@@ -256,8 +191,6 @@ BiergartenPipelineOrchestrator *-- ILogger : owns
|
|||||||
BiergartenPipelineOrchestrator *-- IEnrichmentService : owns
|
BiergartenPipelineOrchestrator *-- IEnrichmentService : owns
|
||||||
BiergartenPipelineOrchestrator *-- DataGenerator : owns
|
BiergartenPipelineOrchestrator *-- DataGenerator : owns
|
||||||
BiergartenPipelineOrchestrator *-- IExportService : owns
|
BiergartenPipelineOrchestrator *-- IExportService : owns
|
||||||
BiergartenPipelineOrchestrator *-- ICuratedDataService : owns
|
|
||||||
BiergartenPipelineOrchestrator *-- IPostalCodeService : owns
|
|
||||||
|
|
||||||
LogEntry *-- LogLevel
|
LogEntry *-- LogLevel
|
||||||
LogEntry *-- PipelinePhase
|
LogEntry *-- PipelinePhase
|
||||||
@@ -282,10 +215,7 @@ LlamaGenerator *-- IPromptDirectory : uses
|
|||||||
IPromptFormatter <|.. Gemma4JinjaPromptFormatter : implements
|
IPromptFormatter <|.. Gemma4JinjaPromptFormatter : implements
|
||||||
IPromptDirectory <|.. PromptDirectory : implements
|
IPromptDirectory <|.. PromptDirectory : implements
|
||||||
|
|
||||||
ICuratedDataService <|.. JsonLoader : implements
|
BiergartenPipelineOrchestrator ..> JsonLoader : uses
|
||||||
ICuratedDataService <|.. MockCuratedDataService : implements
|
|
||||||
|
|
||||||
IPostalCodeService <|.. MockPostalCodeService : implements
|
|
||||||
|
|
||||||
IExportService <|.. SqliteExportService : implements
|
IExportService <|.. SqliteExportService : implements
|
||||||
SqliteExportService *-- IDateTimeProvider : owns
|
SqliteExportService *-- IDateTimeProvider : owns
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 71 KiB |
@@ -39,22 +39,14 @@ fork
|
|||||||
:JsonLoader::LoadBeerStyles("beer-styles.json");
|
:JsonLoader::LoadBeerStyles("beer-styles.json");
|
||||||
:EnrichmentService::PreWarmBeerStyleCache(beer_styles);
|
:EnrichmentService::PreWarmBeerStyleCache(beer_styles);
|
||||||
fork again
|
fork again
|
||||||
:JsonLoader::LoadCities("cities.json");
|
:JsonLoader::LoadLocations("locations.json");
|
||||||
:EnrichmentService::PreWarmLocationCache(sampled_locations);
|
:EnrichmentService::PreWarmLocationCache(sampled_locations);
|
||||||
end fork
|
end fork
|
||||||
fork
|
fork
|
||||||
:JsonLoader::LoadForenamesByCountry(\n "forenames-by-country.json");
|
:JsonLoader::LoadNamesByCountry(\n "forenames-by-country.json",\n "surnames-by-country.json");
|
||||||
fork again
|
|
||||||
:JsonLoader::LoadSurnamesByCountry(\n "surnames-by-country.json");
|
|
||||||
fork again
|
fork again
|
||||||
:JsonLoader::LoadPersonas("personas.json");
|
:JsonLoader::LoadPersonas("personas.json");
|
||||||
end fork
|
end fork
|
||||||
note right
|
|
||||||
Each call is memoized after its first parse
|
|
||||||
(implemented today). MockCuratedDataService
|
|
||||||
returns a fixed in-memory dataset instead
|
|
||||||
under --mocked, skipping these JSON files.
|
|
||||||
end note
|
|
||||||
|
|
||||||
' ═══════════════════════════════════════════
|
' ═══════════════════════════════════════════
|
||||||
' PHASE 0 — USER GENERATION
|
' PHASE 0 — USER GENERATION
|
||||||
@@ -75,7 +67,7 @@ fork
|
|||||||
fork again
|
fork again
|
||||||
|LLM Worker|
|
|LLM Worker|
|
||||||
while (loc_ch has items?) is (yes)
|
while (loc_ch has items?) is (yes)
|
||||||
:Receive City;
|
:Receive Location;
|
||||||
|
|
||||||
:GetLocationContextFromCache(location);
|
:GetLocationContextFromCache(location);
|
||||||
note right
|
note right
|
||||||
@@ -90,15 +82,11 @@ fork again
|
|||||||
ibu_preference, checkin_weight.
|
ibu_preference, checkin_weight.
|
||||||
end note
|
end note
|
||||||
|
|
||||||
:SampleName(forenames_by_country,\n surnames_by_country, location.iso3166_1, rng);
|
:NamesByCountry::SampleName(\n location.iso3166_1);
|
||||||
note right
|
note right
|
||||||
Deterministic lookup -- no LLM involved.
|
Deterministic lookup -- no LLM involved.
|
||||||
Free function (implemented today in
|
Name selected from pre-keyed table
|
||||||
generate_users.cc), not a class method.
|
|
||||||
Name selected from pre-keyed maps
|
|
||||||
and passed into the generation prompt.
|
and passed into the generation prompt.
|
||||||
Skips the city if either map has no
|
|
||||||
entry for iso3166_1.
|
|
||||||
end note
|
end note
|
||||||
|
|
||||||
:GenerateUser(enriched_city, persona, sampled_name)\nvia DataGenerator;
|
:GenerateUser(enriched_city, persona, sampled_name)\nvia DataGenerator;
|
||||||
@@ -146,13 +134,13 @@ end fork
|
|||||||
fork
|
fork
|
||||||
|Orchestrator|
|
|Orchestrator|
|
||||||
:Loop: Sample User from user_pool_
|
:Loop: Sample User from user_pool_
|
||||||
and pair with City;
|
and pair with Location;
|
||||||
:Send BreweryTask(City, User) -> loc_ch;
|
:Send BreweryTask(Location, User) -> loc_ch;
|
||||||
:Close loc_ch;
|
:Close loc_ch;
|
||||||
fork again
|
fork again
|
||||||
|LLM Worker|
|
|LLM Worker|
|
||||||
while (loc_ch has items?) is (yes)
|
while (loc_ch has items?) is (yes)
|
||||||
:Receive BreweryTask(City, User);
|
:Receive BreweryTask(Location, User);
|
||||||
|
|
||||||
:GetLocationContextFromCache(task.location);
|
:GetLocationContextFromCache(task.location);
|
||||||
note right
|
note right
|
||||||
|
|||||||
@@ -12,40 +12,17 @@ title Biergarten Data Pipeline — Class Diagram
|
|||||||
|
|
||||||
package "Domain: Models" {
|
package "Domain: Models" {
|
||||||
|
|
||||||
class City {
|
class Location {
|
||||||
+ city : std::string
|
+ city : std::string
|
||||||
+ state_province : std::string
|
+ state_province : std::string
|
||||||
+ iso3166_2 : std::string
|
+ iso3166_2 : std::string
|
||||||
+ country : std::string
|
+ country : std::string
|
||||||
+ iso3166_1 : std::string
|
+ iso3166_1 : std::string
|
||||||
+ postal_regex : std::vector<std::string>
|
|
||||||
+ postal_code_examples : std::vector<std::string>
|
|
||||||
+ local_languages : std::vector<std::string>
|
+ local_languages : std::vector<std::string>
|
||||||
+ latitude : double
|
+ latitude : double
|
||||||
+ longitude : double
|
+ longitude : double
|
||||||
}
|
}
|
||||||
|
|
||||||
note right of City
|
|
||||||
postal_regex / postal_code_examples are
|
|
||||||
sourced from each cities.json entry's
|
|
||||||
postal_code.city_regex / postal_code.examples
|
|
||||||
(implemented today, ahead of the rest of this
|
|
||||||
diagram -- see ROADMAP.md §1/§2). Renamed from
|
|
||||||
Location to City to match the web backend's
|
|
||||||
City/CityId vocabulary -- see ROADMAP.md §1.
|
|
||||||
end note
|
|
||||||
|
|
||||||
class Address {
|
|
||||||
+ postal_code : std::string
|
|
||||||
}
|
|
||||||
|
|
||||||
note right of Address
|
|
||||||
Mirrors the web backend's BreweryPostLocation.
|
|
||||||
Only postal_code is populated today --
|
|
||||||
address_line1/address_line2 have no fixture
|
|
||||||
data or generator yet. See ROADMAP.md §9.
|
|
||||||
end note
|
|
||||||
|
|
||||||
class LocationContext {
|
class LocationContext {
|
||||||
+ text : std::string
|
+ text : std::string
|
||||||
+ completeness : Completeness
|
+ completeness : Completeness
|
||||||
@@ -59,7 +36,7 @@ package "Domain: Models" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class EnrichedCity {
|
class EnrichedCity {
|
||||||
+ location : City
|
+ location : Location
|
||||||
+ context : LocationContext
|
+ context : LocationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,30 +86,27 @@ package "Domain: Models" {
|
|||||||
+ gender : std::string
|
+ gender : std::string
|
||||||
}
|
}
|
||||||
|
|
||||||
note right of ForenameEntry
|
class NamesByCountry {
|
||||||
forename_list = std::unordered_set<ForenameEntry>
|
- forenames_by_country_ : std::unordered_map<std::string, std::vector<ForenameEntry>>
|
||||||
surname_list = std::unordered_set<std::string>
|
- surnames_by_country_ : std::unordered_map<std::string, std::vector<std::string>>
|
||||||
(aliases declared in curated_data_service.h).
|
+ SampleName(iso3166_1 : const std::string&,\n rng : std::mt19937&) : std::optional<Name>
|
||||||
ICuratedDataService::LoadForenamesByCountry() /
|
}
|
||||||
LoadSurnamesByCountry() each return a flat
|
|
||||||
std::unordered_map<std::string, forename_list>
|
note right of NamesByCountry
|
||||||
or <std::string, surname_list> keyed by ISO
|
Backed by two source-shaped maps,
|
||||||
3166-1 country code -- no NamesByCountry
|
keyed by ISO 3166-1 country code,
|
||||||
wrapper class. SampleName(forenames_by_country,
|
sourced from popular-names-by-country-dataset
|
||||||
surnames_by_country, iso3166_1, rng) is a free
|
(CC0) -- see ETHICS-AND-KNOWN-ISSUES.md.
|
||||||
function, not a method, so it can be called by
|
Gender is preserved per forename so it can
|
||||||
any per-city worker without owning the maps.
|
be threaded through to persona/bio generation
|
||||||
Sourced from popular-names-by-country-dataset
|
later; surnames carry no gender in the source
|
||||||
(CC0) -- see ETHICS-AND-KNOWN-ISSUES.md. Gender
|
data. Pairing happens at sample time, not at
|
||||||
is preserved per forename so it can be threaded
|
fixture-authoring time, so no information from
|
||||||
through to persona/bio generation later; surnames
|
the source dataset is discarded up front.
|
||||||
carry no gender in the source data. Pairing
|
|
||||||
happens at sample time, not at fixture-authoring
|
|
||||||
time, so no information from the source dataset
|
|
||||||
is discarded up front.
|
|
||||||
end note
|
end note
|
||||||
|
|
||||||
ForenameEntry ..> Name : SampleName() produces
|
NamesByCountry *-- "0..*" ForenameEntry
|
||||||
|
NamesByCountry ..> Name : produces
|
||||||
|
|
||||||
class CheckinResult {
|
class CheckinResult {
|
||||||
+ checked_in_at : std::string
|
+ checked_in_at : std::string
|
||||||
@@ -153,8 +127,7 @@ package "Domain: Models" {
|
|||||||
|
|
||||||
class GeneratedBrewery {
|
class GeneratedBrewery {
|
||||||
+ brewery_id : uint64_t
|
+ brewery_id : uint64_t
|
||||||
+ location : City
|
+ location : Location
|
||||||
+ address : Address
|
|
||||||
+ brewery : BreweryResult
|
+ brewery : BreweryResult
|
||||||
+ context_completeness : LocationContext::Completeness
|
+ context_completeness : LocationContext::Completeness
|
||||||
+ metadata : GenerationMetadata
|
+ metadata : GenerationMetadata
|
||||||
@@ -163,7 +136,7 @@ package "Domain: Models" {
|
|||||||
class GeneratedBeer {
|
class GeneratedBeer {
|
||||||
+ beer_id : uint64_t
|
+ beer_id : uint64_t
|
||||||
+ brewery_id : uint64_t
|
+ brewery_id : uint64_t
|
||||||
+ location : City
|
+ location : Location
|
||||||
+ style : BeerStyle
|
+ style : BeerStyle
|
||||||
+ beer : BeerResult
|
+ beer : BeerResult
|
||||||
+ metadata : GenerationMetadata
|
+ metadata : GenerationMetadata
|
||||||
@@ -171,7 +144,7 @@ package "Domain: Models" {
|
|||||||
|
|
||||||
class GeneratedUser {
|
class GeneratedUser {
|
||||||
+ user_id : uint64_t
|
+ user_id : uint64_t
|
||||||
+ location : City
|
+ location : Location
|
||||||
+ user : UserResult
|
+ user : UserResult
|
||||||
+ email : std::string
|
+ email : std::string
|
||||||
+ date_of_birth : std::string
|
+ date_of_birth : std::string
|
||||||
@@ -251,27 +224,27 @@ package "Domain: Application Configuration" {
|
|||||||
package "Domain: Policy" {
|
package "Domain: Policy" {
|
||||||
|
|
||||||
interface ContextStrategy <<interface>> {
|
interface ContextStrategy <<interface>> {
|
||||||
+ QueriesFor(loc : const City&) : std::vector<std::string>
|
+ QueriesFor(loc : const Location&) : std::vector<std::string>
|
||||||
+ MaxContextChars() : size_t
|
+ MaxContextChars() : size_t
|
||||||
}
|
}
|
||||||
|
|
||||||
class BreweryContextStrategy {
|
class BreweryContextStrategy {
|
||||||
+ QueriesFor(loc : const City&) : std::vector<std::string>
|
+ QueriesFor(loc : const Location&) : std::vector<std::string>
|
||||||
+ MaxContextChars() : size_t
|
+ MaxContextChars() : size_t
|
||||||
}
|
}
|
||||||
|
|
||||||
class BeerContextStrategy {
|
class BeerContextStrategy {
|
||||||
+ QueriesFor(loc : const City&) : std::vector<std::string>
|
+ QueriesFor(loc : const Location&) : std::vector<std::string>
|
||||||
+ MaxContextChars() : size_t
|
+ MaxContextChars() : size_t
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SamplingStrategy <<interface>> {
|
interface SamplingStrategy <<interface>> {
|
||||||
+ Sample(locations : const std::vector<City>&) : std::vector<City>
|
+ Sample(locations : const std::vector<Location>&) : std::vector<Location>
|
||||||
}
|
}
|
||||||
|
|
||||||
class UniformSamplingStrategy {
|
class UniformSamplingStrategy {
|
||||||
- sample_size_ : size_t
|
- sample_size_ : size_t
|
||||||
+ Sample(locations : const std::vector<City>&) : std::vector<City>
|
+ Sample(locations : const std::vector<Location>&) : std::vector<Location>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BeerSelectionStrategy <<interface>> {
|
interface BeerSelectionStrategy <<interface>> {
|
||||||
@@ -392,55 +365,32 @@ package "Infrastructure: Pipeline Channel" {
|
|||||||
|
|
||||||
package "Infrastructure: Data Preloading" {
|
package "Infrastructure: Data Preloading" {
|
||||||
|
|
||||||
interface ICuratedDataService <<interface>> {
|
interface DataPreloader <<interface>> {
|
||||||
+ LoadCities(filepath : const std::filesystem::path&) : const std::vector<City>&
|
+ LoadLocations(filepath : const std::filesystem::path&) : std::vector<Location>
|
||||||
+ LoadBeerStyles(filepath : const std::filesystem::path&) : const std::vector<BeerStyle>&
|
+ LoadBeerStyles(filepath : const std::filesystem::path&) : std::vector<BeerStyle>
|
||||||
+ LoadPersonas(filepath : const std::filesystem::path&) : const std::vector<UserPersona>&
|
+ LoadPersonas(filepath : const std::filesystem::path&) : std::vector<UserPersona>
|
||||||
+ LoadForenamesByCountry(forenames_filepath : const std::filesystem::path&) : const std::unordered_map<std::string, forename_list>&
|
+ LoadNamesByCountry(forenames_filepath : const std::filesystem::path&,\n surnames_filepath : const std::filesystem::path&) : NamesByCountry
|
||||||
+ LoadSurnamesByCountry(surnames_filepath : const std::filesystem::path&) : const std::unordered_map<std::string, surname_list>&
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class JsonLoader {
|
class JsonLoader {
|
||||||
+ LoadCities(filepath : const std::filesystem::path&) : const std::vector<City>&
|
+ LoadLocations(filepath : const std::filesystem::path&) : std::vector<Location>
|
||||||
+ LoadBeerStyles(filepath : const std::filesystem::path&) : const std::vector<BeerStyle>&
|
+ LoadBeerStyles(filepath : const std::filesystem::path&) : std::vector<BeerStyle>
|
||||||
+ LoadPersonas(filepath : const std::filesystem::path&) : const std::vector<UserPersona>&
|
+ LoadPersonas(filepath : const std::filesystem::path&) : std::vector<UserPersona>
|
||||||
+ LoadForenamesByCountry(forenames_filepath : const std::filesystem::path&) : const std::unordered_map<std::string, forename_list>&
|
+ LoadNamesByCountry(forenames_filepath : const std::filesystem::path&,\n surnames_filepath : const std::filesystem::path&) : NamesByCountry
|
||||||
+ LoadSurnamesByCountry(surnames_filepath : const std::filesystem::path&) : const std::unordered_map<std::string, surname_list>&
|
|
||||||
}
|
}
|
||||||
|
|
||||||
note right of JsonLoader
|
|
||||||
Each Load* call is memoized after its first
|
|
||||||
parse -- implemented today, ahead of the rest
|
|
||||||
of this package (LoadBeerStyles is still planned).
|
|
||||||
end note
|
|
||||||
|
|
||||||
class MockCuratedDataService {
|
|
||||||
+ LoadCities(filepath : const std::filesystem::path&) : const std::vector<City>&
|
|
||||||
+ LoadBeerStyles(filepath : const std::filesystem::path&) : const std::vector<BeerStyle>&
|
|
||||||
+ LoadPersonas(filepath : const std::filesystem::path&) : const std::vector<UserPersona>&
|
|
||||||
+ LoadForenamesByCountry(forenames_filepath : const std::filesystem::path&) : const std::unordered_map<std::string, forename_list>&
|
|
||||||
+ LoadSurnamesByCountry(surnames_filepath : const std::filesystem::path&) : const std::unordered_map<std::string, surname_list>&
|
|
||||||
}
|
|
||||||
|
|
||||||
note right of MockCuratedDataService
|
|
||||||
Fixed in-memory dataset used in --mocked mode,
|
|
||||||
implemented today for Locations/Personas/
|
|
||||||
Forenames/Surnames; would need a LoadBeerStyles
|
|
||||||
fixture too once that method is implemented.
|
|
||||||
end note
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
package "Infrastructure: Enrichment" {
|
package "Infrastructure: Enrichment" {
|
||||||
|
|
||||||
interface EnrichmentService <<interface>> {
|
interface EnrichmentService <<interface>> {
|
||||||
+ GetLocationContext(loc : const City&,\n strategy : const ContextStrategy&) : LocationContext
|
+ GetLocationContext(loc : const Location&,\n strategy : const ContextStrategy&) : LocationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
class WikipediaService {
|
class WikipediaService {
|
||||||
- client_ : std::unique_ptr<WebClient>
|
- client_ : std::unique_ptr<WebClient>
|
||||||
- extract_cache_ : std::unordered_map<std::string, std::string>
|
- extract_cache_ : std::unordered_map<std::string, std::string>
|
||||||
+ GetLocationContext(loc : const City&,\n strategy : const ContextStrategy&) : LocationContext
|
+ GetLocationContext(loc : const Location&,\n strategy : const ContextStrategy&) : LocationContext
|
||||||
- FetchExtract(query : std::string_view) : std::string
|
- FetchExtract(query : std::string_view) : std::string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,39 +406,6 @@ package "Infrastructure: Enrichment" {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
package "Infrastructure: Postal Code Generation" {
|
|
||||||
|
|
||||||
interface IPostalCodeService <<interface>> {
|
|
||||||
+ GeneratePostalCode(location : const City&) : std::string
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockPostalCodeService {
|
|
||||||
+ GeneratePostalCode(location : const City&) : std::string
|
|
||||||
}
|
|
||||||
|
|
||||||
class XegerPostalCodeService {
|
|
||||||
+ GeneratePostalCode(location : const City&) : std::string
|
|
||||||
}
|
|
||||||
|
|
||||||
note right of MockPostalCodeService
|
|
||||||
Implemented today: ignores postal_regex and
|
|
||||||
returns postal_code_examples.front(). Not yet
|
|
||||||
bound in main.cc's DI graph or used by
|
|
||||||
BiergartenPipelineOrchestrator.
|
|
||||||
end note
|
|
||||||
|
|
||||||
note right of XegerPostalCodeService
|
|
||||||
Not implemented yet. Picks one of
|
|
||||||
location.postal_regex and generates a random
|
|
||||||
string matching it (a "xeger" -- the inverse of
|
|
||||||
regex matching) instead of always replaying a
|
|
||||||
fixed example. See ROADMAP.md §9.
|
|
||||||
end note
|
|
||||||
|
|
||||||
IPostalCodeService <|.. MockPostalCodeService
|
|
||||||
IPostalCodeService <|.. XegerPostalCodeService
|
|
||||||
}
|
|
||||||
|
|
||||||
package "Infrastructure: Prompting" {
|
package "Infrastructure: Prompting" {
|
||||||
|
|
||||||
interface IPromptDirectory <<interface>> {
|
interface IPromptDirectory <<interface>> {
|
||||||
@@ -508,8 +425,8 @@ package "Infrastructure: Prompting" {
|
|||||||
package "Infrastructure: Data Generation" {
|
package "Infrastructure: Data Generation" {
|
||||||
|
|
||||||
interface DataGenerator <<interface>> {
|
interface DataGenerator <<interface>> {
|
||||||
+ GenerateBrewery(location : const City&,\n context : const LocationContext&) : BreweryResult
|
+ GenerateBrewery(location : const Location&,\n context : const LocationContext&) : BreweryResult
|
||||||
+ GenerateBeer(brewery_id : uint64_t,\n location : const City&,\n context : const LocationContext&,\n style : const BeerStyle&) : BeerResult
|
+ GenerateBeer(brewery_id : uint64_t,\n location : const Location&,\n context : const LocationContext&,\n style : const BeerStyle&) : BeerResult
|
||||||
+ GenerateUser(city : const EnrichedCity&,\n persona : const UserPersona&,\n name : const Name&) : UserResult
|
+ GenerateUser(city : const EnrichedCity&,\n persona : const UserPersona&,\n name : const Name&) : UserResult
|
||||||
+ GenerateCheckin(user : const GeneratedUser&,\n brewery : const GeneratedBrewery&,\n timestamp : const std::string&) : CheckinResult
|
+ 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 : uint64_t) : RatingResult
|
+ GenerateRating(user : const GeneratedUser&,\n beer : const GeneratedBeer&,\n checkin_id : uint64_t) : RatingResult
|
||||||
@@ -521,7 +438,7 @@ package "Infrastructure: Data Generation" {
|
|||||||
+ GenerateUser(...) : UserResult
|
+ GenerateUser(...) : UserResult
|
||||||
+ GenerateCheckin(...) : CheckinResult
|
+ GenerateCheckin(...) : CheckinResult
|
||||||
+ GenerateRating(...) : RatingResult
|
+ GenerateRating(...) : RatingResult
|
||||||
- DeterministicHash(location : const City&) : size_t
|
- DeterministicHash(location : const Location&) : size_t
|
||||||
}
|
}
|
||||||
|
|
||||||
class LlamaGenerator {
|
class LlamaGenerator {
|
||||||
@@ -603,7 +520,7 @@ package "Infrastructure: Data Export" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BiergartenPipelineOrchestrator {
|
class BiergartenPipelineOrchestrator {
|
||||||
- curated_data_service_ : std::unique_ptr<ICuratedDataService>
|
- preloader_ : std::unique_ptr<DataPreloader>
|
||||||
- enrichment_service_ : std::unique_ptr<EnrichmentService>
|
- enrichment_service_ : std::unique_ptr<EnrichmentService>
|
||||||
- generator_ : std::unique_ptr<DataGenerator>
|
- generator_ : std::unique_ptr<DataGenerator>
|
||||||
- logger_ : std::unique_ptr<Logger>
|
- logger_ : std::unique_ptr<Logger>
|
||||||
@@ -623,15 +540,15 @@ class BiergartenPipelineOrchestrator {
|
|||||||
- follow_pool_ : std::vector<GeneratedFollow>
|
- follow_pool_ : std::vector<GeneratedFollow>
|
||||||
--
|
--
|
||||||
+ Run() : bool
|
+ Run() : bool
|
||||||
- RunUserPhase(locations : const std::vector<City>&) : void
|
- RunUserPhase(locations : const std::vector<Location>&) : void
|
||||||
- RunBreweryAndBeerPhase(locations : const std::vector<City>&) : void
|
- RunBreweryAndBeerPhase(locations : const std::vector<Location>&) : void
|
||||||
- RunCheckinPhase() : void
|
- RunCheckinPhase() : void
|
||||||
- RunRatingPhase() : void
|
- RunRatingPhase() : void
|
||||||
- RunFollowPhase() : void
|
- RunFollowPhase() : void
|
||||||
}
|
}
|
||||||
|
|
||||||
' --- Orchestration Aggregations (Services & Strategies) ---
|
' --- Orchestration Aggregations (Services & Strategies) ---
|
||||||
BiergartenPipelineOrchestrator *-- ICuratedDataService
|
BiergartenPipelineOrchestrator *-- DataPreloader
|
||||||
BiergartenPipelineOrchestrator *-- EnrichmentService
|
BiergartenPipelineOrchestrator *-- EnrichmentService
|
||||||
BiergartenPipelineOrchestrator *-- DataGenerator
|
BiergartenPipelineOrchestrator *-- DataGenerator
|
||||||
BiergartenPipelineOrchestrator *-- ExportService
|
BiergartenPipelineOrchestrator *-- ExportService
|
||||||
@@ -650,8 +567,7 @@ BiergartenPipelineOrchestrator *-- "0..*" GeneratedCheckin : checkin_pool_
|
|||||||
BiergartenPipelineOrchestrator *-- "0..*" GeneratedFollow : follow_pool_
|
BiergartenPipelineOrchestrator *-- "0..*" GeneratedFollow : follow_pool_
|
||||||
|
|
||||||
' --- Interfaces & Implementations ---
|
' --- Interfaces & Implementations ---
|
||||||
ICuratedDataService <|.. JsonLoader
|
DataPreloader <|.. JsonLoader
|
||||||
ICuratedDataService <|.. MockCuratedDataService
|
|
||||||
Logger <|.. PipelineLogger
|
Logger <|.. PipelineLogger
|
||||||
ContextStrategy <|.. BreweryContextStrategy
|
ContextStrategy <|.. BreweryContextStrategy
|
||||||
ContextStrategy <|.. BeerContextStrategy
|
ContextStrategy <|.. BeerContextStrategy
|
||||||
@@ -682,17 +598,16 @@ PipelineLogger o-- BoundedChannel : logs to
|
|||||||
LogWorker o-- BoundedChannel : drains from
|
LogWorker o-- BoundedChannel : drains from
|
||||||
|
|
||||||
' --- Domain Containment ---
|
' --- Domain Containment ---
|
||||||
EnrichedCity *-- City
|
EnrichedCity *-- Location
|
||||||
EnrichedCity *-- LocationContext
|
EnrichedCity *-- LocationContext
|
||||||
GeneratedBrewery *-- City
|
GeneratedBrewery *-- Location
|
||||||
GeneratedBrewery *-- Address
|
|
||||||
GeneratedBrewery *-- BreweryResult
|
GeneratedBrewery *-- BreweryResult
|
||||||
GeneratedBrewery *-- GenerationMetadata
|
GeneratedBrewery *-- GenerationMetadata
|
||||||
GeneratedBeer *-- City
|
GeneratedBeer *-- Location
|
||||||
GeneratedBeer *-- BeerStyle
|
GeneratedBeer *-- BeerStyle
|
||||||
GeneratedBeer *-- BeerResult
|
GeneratedBeer *-- BeerResult
|
||||||
GeneratedBeer *-- GenerationMetadata
|
GeneratedBeer *-- GenerationMetadata
|
||||||
GeneratedUser *-- City
|
GeneratedUser *-- Location
|
||||||
GeneratedUser *-- UserResult
|
GeneratedUser *-- UserResult
|
||||||
GeneratedUser *-- GenerationMetadata
|
GeneratedUser *-- GenerationMetadata
|
||||||
GeneratedCheckin *-- CheckinResult
|
GeneratedCheckin *-- CheckinResult
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 234 KiB |
@@ -139,9 +139,6 @@ FetchContent_MakeAvailable(cpp-httplib)
|
|||||||
# 5. Executable & Sources
|
# 5. Executable & Sources
|
||||||
add_executable(${PROJECT_NAME}
|
add_executable(${PROJECT_NAME}
|
||||||
includes/services/enrichment/mock_enrichment.h
|
includes/services/enrichment/mock_enrichment.h
|
||||||
includes/services/curated_data/mock_curated_data_service.h
|
|
||||||
includes/services/postal_code/postal_code_service.h
|
|
||||||
includes/services/postal_code/mock_postal_code_service.h
|
|
||||||
includes/json_handling/pretty_print.h)
|
includes/json_handling/pretty_print.h)
|
||||||
|
|
||||||
# --- Entry point ---
|
# --- Entry point ---
|
||||||
@@ -151,12 +148,12 @@ target_sources(${PROJECT_NAME} PRIVATE
|
|||||||
|
|
||||||
# --- json_handling ---
|
# --- json_handling ---
|
||||||
target_sources(${PROJECT_NAME} PRIVATE
|
target_sources(${PROJECT_NAME} PRIVATE
|
||||||
src/services/curated_data/curated_json_data_service.cc
|
src/json_handling/json_loader.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- services: curated_data ---
|
# --- data_model ---
|
||||||
target_sources(${PROJECT_NAME} PRIVATE
|
target_sources(${PROJECT_NAME} PRIVATE
|
||||||
src/services/curated_data/mock_curated_data_service.cc
|
src/data_model/names_by_country.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- application_options ---
|
# --- application_options ---
|
||||||
@@ -266,8 +263,8 @@ target_compile_options(biergarten-pipeline PRIVATE
|
|||||||
|
|
||||||
# 7. Runtime Assets
|
# 7. Runtime Assets
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_SOURCE_DIR}/cities.json
|
${CMAKE_SOURCE_DIR}/locations.json
|
||||||
${CMAKE_BINARY_DIR}/cities.json
|
${CMAKE_BINARY_DIR}/locations.json
|
||||||
COPYONLY
|
COPYONLY
|
||||||
)
|
)
|
||||||
configure_file(
|
configure_file(
|
||||||
@@ -290,3 +287,4 @@ add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
|||||||
${CMAKE_SOURCE_DIR}/prompts
|
${CMAKE_SOURCE_DIR}/prompts
|
||||||
${CMAKE_BINARY_DIR}/prompts
|
${CMAKE_BINARY_DIR}/prompts
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_PIPELINE_H_
|
|
||||||
#define BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_PIPELINE_H_
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file biergarten_pipeline.h
|
|
||||||
* @brief Master umbrella header — includes every public header in the
|
|
||||||
* Biergarten pipeline.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// --- orchestrator ---
|
|
||||||
#include "biergarten_pipeline_orchestrator.h"
|
|
||||||
|
|
||||||
// --- concurrency ---
|
|
||||||
#include "concurrency/bounded_channel.h"
|
|
||||||
|
|
||||||
// --- data_generation ---
|
|
||||||
#include "data_generation/data_generator.h"
|
|
||||||
#include "data_generation/json_grammars.h"
|
|
||||||
#ifndef BIERGARTEN_MOCK_ONLY
|
|
||||||
#include "data_generation/llama_generator.h"
|
|
||||||
#include "data_generation/llama_generator_helpers.h"
|
|
||||||
#endif
|
|
||||||
#include "data_generation/mock_generator.h"
|
|
||||||
#include "data_generation/prompt_formatting/gemma4_jinja_prompt_formatter.h"
|
|
||||||
#include "data_generation/prompt_formatting/prompt_formatter.h"
|
|
||||||
|
|
||||||
// --- data_model ---
|
|
||||||
#include "data_model/generated_models.h"
|
|
||||||
#include "data_model/models.h"
|
|
||||||
|
|
||||||
// --- json_handling ---
|
|
||||||
#include "json_handling/pretty_print.h"
|
|
||||||
#include "services/curated_data/curated_json_data_service.h"
|
|
||||||
|
|
||||||
// --- llama backend ---
|
|
||||||
#ifndef BIERGARTEN_MOCK_ONLY
|
|
||||||
#include "llama_backend_state.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// --- services: curated_data ---
|
|
||||||
#include "services/curated_data/curated_data_service.h"
|
|
||||||
#include "services/curated_data/mock_curated_data_service.h"
|
|
||||||
|
|
||||||
// --- services: database ---
|
|
||||||
#include "services/database/export_service.h"
|
|
||||||
#include "services/database/sqlite_connection_helpers.h"
|
|
||||||
#include "services/database/sqlite_export_service.h"
|
|
||||||
#include "services/database/sqlite_export_service_helpers.h"
|
|
||||||
#include "services/database/sqlite_handle_types.h"
|
|
||||||
#include "services/database/sqlite_statement_helpers.h"
|
|
||||||
|
|
||||||
// --- services: datetime ---
|
|
||||||
#include "services/datetime/date_time_provider.h"
|
|
||||||
#include "services/datetime/timer.h"
|
|
||||||
|
|
||||||
// --- services: enrichment ---
|
|
||||||
#include "services/enrichment/enrichment_service.h"
|
|
||||||
#include "services/enrichment/mock_enrichment.h"
|
|
||||||
#include "services/enrichment/wikipedia_service.h"
|
|
||||||
|
|
||||||
// --- services: logging ---
|
|
||||||
#include "services/logging/log_dispatcher.h"
|
|
||||||
#include "services/logging/log_entry.h"
|
|
||||||
#include "services/logging/log_producer.h"
|
|
||||||
#include "services/logging/logger.h"
|
|
||||||
|
|
||||||
// --- services: postal_code ---
|
|
||||||
#include "services/postal_code/mock_postal_code_service.h"
|
|
||||||
#include "services/postal_code/postal_code_service.h"
|
|
||||||
|
|
||||||
// --- services: prompting ---
|
|
||||||
#include "services/prompting/prompt_directory.h"
|
|
||||||
|
|
||||||
// --- web_client ---
|
|
||||||
#include "web_client/http_web_client.h"
|
|
||||||
#include "web_client/web_client.h"
|
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_PIPELINE_H_
|
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_PIPELINE_ORCHESTRATOR_H_
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_DATA_GENERATOR_H_
|
||||||
#define BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_PIPELINE_ORCHESTRATOR_H_
|
#define BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_DATA_GENERATOR_H_
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file biergarten_pipeline_orchestrator.h
|
* @file biergarten_pipeline_orchestrator.h
|
||||||
* @brief Orchestration for end-to-end brewery and user data generation.
|
* @brief Orchestration for end-to-end brewery and user data generation.
|
||||||
|
*
|
||||||
|
* Coordinates location loading, enrichment, and generation phases to produce
|
||||||
|
* a complete dataset, wiring dependencies selected at the composition root.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -16,7 +19,7 @@
|
|||||||
#include "services/database/export_service.h"
|
#include "services/database/export_service.h"
|
||||||
#include "services/enrichment/enrichment_service.h"
|
#include "services/enrichment/enrichment_service.h"
|
||||||
#include "services/logging/logger.h"
|
#include "services/logging/logger.h"
|
||||||
#include "services/postal_code/postal_code_service.h"
|
|
||||||
/**
|
/**
|
||||||
* @brief Main orchestrator for the Biergarten data generation pipeline.
|
* @brief Main orchestrator for the Biergarten data generation pipeline.
|
||||||
*
|
*
|
||||||
@@ -34,7 +37,6 @@ class BiergartenPipelineOrchestrator {
|
|||||||
* @param exporter Database backend for persisting generated records.
|
* @param exporter Database backend for persisting generated records.
|
||||||
* @param curated_data_service Loads curated location, persona, and name
|
* @param curated_data_service Loads curated location, persona, and name
|
||||||
* data used to seed generation.
|
* data used to seed generation.
|
||||||
* @param postal_code_service
|
|
||||||
* @param application_options CLI configuration and paths.
|
* @param application_options CLI configuration and paths.
|
||||||
*/
|
*/
|
||||||
BiergartenPipelineOrchestrator(
|
BiergartenPipelineOrchestrator(
|
||||||
@@ -43,7 +45,6 @@ class BiergartenPipelineOrchestrator {
|
|||||||
std::unique_ptr<DataGenerator> generator,
|
std::unique_ptr<DataGenerator> generator,
|
||||||
std::unique_ptr<IExportService> exporter,
|
std::unique_ptr<IExportService> exporter,
|
||||||
std::unique_ptr<ICuratedDataService> curated_data_service,
|
std::unique_ptr<ICuratedDataService> curated_data_service,
|
||||||
std::unique_ptr<IPostalCodeService> postal_code_service,
|
|
||||||
const ApplicationOptions& application_options);
|
const ApplicationOptions& application_options);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,7 +81,6 @@ class BiergartenPipelineOrchestrator {
|
|||||||
*/
|
*/
|
||||||
std::unique_ptr<IExportService> exporter_;
|
std::unique_ptr<IExportService> exporter_;
|
||||||
std::unique_ptr<ICuratedDataService> curated_data_service_;
|
std::unique_ptr<ICuratedDataService> curated_data_service_;
|
||||||
std::unique_ptr<IPostalCodeService> postal_code_service_;
|
|
||||||
|
|
||||||
ApplicationOptions application_options_;
|
ApplicationOptions application_options_;
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class BiergartenPipelineOrchestrator {
|
|||||||
* @return Vector of locations randomly sampled per
|
* @return Vector of locations randomly sampled per
|
||||||
* PipelineOptions::location_count.
|
* PipelineOptions::location_count.
|
||||||
*/
|
*/
|
||||||
std::vector<City> QueryCitiesWithCountries();
|
std::vector<Location> QueryCitiesWithCountries();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generate breweries for enriched cities.
|
* @brief Generate breweries for enriched cities.
|
||||||
@@ -103,6 +103,10 @@ class BiergartenPipelineOrchestrator {
|
|||||||
* @brief Generate users grounded in sampled names and personas for
|
* @brief Generate users grounded in sampled names and personas for
|
||||||
* enriched cities.
|
* enriched cities.
|
||||||
*
|
*
|
||||||
|
* Loads personas.json / forenames-by-country.json /
|
||||||
|
* surnames-by-country.json itself, mirroring how QueryCitiesWithCountries()
|
||||||
|
* owns its own locations.json load.
|
||||||
|
*
|
||||||
* @param cities Span of enriched city data.
|
* @param cities Span of enriched city data.
|
||||||
*/
|
*/
|
||||||
void GenerateUsers(std::span<const EnrichedCity> cities);
|
void GenerateUsers(std::span<const EnrichedCity> cities);
|
||||||
@@ -116,4 +120,4 @@ class BiergartenPipelineOrchestrator {
|
|||||||
std::vector<UserRecord> generated_users_;
|
std::vector<UserRecord> generated_users_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_PIPELINE_ORCHESTRATOR_H_
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_DATA_GENERATOR_H_
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "concurrency/bounded_channel.h"
|
#include "bounded_channel.h"
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void BoundedChannel<T>::Send(T item) {
|
void BoundedChannel<T>::Send(T item) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class DataGenerator {
|
|||||||
* @param region_context Additional regional context text.
|
* @param region_context Additional regional context text.
|
||||||
* @return Brewery generation result.
|
* @return Brewery generation result.
|
||||||
*/
|
*/
|
||||||
virtual BreweryResult GenerateBrewery(const City& location,
|
virtual BreweryResult GenerateBrewery(const Location& location,
|
||||||
const std::string& region_context) = 0;
|
const std::string& region_context) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_DATA_GENERATION_LLAMA_GENERATOR_H_
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_DATA_GENERATION_LLAMA_GENERATOR_H_
|
||||||
#define BIERGARTEN_PIPELINE_INCLUDES_DATA_GENERATION_LLAMA_GENERATOR_H_
|
#define BIERGARTEN_PIPELINE_INCLUDES_DATA_GENERATION_LLAMA_GENERATOR_H_
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file data_generation/llama_generator.h
|
* @file data_generation/llama_generator.h
|
||||||
* @brief llama.cpp-backed implementation of DataGenerator.
|
* @brief llama.cpp-backed implementation of DataGenerator.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "../services/prompting/prompt_directory.h"
|
||||||
#include "data_generation/data_generator.h"
|
#include "data_generation/data_generator.h"
|
||||||
#include "data_generation/prompt_formatting/prompt_formatter.h"
|
#include "data_generation/prompt_formatting/prompt_formatter.h"
|
||||||
#include "data_model/models.h"
|
#include "data_model/models.h"
|
||||||
#include "services/logging/logger.h"
|
#include "services/logging/logger.h"
|
||||||
#include "services/prompting/prompt_directory.h"
|
|
||||||
|
|
||||||
struct llama_model;
|
struct llama_model;
|
||||||
struct llama_context;
|
struct llama_context;
|
||||||
@@ -55,7 +56,7 @@ class LlamaGenerator final : public DataGenerator {
|
|||||||
* @param region_context Additional regional context.
|
* @param region_context Additional regional context.
|
||||||
* @return Generated brewery result.
|
* @return Generated brewery result.
|
||||||
*/
|
*/
|
||||||
BreweryResult GenerateBrewery(const City& location,
|
BreweryResult GenerateBrewery(const Location& location,
|
||||||
const std::string& region_context) override;
|
const std::string& region_context) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class MockGenerator final : public DataGenerator {
|
|||||||
* @param region_context Unused for mock generation.
|
* @param region_context Unused for mock generation.
|
||||||
* @return Generated brewery result.
|
* @return Generated brewery result.
|
||||||
*/
|
*/
|
||||||
BreweryResult GenerateBrewery(const City& location,
|
BreweryResult GenerateBrewery(const Location& location,
|
||||||
const std::string& region_context) override;
|
const std::string& region_context) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +46,7 @@ class MockGenerator final : public DataGenerator {
|
|||||||
* @param location City and country names.
|
* @param location City and country names.
|
||||||
* @return Deterministic hash value.
|
* @return Deterministic hash value.
|
||||||
*/
|
*/
|
||||||
static size_t DeterministicHash(const City& location);
|
static size_t DeterministicHash(const Location& location);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Combines city, persona, and name into a stable hash value.
|
* @brief Combines city, persona, and name into a stable hash value.
|
||||||
@@ -56,7 +56,7 @@ class MockGenerator final : public DataGenerator {
|
|||||||
* @param name Sampled first/last name.
|
* @param name Sampled first/last name.
|
||||||
* @return Deterministic hash value.
|
* @return Deterministic hash value.
|
||||||
*/
|
*/
|
||||||
static size_t DeterministicHash(const City& location,
|
static size_t DeterministicHash(const Location& location,
|
||||||
const UserPersona& persona, const Name& name);
|
const UserPersona& persona, const Name& name);
|
||||||
|
|
||||||
// Hash stride constants for deterministic distribution across fixed-size
|
// Hash stride constants for deterministic distribution across fixed-size
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
* enriched data, and complete generation results.
|
* enriched data, and complete generation results.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "data_model/models.h"
|
#include "data_model/models.h"
|
||||||
@@ -88,45 +87,15 @@ struct UserResult {
|
|||||||
* @brief Enriched city data with Wikipedia context.
|
* @brief Enriched city data with Wikipedia context.
|
||||||
*/
|
*/
|
||||||
struct EnrichedCity {
|
struct EnrichedCity {
|
||||||
City location;
|
Location location;
|
||||||
std::string region_context{};
|
std::string region_context{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A brewery's address. Owns the `City` it belongs to alongside its
|
|
||||||
* street-level fields. Mirrors the `brewery_addresses` table and the
|
|
||||||
* backend's `BreweryPostLocation` shape.
|
|
||||||
*
|
|
||||||
* Only `city` and `postal_code` are populated today -- street-address
|
|
||||||
* generation has no fixture data or service yet, so the address lines stay
|
|
||||||
* empty.
|
|
||||||
*/
|
|
||||||
struct BreweryAddress {
|
|
||||||
City city{};
|
|
||||||
std::optional<std::string> address_line_1{};
|
|
||||||
std::optional<std::string> address_line_2{};
|
|
||||||
std::string postal_code{};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A user's address. Owns the `City` it belongs to alongside its
|
|
||||||
* street-level fields. Mirrors the `user_addresses` table.
|
|
||||||
*
|
|
||||||
* Only `city` is populated today; the street-level columns exist for future
|
|
||||||
* enrichment.
|
|
||||||
*/
|
|
||||||
struct UserAddress {
|
|
||||||
City city{};
|
|
||||||
std::optional<std::string> address_line_1{};
|
|
||||||
std::optional<std::string> address_line_2{};
|
|
||||||
std::optional<std::string> postal_code{};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Helper struct to store generated brewery data.
|
* @brief Helper struct to store generated brewery data.
|
||||||
*/
|
*/
|
||||||
struct BreweryRecord {
|
struct BreweryRecord {
|
||||||
BreweryAddress address;
|
Location location;
|
||||||
BreweryResult brewery;
|
BreweryResult brewery;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -138,7 +107,7 @@ struct BreweryRecord {
|
|||||||
* consumer can register real accounts from the pipeline's SQLite export.
|
* consumer can register real accounts from the pipeline's SQLite export.
|
||||||
*/
|
*/
|
||||||
struct UserRecord {
|
struct UserRecord {
|
||||||
UserAddress address{};
|
Location location;
|
||||||
UserResult user;
|
UserResult user;
|
||||||
std::string email{};
|
std::string email{};
|
||||||
std::string date_of_birth{};
|
std::string date_of_birth{};
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -22,13 +21,13 @@ class ILogger;
|
|||||||
namespace prog_opts = boost::program_options;
|
namespace prog_opts = boost::program_options;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// City Models
|
// Location Models
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Canonical location record for city-level generation.
|
* @brief Canonical location record for city-level generation.
|
||||||
*/
|
*/
|
||||||
struct City {
|
struct Location {
|
||||||
std::string city{};
|
std::string city{};
|
||||||
std::string state_province{};
|
std::string state_province{};
|
||||||
|
|
||||||
@@ -44,22 +43,20 @@ struct City {
|
|||||||
*/
|
*/
|
||||||
std::string iso3166_1{};
|
std::string iso3166_1{};
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A list of regular expressions used for valid postal codes in the
|
|
||||||
* region.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
std::vector<std::string> postal_regex{};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Example postal codes for the region.
|
|
||||||
*/
|
|
||||||
std::vector<std::string> postal_code_examples{};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Local language codes in priority order.
|
* @brief Local language codes in priority order.
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> local_languages{};
|
std::vector<std::string> local_languages{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Latitude in decimal degrees.
|
||||||
|
*/
|
||||||
|
double latitude{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Longitude in decimal degrees.
|
||||||
|
*/
|
||||||
|
double longitude{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -69,7 +66,7 @@ struct City {
|
|||||||
/**
|
/**
|
||||||
* @brief A sampled first/last name pair, with the source forename's gender.
|
* @brief A sampled first/last name pair, with the source forename's gender.
|
||||||
*
|
*
|
||||||
* Produced by the SampleName() helper in generate_users.cc.
|
* Produced by NamesByCountry::SampleName();
|
||||||
*/
|
*/
|
||||||
struct Name {
|
struct Name {
|
||||||
std::string first_name{};
|
std::string first_name{};
|
||||||
@@ -96,23 +93,8 @@ struct ForenameEntry {
|
|||||||
* dataset (e.g. "M", "F").
|
* dataset (e.g. "M", "F").
|
||||||
*/
|
*/
|
||||||
std::string gender{};
|
std::string gender{};
|
||||||
|
|
||||||
bool operator==(const ForenameEntry& other) const {
|
|
||||||
return name == other.name && gender == other.gender;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace std {
|
|
||||||
template <>
|
|
||||||
struct hash<ForenameEntry> {
|
|
||||||
size_t operator()(const ForenameEntry& entry) const noexcept {
|
|
||||||
const size_t name_hash = std::hash<std::string>{}(entry.name);
|
|
||||||
const size_t gender_hash = std::hash<std::string>{}(entry.gender);
|
|
||||||
return name_hash ^ (gender_hash << 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A persona archetype used to ground LLM-generated user bios.
|
* @brief A persona archetype used to ground LLM-generated user bios.
|
||||||
*/
|
*/
|
||||||
|
|||||||
51
tooling/pipeline/includes/data_model/names_by_country.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_DATA_MODEL_NAMES_BY_COUNTRY_H_
|
||||||
|
#define BIERGARTEN_PIPELINE_INCLUDES_DATA_MODEL_NAMES_BY_COUNTRY_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file data_model/names_by_country.h
|
||||||
|
* @brief Sampling over the names-by-country fixture data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "data_model/models.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Samples locale-appropriate names from curated forename/surname
|
||||||
|
* fixture data, keyed by ISO 3166-1 country code.
|
||||||
|
*
|
||||||
|
* Forenames and surnames are kept in separate maps (mirroring the source
|
||||||
|
* dataset's own shape) so per-forename gender is preserved; pairing happens
|
||||||
|
* at sample time rather than being precomputed, so no information from the
|
||||||
|
* source dataset is discarded up front.
|
||||||
|
*/
|
||||||
|
class NamesByCountry {
|
||||||
|
public:
|
||||||
|
NamesByCountry(std::unordered_map<std::string, std::vector<ForenameEntry>>
|
||||||
|
forenames_by_country,
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>>
|
||||||
|
surnames_by_country);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Samples a first/last name pair for the given country.
|
||||||
|
*
|
||||||
|
* @param iso3166_1 ISO 3166-1 country code to sample for.
|
||||||
|
* @param rng Random source used for sampling.
|
||||||
|
* @return A sampled Name, or std::nullopt if the country has no forename
|
||||||
|
* or no surname entries.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::optional<Name> SampleName(const std::string& iso3166_1,
|
||||||
|
std::mt19937& rng) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, std::vector<ForenameEntry>>
|
||||||
|
forenames_by_country_;
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>>
|
||||||
|
surnames_by_country_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_DATA_MODEL_NAMES_BY_COUNTRY_H_
|
||||||
47
tooling/pipeline/includes/json_handling/json_loader.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_JSON_HANDLING_JSON_LOADER_H_
|
||||||
|
#define BIERGARTEN_PIPELINE_INCLUDES_JSON_HANDLING_JSON_LOADER_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file json_handling/json_loader.h
|
||||||
|
* @brief JSON-backed implementation of ICuratedDataService.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "data_model/models.h"
|
||||||
|
#include "data_model/names_by_country.h"
|
||||||
|
#include "services/curated_data/curated_data_service.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads curated location, persona, and name data from JSON files.
|
||||||
|
*/
|
||||||
|
class JsonLoader final : public ICuratedDataService {
|
||||||
|
public:
|
||||||
|
JsonLoader() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses a JSON array file and returns all location records.
|
||||||
|
*/
|
||||||
|
std::vector<Location> LoadLocations(
|
||||||
|
const std::filesystem::path& filepath) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses a JSON array file and returns all persona records.
|
||||||
|
*/
|
||||||
|
std::vector<UserPersona> LoadPersonas(
|
||||||
|
const std::filesystem::path& filepath) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses the names-by-country fixture pair into a sampling-capable
|
||||||
|
* NamesByCountry.
|
||||||
|
*
|
||||||
|
* @param forenames_filepath Path to forenames-by-country.json.
|
||||||
|
* @param surnames_filepath Path to surnames-by-country.json.
|
||||||
|
*/
|
||||||
|
NamesByCountry LoadNamesByCountry(
|
||||||
|
const std::filesystem::path& forenames_filepath,
|
||||||
|
const std::filesystem::path& surnames_filepath) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_JSON_HANDLING_JSON_LOADER_H_
|
||||||
@@ -106,4 +106,4 @@ inline void PrettyPrint(std::ostream& outstream,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_JSON_HANDLING_PRETTY_PRINT_H_
|
#endif
|
||||||
|
|||||||
@@ -7,18 +7,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "data_model/models.h"
|
#include "data_model/models.h"
|
||||||
|
#include "data_model/names_by_country.h"
|
||||||
using ForenameList = std::vector<ForenameEntry>;
|
|
||||||
using SurnameList = std::vector<std::string>;
|
|
||||||
using CityList = std::vector<City>;
|
|
||||||
using PersonasList = std::vector<UserPersona>;
|
|
||||||
using ForenamesByCountryMap = std::unordered_map<std::string, ForenameList>;
|
|
||||||
using SurnamesByCountryMap = std::unordered_map<std::string, SurnameList>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Interface for services that load curated data used to seed
|
* @brief Interface for services that load curated data used to seed
|
||||||
@@ -37,14 +29,25 @@ class ICuratedDataService {
|
|||||||
/**
|
/**
|
||||||
* @brief Loads all curated location records.
|
* @brief Loads all curated location records.
|
||||||
*/
|
*/
|
||||||
virtual const CityList& LoadCities() = 0;
|
virtual std::vector<Location> LoadLocations(
|
||||||
|
const std::filesystem::path& filepath) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Loads all curated persona records.
|
* @brief Loads all curated persona records.
|
||||||
*/
|
*/
|
||||||
virtual const PersonasList& LoadPersonas() = 0;
|
virtual std::vector<UserPersona> LoadPersonas(
|
||||||
virtual const ForenamesByCountryMap& LoadForenamesByCountry() = 0;
|
const std::filesystem::path& filepath) = 0;
|
||||||
virtual const SurnamesByCountryMap& LoadSurnamesByCountry() = 0;
|
|
||||||
|
/**
|
||||||
|
* @brief Loads the names-by-country fixture pair into a sampling-capable
|
||||||
|
* NamesByCountry.
|
||||||
|
*
|
||||||
|
* @param forenames_filepath Path to forenames-by-country.json.
|
||||||
|
* @param surnames_filepath Path to surnames-by-country.json.
|
||||||
|
*/
|
||||||
|
virtual NamesByCountry LoadNamesByCountry(
|
||||||
|
const std::filesystem::path& forenames_filepath,
|
||||||
|
const std::filesystem::path& surnames_filepath) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_CURATED_DATA_CURATED_DATA_SERVICE_H_
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_CURATED_DATA_CURATED_DATA_SERVICE_H_
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_JSON_HANDLING_JSON_LOADER_H_
|
|
||||||
#define BIERGARTEN_PIPELINE_INCLUDES_JSON_HANDLING_JSON_LOADER_H_
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file json_handling/json_loader.h
|
|
||||||
* @brief JSON-backed implementation of ICuratedDataService.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "data_model/models.h"
|
|
||||||
#include "services/curated_data/curated_data_service.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief File locations for the curated JSON fixtures consumed by
|
|
||||||
* CuratedJsonDataService.
|
|
||||||
*/
|
|
||||||
struct CuratedDataFilePaths {
|
|
||||||
std::filesystem::path cities_path;
|
|
||||||
std::filesystem::path personas_path;
|
|
||||||
std::filesystem::path forenames_path;
|
|
||||||
std::filesystem::path surnames_path;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Loads curated location, persona, and name data from JSON files.
|
|
||||||
*/
|
|
||||||
class CuratedJsonDataService final : public ICuratedDataService {
|
|
||||||
struct cache {
|
|
||||||
CityList locations;
|
|
||||||
PersonasList personas;
|
|
||||||
ForenamesByCountryMap forenames_by_country;
|
|
||||||
SurnamesByCountryMap surnames_by_country;
|
|
||||||
|
|
||||||
cache() = default;
|
|
||||||
~cache() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
CuratedDataFilePaths filepaths_;
|
|
||||||
cache cache_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit CuratedJsonDataService(CuratedDataFilePaths filepaths);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Parses a JSON array file and returns all city records.
|
|
||||||
*/
|
|
||||||
const CityList& LoadCities() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Parses a JSON array file and returns all persona records.
|
|
||||||
*/
|
|
||||||
const PersonasList& LoadPersonas() override;
|
|
||||||
|
|
||||||
const ForenamesByCountryMap& LoadForenamesByCountry() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Parses a JSON file and returns all the forenames per country.
|
|
||||||
*/
|
|
||||||
const SurnamesByCountryMap& LoadSurnamesByCountry() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_JSON_HANDLING_JSON_LOADER_H_
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_CURATED_DATA_MOCK_CURATED_DATA_SERVICE_H_
|
|
||||||
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_CURATED_DATA_MOCK_CURATED_DATA_SERVICE_H_
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file services/curated_data/mock_curated_data_service.h
|
|
||||||
* @brief In-memory ICuratedDataService backed by a small fixed dataset, used
|
|
||||||
* when file-backed curated data is disabled (mock mode).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "data_model/models.h"
|
|
||||||
#include "services/curated_data/curated_data_service.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Curated data service returning a small fixed in-memory dataset in
|
|
||||||
* place of the JSON fixture files used by JsonLoader.
|
|
||||||
*/
|
|
||||||
class MockCuratedDataService final : public ICuratedDataService {
|
|
||||||
public:
|
|
||||||
MockCuratedDataService();
|
|
||||||
|
|
||||||
const CityList& LoadCities() override;
|
|
||||||
|
|
||||||
const PersonasList& LoadPersonas() override;
|
|
||||||
|
|
||||||
const ForenamesByCountryMap& LoadForenamesByCountry() override;
|
|
||||||
|
|
||||||
const SurnamesByCountryMap& LoadSurnamesByCountry() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
CityList locations_;
|
|
||||||
PersonasList personas_;
|
|
||||||
ForenamesByCountryMap forenames_by_country_;
|
|
||||||
SurnamesByCountryMap surnames_by_country_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_CURATED_DATA_MOCK_CURATED_DATA_SERVICE_H_
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include "services/database/sqlite_handle_types.h"
|
#include "sqlite_handle_types.h"
|
||||||
|
|
||||||
namespace sqlite_export_service_internal {
|
namespace sqlite_export_service_internal {
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "../datetime/date_time_provider.h"
|
||||||
#include "data_model/models.h"
|
#include "data_model/models.h"
|
||||||
#include "services/database/export_service.h"
|
#include "export_service.h"
|
||||||
#include "services/database/sqlite_export_service_helpers.h"
|
#include "sqlite_export_service_helpers.h"
|
||||||
#include "services/datetime/date_time_provider.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Persists generated brewery records into a fresh SQLite database.
|
* @brief Persists generated brewery records into a fresh SQLite database.
|
||||||
@@ -45,7 +45,7 @@ class SqliteExportService final : public IExportService {
|
|||||||
void RollbackAndCloseNoThrow() noexcept;
|
void RollbackAndCloseNoThrow() noexcept;
|
||||||
|
|
||||||
[[nodiscard]] std::filesystem::path BuildDatabasePath() const;
|
[[nodiscard]] std::filesystem::path BuildDatabasePath() const;
|
||||||
[[nodiscard]] static std::string BuildCityKey(const City& location);
|
[[nodiscard]] static std::string BuildLocationKey(const Location& location);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the row id for @p location, inserting it first if it has
|
* @brief Returns the row id for @p location, inserting it first if it has
|
||||||
@@ -54,20 +54,18 @@ class SqliteExportService final : public IExportService {
|
|||||||
* Shared by both ProcessRecord() overloads so breweries and users
|
* Shared by both ProcessRecord() overloads so breweries and users
|
||||||
* referencing the same location resolve to the same row.
|
* referencing the same location resolve to the same row.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] sqlite3_int64 ResolveCityId(const City& location);
|
[[nodiscard]] sqlite3_int64 ResolveLocationId(const Location& location);
|
||||||
|
|
||||||
std::unique_ptr<IDateTimeProvider> date_time_provider_;
|
std::unique_ptr<IDateTimeProvider> date_time_provider_;
|
||||||
std::filesystem::path output_path_;
|
std::filesystem::path output_path_;
|
||||||
std::string run_timestamp_utc_;
|
std::string run_timestamp_utc_;
|
||||||
std::filesystem::path database_path_;
|
std::filesystem::path database_path_;
|
||||||
SqliteDatabaseHandle db_handle_;
|
SqliteDatabaseHandle db_handle_;
|
||||||
SqliteStatementHandle insert_city_stmt_;
|
SqliteStatementHandle insert_location_stmt_;
|
||||||
SqliteStatementHandle insert_brewery_stmt_;
|
SqliteStatementHandle insert_brewery_stmt_;
|
||||||
SqliteStatementHandle insert_brewery_address_stmt_;
|
|
||||||
SqliteStatementHandle insert_user_stmt_;
|
SqliteStatementHandle insert_user_stmt_;
|
||||||
SqliteStatementHandle insert_user_address_stmt_;
|
|
||||||
bool transaction_open_ = false;
|
bool transaction_open_ = false;
|
||||||
std::unordered_map<std::string, sqlite3_int64> city_cache_;
|
std::unordered_map<std::string, sqlite3_int64> location_cache_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_DATABASE_SQLITE_EXPORT_SERVICE_H_
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_DATABASE_SQLITE_EXPORT_SERVICE_H_
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
/* Umbrella header for backward compatibility. */
|
/* Umbrella header for backward compatibility. */
|
||||||
|
|
||||||
#include "services/database/sqlite_connection_helpers.h"
|
#include "sqlite_connection_helpers.h"
|
||||||
#include "services/database/sqlite_handle_types.h"
|
#include "sqlite_handle_types.h"
|
||||||
#include "services/database/sqlite_statement_helpers.h"
|
#include "sqlite_statement_helpers.h"
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_DATABASE_SQLITE_EXPORT_SERVICE_HELPERS_H_
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_DATABASE_SQLITE_EXPORT_SERVICE_HELPERS_H_
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ using SqliteDatabaseHandle = std::unique_ptr<sqlite3, SqliteDatabaseDeleter>;
|
|||||||
using SqliteStatementHandle =
|
using SqliteStatementHandle =
|
||||||
std::unique_ptr<sqlite3_stmt, SqliteStatementDeleter>;
|
std::unique_ptr<sqlite3_stmt, SqliteStatementDeleter>;
|
||||||
|
|
||||||
// Represents a parameter that is bound to a prepared SQLite statement.
|
|
||||||
// N.B. indices are 1 based.
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct BoundParam {
|
struct BindParam {
|
||||||
int index;
|
int index;
|
||||||
T value;
|
T value;
|
||||||
std::string_view action;
|
std::string_view action;
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "services/database/sqlite_handle_types.h"
|
#include "sqlite_handle_types.h"
|
||||||
|
|
||||||
namespace sqlite_export_service_internal {
|
namespace sqlite_export_service_internal {
|
||||||
|
|
||||||
inline constexpr std::string_view kCreateCitiesTableSql = R"sql(
|
inline constexpr std::string_view kCreateLocationsTableSql = R"sql(
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS cities (
|
CREATE TABLE IF NOT EXISTS locations (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
city TEXT NOT NULL,
|
city TEXT NOT NULL,
|
||||||
state_province TEXT NOT NULL,
|
state_province TEXT NOT NULL,
|
||||||
@@ -27,7 +27,9 @@ CREATE TABLE IF NOT EXISTS cities (
|
|||||||
country TEXT NOT NULL,
|
country TEXT NOT NULL,
|
||||||
iso3166_1 TEXT NOT NULL,
|
iso3166_1 TEXT NOT NULL,
|
||||||
local_languages_json TEXT NOT NULL,
|
local_languages_json TEXT NOT NULL,
|
||||||
UNIQUE(city, state_province, iso3166_2, country)
|
latitude REAL NOT NULL,
|
||||||
|
longitude REAL NOT NULL,
|
||||||
|
UNIQUE(city, state_province, iso3166_2, country, latitude, longitude)
|
||||||
);
|
);
|
||||||
|
|
||||||
)sql";
|
)sql";
|
||||||
@@ -36,21 +38,23 @@ inline constexpr std::string_view kCreateBreweriesTableSql = R"sql(
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS breweries (
|
CREATE TABLE IF NOT EXISTS breweries (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
city_id INTEGER NOT NULL,
|
location_id INTEGER NOT NULL,
|
||||||
name_en TEXT NOT NULL,
|
name_en TEXT NOT NULL,
|
||||||
description_en TEXT NOT NULL,
|
description_en TEXT NOT NULL,
|
||||||
name_local TEXT NOT NULL,
|
name_local TEXT NOT NULL,
|
||||||
description_local TEXT NOT NULL,
|
description_local TEXT NOT NULL,
|
||||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
FOREIGN KEY(location_id) REFERENCES locations(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_breweries_city_id ON breweries(city_id);
|
CREATE INDEX IF NOT EXISTS idx_breweries_location_id ON breweries(location_id);
|
||||||
|
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
inline constexpr std::string_view kCreateUsersTableSql = R"sql(
|
inline constexpr std::string_view kCreateUsersTableSql = R"sql(
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
location_id INTEGER NOT NULL,
|
||||||
first_name TEXT NOT NULL,
|
first_name TEXT NOT NULL,
|
||||||
last_name TEXT NOT NULL,
|
last_name TEXT NOT NULL,
|
||||||
gender TEXT NOT NULL,
|
gender TEXT NOT NULL,
|
||||||
@@ -58,61 +62,30 @@ CREATE TABLE IF NOT EXISTS users (
|
|||||||
bio TEXT NOT NULL,
|
bio TEXT NOT NULL,
|
||||||
activity_weight REAL NOT NULL,
|
activity_weight REAL NOT NULL,
|
||||||
email TEXT NOT NULL UNIQUE,
|
email TEXT NOT NULL UNIQUE,
|
||||||
date_of_birth TEXT NOT NULL
|
date_of_birth TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(location_id) REFERENCES locations(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_location_id ON users(location_id);
|
||||||
|
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
inline constexpr std::string_view kCreateBreweryAddressTableSql = R"sql(
|
inline constexpr std::string_view kInsertLocationSql = R"sql(
|
||||||
CREATE TABLE IF NOT EXISTS brewery_addresses (
|
INSERT INTO locations (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
brewery_id INTEGER NOT NULL,
|
|
||||||
address_line_1 TEXT,
|
|
||||||
address_line_2 TEXT,
|
|
||||||
postal_code TEXT NOT NULL,
|
|
||||||
city_id INTEGER NOT NULL,
|
|
||||||
FOREIGN KEY(brewery_id) REFERENCES breweries(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_brewery_addresses_brewery_id
|
|
||||||
ON brewery_addresses(brewery_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_brewery_addresses_city_id
|
|
||||||
ON brewery_addresses(city_id);
|
|
||||||
)sql";
|
|
||||||
|
|
||||||
inline constexpr std::string_view kCreateUserAddressTableSql = R"sql(
|
|
||||||
CREATE TABLE IF NOT EXISTS user_addresses (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
address_line_1 TEXT,
|
|
||||||
address_line_2 TEXT,
|
|
||||||
postal_code TEXT,
|
|
||||||
city_id INTEGER NOT NULL,
|
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_user_addresses_user_id
|
|
||||||
ON user_addresses(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_user_addresses_city_id
|
|
||||||
ON user_addresses(city_id);
|
|
||||||
)sql";
|
|
||||||
|
|
||||||
inline constexpr std::string_view kInsertCitySql = R"sql(
|
|
||||||
INSERT INTO cities (
|
|
||||||
city,
|
city,
|
||||||
state_province,
|
state_province,
|
||||||
iso3166_2,
|
iso3166_2,
|
||||||
country,
|
country,
|
||||||
iso3166_1,
|
iso3166_1,
|
||||||
local_languages_json
|
local_languages_json,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?);
|
latitude,
|
||||||
|
longitude
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
inline constexpr std::string_view kInsertBrewerySql = R"sql(
|
inline constexpr std::string_view kInsertBrewerySql = R"sql(
|
||||||
INSERT INTO breweries (
|
INSERT INTO breweries (
|
||||||
city_id,
|
location_id,
|
||||||
name_en,
|
name_en,
|
||||||
description_en,
|
description_en,
|
||||||
name_local,
|
name_local,
|
||||||
@@ -122,6 +95,7 @@ INSERT INTO breweries (
|
|||||||
|
|
||||||
inline constexpr std::string_view kInsertUserSql = R"sql(
|
inline constexpr std::string_view kInsertUserSql = R"sql(
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
|
location_id,
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
gender,
|
gender,
|
||||||
@@ -130,64 +104,35 @@ INSERT INTO users (
|
|||||||
activity_weight,
|
activity_weight,
|
||||||
email,
|
email,
|
||||||
date_of_birth
|
date_of_birth
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
)sql";
|
|
||||||
|
|
||||||
inline constexpr std::string_view kInsertBreweryAddressSql = R"sql(
|
|
||||||
INSERT INTO brewery_addresses (
|
|
||||||
brewery_id,
|
|
||||||
postal_code,
|
|
||||||
city_id
|
|
||||||
) VALUES (?, ?, ?);
|
|
||||||
)sql";
|
|
||||||
|
|
||||||
inline constexpr std::string_view kInsertUserAddressSql = R"sql(
|
|
||||||
INSERT INTO user_addresses (
|
|
||||||
user_id,
|
|
||||||
city_id
|
|
||||||
) VALUES (?, ?);
|
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
// sqlite3_bind_*() parameter indices are 1-based, matching the "?"
|
// sqlite3_bind_*() parameter indices are 1-based, matching the "?"
|
||||||
// placeholder order in the SQL above.
|
// placeholder order in the SQL above.
|
||||||
enum CityBindIndex {
|
inline constexpr int kLocationCityBindIndex = 1;
|
||||||
kCityNameBindIndex = 1,
|
inline constexpr int kLocationStateProvinceBindIndex = 2;
|
||||||
kCityStateProvinceBindIndex,
|
inline constexpr int kLocationIso31662BindIndex = 3;
|
||||||
kCityIso31662BindIndex,
|
inline constexpr int kLocationCountryBindIndex = 4;
|
||||||
kCityCountryBindIndex,
|
inline constexpr int kLocationIso31661BindIndex = 5;
|
||||||
kCityIso31661BindIndex,
|
inline constexpr int kLocationLanguagesBindIndex = 6;
|
||||||
kCityLanguagesBindIndex,
|
inline constexpr int kLocationLatitudeBindIndex = 7;
|
||||||
};
|
inline constexpr int kLocationLongitudeBindIndex = 8;
|
||||||
|
|
||||||
enum BreweryBindIndex {
|
inline constexpr int kBreweryLocationIdBindIndex = 1;
|
||||||
kBreweryCityIdBindIndex = 1,
|
inline constexpr int kBreweryEnglishNameBindIndex = 2;
|
||||||
kBreweryEnglishNameBindIndex,
|
inline constexpr int kBreweryEnglishDescriptionBindIndex = 3;
|
||||||
kBreweryEnglishDescriptionBindIndex,
|
inline constexpr int kBreweryLocalNameBindIndex = 4;
|
||||||
kBreweryLocalNameBindIndex,
|
inline constexpr int kBreweryLocalDescriptionBindIndex = 5;
|
||||||
kBreweryLocalDescriptionBindIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum UserBindIndex {
|
inline constexpr int kUserLocationIdBindIndex = 1;
|
||||||
kUserFirstNameBindIndex = 1,
|
inline constexpr int kUserFirstNameBindIndex = 2;
|
||||||
kUserLastNameBindIndex,
|
inline constexpr int kUserLastNameBindIndex = 3;
|
||||||
kUserGenderBindIndex,
|
inline constexpr int kUserGenderBindIndex = 4;
|
||||||
kUserUsernameBindIndex,
|
inline constexpr int kUserUsernameBindIndex = 5;
|
||||||
kUserBioBindIndex,
|
inline constexpr int kUserBioBindIndex = 6;
|
||||||
kUserActivityWeightBindIndex,
|
inline constexpr int kUserActivityWeightBindIndex = 7;
|
||||||
kUserEmailBindIndex,
|
inline constexpr int kUserEmailBindIndex = 8;
|
||||||
kUserDateOfBirthBindIndex,
|
inline constexpr int kUserDateOfBirthBindIndex = 9;
|
||||||
};
|
|
||||||
|
|
||||||
enum BreweryAddressBindIndex {
|
|
||||||
kBreweryAddressBreweryIdBindIndex = 1,
|
|
||||||
kBreweryAddressPostalCodeBindIndex,
|
|
||||||
kBreweryAddressCityIdBindIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum UserAddressBindIndex {
|
|
||||||
kUserAddressUserIdBindIndex = 1,
|
|
||||||
kUserAddressCityIdBindIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
SqliteStatementHandle PrepareStatement(const SqliteDatabaseHandle& db_handle,
|
SqliteStatementHandle PrepareStatement(const SqliteDatabaseHandle& db_handle,
|
||||||
std::string_view sql,
|
std::string_view sql,
|
||||||
@@ -196,13 +141,13 @@ SqliteStatementHandle PrepareStatement(const SqliteDatabaseHandle& db_handle,
|
|||||||
void ResetStatement(SqliteStatementHandle& statement);
|
void ResetStatement(SqliteStatementHandle& statement);
|
||||||
|
|
||||||
void Bind(const SqliteStatementHandle& statement,
|
void Bind(const SqliteStatementHandle& statement,
|
||||||
const BoundParam<std::string_view>& param);
|
const BindParam<std::string_view>& param);
|
||||||
|
|
||||||
void Bind(const SqliteStatementHandle& statement,
|
void Bind(const SqliteStatementHandle& statement,
|
||||||
const BoundParam<double>& param);
|
const BindParam<double>& param);
|
||||||
|
|
||||||
void Bind(const SqliteStatementHandle& statement,
|
void Bind(const SqliteStatementHandle& statement,
|
||||||
const BoundParam<sqlite3_int64>& param);
|
const BindParam<sqlite3_int64>& param);
|
||||||
|
|
||||||
void StepStatement(const SqliteDatabaseHandle& db_handle,
|
void StepStatement(const SqliteDatabaseHandle& db_handle,
|
||||||
const SqliteStatementHandle& statement,
|
const SqliteStatementHandle& statement,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class IEnrichmentService {
|
|||||||
* @param loc Location to enrich.
|
* @param loc Location to enrich.
|
||||||
* @return Context text, or an empty string if unavailable.
|
* @return Context text, or an empty string if unavailable.
|
||||||
*/
|
*/
|
||||||
virtual std::string GetLocationContext(const City& loc) = 0;
|
virtual std::string GetLocationContext(const Location& loc) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_ENRICHMENT_ENRICHMENT_SERVICE_H_
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_ENRICHMENT_ENRICHMENT_SERVICE_H_
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "services/enrichment/enrichment_service.h"
|
#include "enrichment_service.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Enrichment service that returns no context for any location.
|
* @brief Enrichment service that returns no context for any location.
|
||||||
*/
|
*/
|
||||||
class MockEnrichmentService final : public IEnrichmentService {
|
class MockEnrichmentService final : public IEnrichmentService {
|
||||||
public:
|
public:
|
||||||
std::string GetLocationContext(const City& /*loc*/) override {
|
std::string GetLocationContext(const Location& /*loc*/) override {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "services/enrichment/enrichment_service.h"
|
#include "enrichment_service.h"
|
||||||
#include "services/logging/logger.h"
|
#include "services/logging/logger.h"
|
||||||
#include "web_client/web_client.h"
|
#include "web_client/web_client.h"
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class WikipediaEnrichmentService final : public IEnrichmentService {
|
|||||||
/**
|
/**
|
||||||
* @brief Returns the Wikipedia-derived context for a location.
|
* @brief Returns the Wikipedia-derived context for a location.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] std::string GetLocationContext(const City& loc) override;
|
[[nodiscard]] std::string GetLocationContext(const Location& loc) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string FetchExtract(std::string_view query);
|
std::string FetchExtract(std::string_view query);
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
* a bounded channel for later processing by the dispatcher.
|
* a bounded channel for later processing by the dispatcher.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_PRODUCER_H_
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_CHANNEL_LOGGER_H_
|
||||||
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_PRODUCER_H_
|
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_CHANNEL_LOGGER_H_
|
||||||
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
@@ -50,4 +50,4 @@ class LogProducer final : public ILogger {
|
|||||||
BoundedChannel<LogEntry>& channel_;
|
BoundedChannel<LogEntry>& channel_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_PRODUCER_H_
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_CHANNEL_LOGGER_H_
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_POSTAL_CODE_MOCK_POSTAL_CODE_SERVICE_H_
|
|
||||||
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_POSTAL_CODE_MOCK_POSTAL_CODE_SERVICE_H_
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file services/postal_code/mock_postal_code_service.h
|
|
||||||
* @brief Placeholder IPostalCodeService used until xeger-based generation is
|
|
||||||
* implemented.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "data_model/models.h"
|
|
||||||
#include "services/postal_code/postal_code_service.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Postal code service that returns the location's first known
|
|
||||||
* example postal code rather than generating one from its regex.
|
|
||||||
*/
|
|
||||||
class MockPostalCodeService final : public IPostalCodeService {
|
|
||||||
public:
|
|
||||||
std::string GeneratePostalCode(const City& location) override {
|
|
||||||
if (location.postal_code_examples.empty()) {
|
|
||||||
throw std::runtime_error(
|
|
||||||
"MockPostalCodeService: location has no postal_code_examples: " +
|
|
||||||
location.city);
|
|
||||||
}
|
|
||||||
return location.postal_code_examples.front();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_POSTAL_CODE_MOCK_POSTAL_CODE_SERVICE_H_
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_POSTAL_CODE_POSTAL_CODE_SERVICE_H_
|
|
||||||
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_POSTAL_CODE_POSTAL_CODE_SERVICE_H_
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file services/postal_code/postal_code_service.h
|
|
||||||
* @brief Abstraction for generating a postal code for a location.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "data_model/models.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Interface for services that generate a postal code string matching
|
|
||||||
* a location's postal code format (`City::postal_regex`).
|
|
||||||
*/
|
|
||||||
class IPostalCodeService {
|
|
||||||
public:
|
|
||||||
virtual ~IPostalCodeService() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Generates a postal code for the given location.
|
|
||||||
*
|
|
||||||
* @param location Location whose postal code format to generate for.
|
|
||||||
* @return A postal code string matching `location.postal_regex`.
|
|
||||||
*/
|
|
||||||
virtual std::string GeneratePostalCode(const City& location) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_POSTAL_CODE_POSTAL_CODE_SERVICE_H_
|
|
||||||
@@ -52,4 +52,4 @@ class HttpWebClient final : public WebClient {
|
|||||||
std::shared_ptr<ILogger> logger_;
|
std::shared_ptr<ILogger> logger_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_WEB_CLIENT_HTTP_WEB_CLIENT_H_
|
#endif
|
||||||
|
|||||||
1002
tooling/pipeline/locations.json
Normal file
@@ -52,7 +52,7 @@ WORKDIR /app/build
|
|||||||
COPY --from=builder /app/build/biergarten-pipeline ./
|
COPY --from=builder /app/build/biergarten-pipeline ./
|
||||||
|
|
||||||
# Copy required config files
|
# Copy required config files
|
||||||
COPY cities.json /app/build/
|
COPY locations.json /app/build/
|
||||||
COPY beer-styles.json /app/build/
|
COPY beer-styles.json /app/build/
|
||||||
|
|
||||||
# Copy prompt templates
|
# Copy prompt templates
|
||||||
|
|||||||
@@ -7,20 +7,17 @@
|
|||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "services/postal_code/postal_code_service.h"
|
|
||||||
|
|
||||||
BiergartenPipelineOrchestrator::BiergartenPipelineOrchestrator(
|
BiergartenPipelineOrchestrator::BiergartenPipelineOrchestrator(
|
||||||
std::shared_ptr<ILogger> logger,
|
std::shared_ptr<ILogger> logger,
|
||||||
std::unique_ptr<IEnrichmentService> context_service,
|
std::unique_ptr<IEnrichmentService> context_service,
|
||||||
std::unique_ptr<DataGenerator> generator,
|
std::unique_ptr<DataGenerator> generator,
|
||||||
std::unique_ptr<IExportService> exporter,
|
std::unique_ptr<IExportService> exporter,
|
||||||
std::unique_ptr<ICuratedDataService> curated_data_service,
|
std::unique_ptr<ICuratedDataService> curated_data_service,
|
||||||
std::unique_ptr<IPostalCodeService> postal_code_service,
|
|
||||||
const ApplicationOptions& application_options)
|
const ApplicationOptions& application_options)
|
||||||
: logger_(std::move(logger)),
|
: logger_(std::move(logger)),
|
||||||
context_service_(std::move(context_service)),
|
context_service_(std::move(context_service)),
|
||||||
generator_(std::move(generator)),
|
generator_(std::move(generator)),
|
||||||
exporter_(std::move(exporter)),
|
exporter_(std::move(exporter)),
|
||||||
curated_data_service_(std::move(curated_data_service)),
|
curated_data_service_(std::move(curated_data_service)),
|
||||||
postal_code_service_(std::move(postal_code_service)),
|
application_options_(application_options) {
|
||||||
application_options_(application_options) {}
|
}
|
||||||
@@ -22,17 +22,12 @@ void BiergartenPipelineOrchestrator::GenerateBreweries(
|
|||||||
|
|
||||||
const auto generate_record =
|
const auto generate_record =
|
||||||
[this, &skipped_count](
|
[this, &skipped_count](
|
||||||
const City& location,
|
const Location& location,
|
||||||
const std::string& region_context) -> std::optional<BreweryRecord> {
|
const std::string& region_context) -> std::optional<BreweryRecord> {
|
||||||
try {
|
try {
|
||||||
const BreweryResult brewery =
|
const BreweryResult brewery =
|
||||||
generator_->GenerateBrewery(location, region_context);
|
generator_->GenerateBrewery(location, region_context);
|
||||||
const std::string postal_code =
|
return BreweryRecord{.location = location, .brewery = brewery};
|
||||||
postal_code_service_->GeneratePostalCode(location);
|
|
||||||
return BreweryRecord{
|
|
||||||
.address =
|
|
||||||
BreweryAddress{.city = location, .postal_code = postal_code},
|
|
||||||
.brewery = brewery};
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
++skipped_count;
|
++skipped_count;
|
||||||
|
|
||||||
@@ -52,13 +47,13 @@ void BiergartenPipelineOrchestrator::GenerateBreweries(
|
|||||||
exporter_->ProcessRecord(record);
|
exporter_->ProcessRecord(record);
|
||||||
} catch (const std::exception& export_exception) {
|
} catch (const std::exception& export_exception) {
|
||||||
++export_failed_count;
|
++export_failed_count;
|
||||||
logger_->Log({.level = LogLevel::Warn,
|
logger_->Log(
|
||||||
.phase = PipelinePhase::BreweryAndBeerGeneration,
|
{.level = LogLevel::Warn,
|
||||||
.message = std::format(
|
.phase = PipelinePhase::BreweryAndBeerGeneration,
|
||||||
"[Pipeline] Generated brewery for '{}' ({}) "
|
.message = std::format("[Pipeline] Generated brewery for '{}' ({}) "
|
||||||
"but SQLite export failed: {}",
|
"but SQLite export failed: {}",
|
||||||
record.address.city.city, record.address.city.country,
|
record.location.city, record.location.country,
|
||||||
export_exception.what())});
|
export_exception.what())});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <iterator>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "biergarten_pipeline_orchestrator.h"
|
#include "biergarten_pipeline_orchestrator.h"
|
||||||
#include "services/curated_data/curated_json_data_service.h"
|
#include "json_handling/json_loader.h"
|
||||||
#include "services/logging/logger.h"
|
#include "services/logging/logger.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -25,7 +24,7 @@ std::string Sanitize(std::string_view value) {
|
|||||||
for (const char character : value) {
|
for (const char character : value) {
|
||||||
if (std::isalnum(static_cast<unsigned char>(character)) != 0) {
|
if (std::isalnum(static_cast<unsigned char>(character)) != 0) {
|
||||||
out.push_back(static_cast<char>(
|
out.push_back(static_cast<char>(
|
||||||
std::tolower(static_cast<unsigned char>(character))));
|
std::tolower(static_cast<unsigned char>(character))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
@@ -70,37 +69,7 @@ std::string GenerateDateOfBirth(std::mt19937& rng) {
|
|||||||
static_cast<unsigned>(birth_ymd.month()),
|
static_cast<unsigned>(birth_ymd.month()),
|
||||||
static_cast<unsigned>(birth_ymd.day()));
|
static_cast<unsigned>(birth_ymd.day()));
|
||||||
}
|
}
|
||||||
|
} // namespace
|
||||||
std::optional<Name> SampleName(
|
|
||||||
const ForenamesByCountryMap& forenames_by_country,
|
|
||||||
const SurnamesByCountryMap& surnames_by_country,
|
|
||||||
const std::string& iso3166_1, std::mt19937& rng) {
|
|
||||||
const auto forenames_it = forenames_by_country.find(iso3166_1);
|
|
||||||
const auto surnames_it = surnames_by_country.find(iso3166_1);
|
|
||||||
|
|
||||||
if (forenames_it == forenames_by_country.end() ||
|
|
||||||
surnames_it == surnames_by_country.end() ||
|
|
||||||
forenames_it->second.empty() || surnames_it->second.empty()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ForenameList& forenames = forenames_it->second;
|
|
||||||
const SurnameList& surnames = surnames_it->second;
|
|
||||||
|
|
||||||
std::uniform_int_distribution<size_t> forename_dist(0, forenames.size() - 1);
|
|
||||||
std::uniform_int_distribution<size_t> surname_dist(0, surnames.size() - 1);
|
|
||||||
|
|
||||||
auto forename_it = forenames.begin();
|
|
||||||
std::advance(forename_it, forename_dist(rng));
|
|
||||||
|
|
||||||
auto surname_it = surnames.begin();
|
|
||||||
std::advance(surname_it, surname_dist(rng));
|
|
||||||
|
|
||||||
return Name{.first_name = forename_it->name,
|
|
||||||
.last_name = *surname_it,
|
|
||||||
.gender = forename_it->gender};
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void BiergartenPipelineOrchestrator::GenerateUsers(
|
void BiergartenPipelineOrchestrator::GenerateUsers(
|
||||||
std::span<const EnrichedCity> cities) {
|
std::span<const EnrichedCity> cities) {
|
||||||
@@ -108,17 +77,17 @@ void BiergartenPipelineOrchestrator::GenerateUsers(
|
|||||||
.phase = PipelinePhase::UserGeneration,
|
.phase = PipelinePhase::UserGeneration,
|
||||||
.message = "=== SAMPLE USER GENERATION ==="});
|
.message = "=== SAMPLE USER GENERATION ==="});
|
||||||
|
|
||||||
const PersonasList& personas = curated_data_service_->LoadPersonas();
|
const std::vector<UserPersona> personas =
|
||||||
|
curated_data_service_->LoadPersonas("personas.json");
|
||||||
|
|
||||||
if (personas.empty()) {
|
if (personas.empty()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"No personas available in personas.json for user generation");
|
"No personas available in personas.json for user generation");
|
||||||
}
|
}
|
||||||
|
|
||||||
const ForenamesByCountryMap& forenames_by_country =
|
const NamesByCountry names_by_country =
|
||||||
curated_data_service_->LoadForenamesByCountry();
|
curated_data_service_->LoadNamesByCountry(
|
||||||
const SurnamesByCountryMap& surnames_by_country =
|
"forenames-by-country.json", "surnames-by-country.json");
|
||||||
curated_data_service_->LoadSurnamesByCountry();
|
|
||||||
|
|
||||||
std::mt19937 rng(std::random_device{}());
|
std::mt19937 rng(std::random_device{}());
|
||||||
std::uniform_int_distribution<size_t> persona_dist(0, personas.size() - 1);
|
std::uniform_int_distribution<size_t> persona_dist(0, personas.size() - 1);
|
||||||
@@ -126,18 +95,19 @@ void BiergartenPipelineOrchestrator::GenerateUsers(
|
|||||||
generated_users_.clear();
|
generated_users_.clear();
|
||||||
size_t skipped_count = 0;
|
size_t skipped_count = 0;
|
||||||
size_t export_failed_count = 0;
|
size_t export_failed_count = 0;
|
||||||
std::unordered_set<std::string> used_email_local_parts;
|
|
||||||
|
|
||||||
const auto generate_record =
|
const auto generate_record =
|
||||||
[this, &rng, &skipped_count, &used_email_local_parts](
|
[this, &rng, &skipped_count](
|
||||||
const EnrichedCity& city, const UserPersona& persona,
|
const EnrichedCity& city, const UserPersona& persona,
|
||||||
const Name& sampled_name) -> std::optional<UserRecord> {
|
const Name& sampled_name) -> std::optional<UserRecord> {
|
||||||
try {
|
try {
|
||||||
|
std::unordered_set<std::string> used_email_local_parts;
|
||||||
|
|
||||||
const UserResult user =
|
const UserResult user =
|
||||||
generator_->GenerateUser(city, persona, sampled_name);
|
generator_->GenerateUser(city, persona, sampled_name);
|
||||||
|
|
||||||
return UserRecord{
|
return UserRecord{
|
||||||
.address = UserAddress{.city = city.location},
|
.location = city.location,
|
||||||
.user = user,
|
.user = user,
|
||||||
.email = BuildEmail(sampled_name, used_email_local_parts),
|
.email = BuildEmail(sampled_name, used_email_local_parts),
|
||||||
.date_of_birth = GenerateDateOfBirth(rng),
|
.date_of_birth = GenerateDateOfBirth(rng),
|
||||||
@@ -155,26 +125,25 @@ void BiergartenPipelineOrchestrator::GenerateUsers(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const auto export_record = [this,
|
const auto export_record = [this,
|
||||||
&export_failed_count](const UserRecord& record) {
|
&export_failed_count](const UserRecord& record) {
|
||||||
try {
|
try {
|
||||||
exporter_->ProcessRecord(record);
|
exporter_->ProcessRecord(record);
|
||||||
} catch (const std::exception& export_exception) {
|
} catch (const std::exception& export_exception) {
|
||||||
++export_failed_count;
|
++export_failed_count;
|
||||||
|
|
||||||
logger_->Log({.level = LogLevel::Warn,
|
logger_->Log(
|
||||||
.phase = PipelinePhase::UserGeneration,
|
{.level = LogLevel::Warn,
|
||||||
.message = std::format(
|
.phase = PipelinePhase::UserGeneration,
|
||||||
"[Pipeline] Generated user for '{}' ({}) but "
|
.message = std::format("[Pipeline] Generated user for '{}' ({}) but "
|
||||||
"SQLite export failed: {}",
|
"SQLite export failed: {}",
|
||||||
record.address.city.city, record.address.city.country,
|
record.location.city, record.location.country,
|
||||||
export_exception.what())});
|
export_exception.what())});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& city : cities) {
|
for (const auto& city : cities) {
|
||||||
const std::optional<Name> sampled_name =
|
const std::optional<Name> sampled_name =
|
||||||
SampleName(forenames_by_country, surnames_by_country,
|
names_by_country.SampleName(city.location.iso3166_1, rng);
|
||||||
city.location.iso3166_1, rng);
|
|
||||||
|
|
||||||
if (!sampled_name.has_value()) {
|
if (!sampled_name.has_value()) {
|
||||||
++skipped_count;
|
++skipped_count;
|
||||||
@@ -202,11 +171,11 @@ void BiergartenPipelineOrchestrator::GenerateUsers(
|
|||||||
|
|
||||||
if (skipped_count > 0) {
|
if (skipped_count > 0) {
|
||||||
logger_->Log(
|
logger_->Log(
|
||||||
{.level = LogLevel::Warn,
|
{.level = LogLevel::Warn,
|
||||||
.phase = PipelinePhase::UserGeneration,
|
.phase = PipelinePhase::UserGeneration,
|
||||||
.message = std::format(
|
.message = std::format(
|
||||||
"[Pipeline] Skipped {} city/cities during user generation",
|
"[Pipeline] Skipped {} city/cities during user generation",
|
||||||
skipped_count)});
|
skipped_count)});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (export_failed_count > 0) {
|
if (export_failed_count > 0) {
|
||||||
|
|||||||
@@ -7,26 +7,26 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <format>
|
#include <format>
|
||||||
|
|
||||||
|
#include "../../includes/json_handling/pretty_print.h"
|
||||||
#include "biergarten_pipeline_orchestrator.h"
|
#include "biergarten_pipeline_orchestrator.h"
|
||||||
#include "json_handling/pretty_print.h"
|
|
||||||
#include "services/logging/logger.h"
|
#include "services/logging/logger.h"
|
||||||
|
|
||||||
void BiergartenPipelineOrchestrator::LogResults() const {
|
void BiergartenPipelineOrchestrator::LogResults() const {
|
||||||
boost::json::array brewery_output;
|
boost::json::array brewery_output;
|
||||||
|
|
||||||
for (const auto& [address, brewery] : generated_breweries_) {
|
for (const auto& [location, brewery] : generated_breweries_) {
|
||||||
const City& location = address.city;
|
|
||||||
brewery_output.push_back(boost::json::object{
|
brewery_output.push_back(boost::json::object{
|
||||||
{"name_en", brewery.name_en},
|
{"name_en", brewery.name_en},
|
||||||
{"description_en", brewery.description_en},
|
{"description_en", brewery.description_en},
|
||||||
{"name_local", brewery.name_local},
|
{"name_local", brewery.name_local},
|
||||||
{"description_local", brewery.description_local},
|
{"description_local", brewery.description_local},
|
||||||
{"postal_code", address.postal_code},
|
|
||||||
{"location", boost::json::object{
|
{"location", boost::json::object{
|
||||||
{"city", location.city},
|
{"city", location.city},
|
||||||
{"country", location.country},
|
{"country", location.country},
|
||||||
{"state_province", location.state_province},
|
{"state_province", location.state_province},
|
||||||
{"iso3166_2", location.iso3166_2},
|
{"iso3166_2", location.iso3166_2},
|
||||||
|
{"latitude", location.latitude},
|
||||||
|
{"longitude", location.longitude},
|
||||||
}}});
|
}}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,22 +6,24 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
#include "biergarten_pipeline_orchestrator.h"
|
#include "biergarten_pipeline_orchestrator.h"
|
||||||
#include "services/curated_data/curated_json_data_service.h"
|
#include "json_handling/json_loader.h"
|
||||||
#include "services/logging/logger.h"
|
#include "services/logging/logger.h"
|
||||||
|
|
||||||
std::vector<City>
|
std::vector<Location>
|
||||||
BiergartenPipelineOrchestrator::QueryCitiesWithCountries() {
|
BiergartenPipelineOrchestrator::QueryCitiesWithCountries() {
|
||||||
logger_->Log({.level = LogLevel::Info,
|
logger_->Log({.level = LogLevel::Info,
|
||||||
.phase = PipelinePhase::Startup,
|
.phase = PipelinePhase::Startup,
|
||||||
.message = "=== GEOGRAPHIC DATA OVERVIEW ==="});
|
.message = "=== GEOGRAPHIC DATA OVERVIEW ==="});
|
||||||
|
|
||||||
const std::vector<City>& all_locations =
|
const std::filesystem::path locations_path = "locations.json";
|
||||||
curated_data_service_->LoadCities();
|
|
||||||
|
auto all_locations = curated_data_service_->LoadLocations(locations_path);
|
||||||
|
|
||||||
const size_t sample_count = std::min(
|
const size_t sample_count = std::min(
|
||||||
static_cast<size_t>(application_options_.pipeline.location_count),
|
static_cast<size_t>(application_options_.pipeline.location_count),
|
||||||
@@ -31,7 +33,7 @@ BiergartenPipelineOrchestrator::QueryCitiesWithCountries() {
|
|||||||
static_cast<std::iter_difference_t<decltype(all_locations.cbegin())>>(
|
static_cast<std::iter_difference_t<decltype(all_locations.cbegin())>>(
|
||||||
sample_count);
|
sample_count);
|
||||||
|
|
||||||
std::vector<City> sampled_locations;
|
std::vector<Location> sampled_locations;
|
||||||
sampled_locations.reserve(sample_count);
|
sampled_locations.reserve(sample_count);
|
||||||
|
|
||||||
std::random_device random_generator;
|
std::random_device random_generator;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ bool BiergartenPipelineOrchestrator::Run() {
|
|||||||
try {
|
try {
|
||||||
exporter_->Initialize();
|
exporter_->Initialize();
|
||||||
|
|
||||||
std::vector<City> cities = QueryCitiesWithCountries();
|
std::vector<Location> cities = QueryCitiesWithCountries();
|
||||||
std::vector<EnrichedCity> enriched;
|
std::vector<EnrichedCity> enriched;
|
||||||
enriched.reserve(cities.size());
|
enriched.reserve(cities.size());
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ static std::string FormatLocalLanguageCodes(
|
|||||||
static constexpr int kBreweryInitialMaxTokens = 2800;
|
static constexpr int kBreweryInitialMaxTokens = 2800;
|
||||||
|
|
||||||
BreweryResult LlamaGenerator::GenerateBrewery(
|
BreweryResult LlamaGenerator::GenerateBrewery(
|
||||||
const City& location, const std::string& region_context) {
|
const Location& location, const std::string& region_context) {
|
||||||
/**
|
/**
|
||||||
* Preprocess and truncate region context to manageable size
|
* Preprocess and truncate region context to manageable size
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -30,13 +30,10 @@ UserResult LlamaGenerator::GenerateUser(const EnrichedCity& city,
|
|||||||
const std::string system_prompt = prompt_directory_->Load("USER_GENERATION");
|
const std::string system_prompt = prompt_directory_->Load("USER_GENERATION");
|
||||||
|
|
||||||
std::string user_prompt = std::format(
|
std::string user_prompt = std::format(
|
||||||
"## NAME:\n\n{} {}\n\n"
|
"## NAME:\n{} {}\n\n## GENDER:\n{}\n\n## CITY:\n{}\n\n## "
|
||||||
"## GENDER:\n\n{}\n\n"
|
"COUNTRY:\n{}\n\n"
|
||||||
"## CITY:\n\n{}\n\n"
|
"## PERSONA:\n{}\n\n## PERSONA DESCRIPTION:\n{}\n\n## STYLE "
|
||||||
"## COUNTRY:\n\n{}\n\n"
|
"AFFINITIES:\n{}",
|
||||||
"## PERSONA:\n\n{}\n\n"
|
|
||||||
"## PERSONA DESCRIPTION:\n\n{}\n\n"
|
|
||||||
"## STYLE AFFINITIES:\n\n{}",
|
|
||||||
name.first_name, name.last_name, name.gender, city.location.city,
|
name.first_name, name.last_name, name.gender, city.location.city,
|
||||||
city.location.country, persona.name, persona.description,
|
city.location.country, persona.name, persona.description,
|
||||||
style_affinities);
|
style_affinities);
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <llama.h>
|
|
||||||
|
|
||||||
#include "data_generation/llama_generator_helpers.h"
|
#include "data_generation/llama_generator_helpers.h"
|
||||||
|
#include "llama.h"
|
||||||
namespace {
|
namespace {
|
||||||
/**
|
/**
|
||||||
* String trimming: removes leading and trailing whitespace
|
* String trimming: removes leading and trailing whitespace
|
||||||
|
|||||||
@@ -14,10 +14,9 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <llama.h>
|
|
||||||
|
|
||||||
#include "data_generation/llama_generator.h"
|
#include "data_generation/llama_generator.h"
|
||||||
#include "data_generation/llama_generator_helpers.h"
|
#include "data_generation/llama_generator_helpers.h"
|
||||||
|
#include "llama.h"
|
||||||
|
|
||||||
static constexpr size_t kPromptTokenSlack = 8;
|
static constexpr size_t kPromptTokenSlack = 8;
|
||||||
// Minimum tokens to keep when using top-p sampling. Ensures at least one
|
// Minimum tokens to keep when using top-p sampling. Ensures at least one
|
||||||
|
|||||||
@@ -11,9 +11,8 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <llama.h>
|
|
||||||
|
|
||||||
#include "data_model/models.h"
|
#include "data_model/models.h"
|
||||||
|
#include "llama.h"
|
||||||
|
|
||||||
static constexpr uint32_t kMaxContextSize = 32768U;
|
static constexpr uint32_t kMaxContextSize = 32768U;
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <ggml-backend.h>
|
|
||||||
#include <llama.h>
|
|
||||||
|
|
||||||
#include "data_generation/llama_generator.h"
|
#include "data_generation/llama_generator.h"
|
||||||
|
#include "ggml-backend.h"
|
||||||
|
#include "llama.h"
|
||||||
|
|
||||||
// Maximum batch size for decode operations. Capping the batch prevents
|
// Maximum batch size for decode operations. Capping the batch prevents
|
||||||
// excessive memory allocation while maintaining inference performance.
|
// excessive memory allocation while maintaining inference performance.
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
|
|
||||||
#include "data_generation/mock_generator.h"
|
#include "data_generation/mock_generator.h"
|
||||||
|
|
||||||
size_t MockGenerator::DeterministicHash(const City& location) {
|
size_t MockGenerator::DeterministicHash(const Location& location) {
|
||||||
size_t seed = 0;
|
size_t seed = 0;
|
||||||
boost::hash_combine(seed, location.city);
|
boost::hash_combine(seed, location.city);
|
||||||
boost::hash_combine(seed, location.country);
|
boost::hash_combine(seed, location.country);
|
||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t MockGenerator::DeterministicHash(const City& location,
|
size_t MockGenerator::DeterministicHash(const Location& location,
|
||||||
const UserPersona& persona,
|
const UserPersona& persona,
|
||||||
const Name& name) {
|
const Name& name) {
|
||||||
size_t seed = DeterministicHash(location);
|
size_t seed = DeterministicHash(location);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include "data_generation/mock_generator.h"
|
#include "data_generation/mock_generator.h"
|
||||||
|
|
||||||
BreweryResult MockGenerator::GenerateBrewery(
|
BreweryResult MockGenerator::GenerateBrewery(
|
||||||
const City& location, const std::string& /*region_context*/) {
|
const Location& location, const std::string& /*region_context*/) {
|
||||||
const size_t hash = DeterministicHash(location);
|
const size_t hash = DeterministicHash(location);
|
||||||
|
|
||||||
const std::string_view adjective =
|
const std::string_view adjective =
|
||||||
|
|||||||
40
tooling/pipeline/src/data_model/names_by_country.cc
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* @file data_model/names_by_country.cc
|
||||||
|
* @brief NamesByCountry::SampleName() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "data_model/names_by_country.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
NamesByCountry::NamesByCountry(
|
||||||
|
std::unordered_map<std::string, std::vector<ForenameEntry>>
|
||||||
|
forenames_by_country,
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>>
|
||||||
|
surnames_by_country)
|
||||||
|
: forenames_by_country_(std::move(forenames_by_country)),
|
||||||
|
surnames_by_country_(std::move(surnames_by_country)) {}
|
||||||
|
|
||||||
|
std::optional<Name> NamesByCountry::SampleName(const std::string& iso3166_1,
|
||||||
|
std::mt19937& rng) const {
|
||||||
|
const auto forenames_it = forenames_by_country_.find(iso3166_1);
|
||||||
|
const auto surnames_it = surnames_by_country_.find(iso3166_1);
|
||||||
|
|
||||||
|
if (forenames_it == forenames_by_country_.end() ||
|
||||||
|
surnames_it == surnames_by_country_.end() ||
|
||||||
|
forenames_it->second.empty() || surnames_it->second.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<ForenameEntry>& forenames = forenames_it->second;
|
||||||
|
const std::vector<std::string>& surnames = surnames_it->second;
|
||||||
|
|
||||||
|
std::uniform_int_distribution<size_t> forename_dist(0, forenames.size() - 1);
|
||||||
|
std::uniform_int_distribution<size_t> surname_dist(0, surnames.size() - 1);
|
||||||
|
|
||||||
|
const ForenameEntry& forename = forenames[forename_dist(rng)];
|
||||||
|
|
||||||
|
return Name{.first_name = forename.name,
|
||||||
|
.last_name = surnames[surname_dist(rng)],
|
||||||
|
.gender = forename.gender};
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* @file json_handling/json_loader.cc
|
* @file json_handling/json_loader.cc
|
||||||
* @brief Parses curated location JSON input into strongly typed City
|
* @brief Parses curated location JSON input into strongly typed Location
|
||||||
* records with strict field validation and descriptive error reporting.
|
* records with strict field validation and descriptive error reporting.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "services/curated_data/curated_json_data_service.h"
|
#include "json_handling/json_loader.h"
|
||||||
|
|
||||||
#include <boost/json.hpp>
|
#include <boost/json.hpp>
|
||||||
#include <format>
|
#include <format>
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "services/logging/logger.h"
|
#include "services/logging/logger.h"
|
||||||
|
|
||||||
@@ -106,89 +106,51 @@ std::string ReadFirstOfStringArray(const boost::json::object& object,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
CuratedJsonDataService::CuratedJsonDataService(CuratedDataFilePaths filepaths)
|
std::vector<Location> JsonLoader::LoadLocations(
|
||||||
: filepaths_(std::move(filepaths)) {}
|
const std::filesystem::path& filepath) {
|
||||||
|
const boost::json::value root = ParseJsonFile(filepath, "locations");
|
||||||
const CityList& CuratedJsonDataService::LoadCities() {
|
|
||||||
if (!cache_.locations.empty()) {
|
|
||||||
return cache_.locations;
|
|
||||||
}
|
|
||||||
|
|
||||||
const boost::json::value root =
|
|
||||||
ParseJsonFile(filepaths_.cities_path, "cities");
|
|
||||||
|
|
||||||
if (!root.is_array()) {
|
if (!root.is_array()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Invalid cities JSON: root element must be an array");
|
"Invalid locations JSON: root element must be an array");
|
||||||
}
|
}
|
||||||
|
|
||||||
CityList locations;
|
std::vector<Location> locations;
|
||||||
const auto& items = root.as_array();
|
const auto& items = root.as_array();
|
||||||
locations.reserve(items.size());
|
locations.reserve(items.size());
|
||||||
|
|
||||||
for (const auto& item : items) {
|
for (const auto& item : items) {
|
||||||
if (!item.is_object()) {
|
if (!item.is_object()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Invalid cities JSON: each entry must be an object");
|
"Invalid locations JSON: each entry must be an object");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& object = item.as_object();
|
const auto& object = item.as_object();
|
||||||
|
locations.push_back(Location{
|
||||||
const boost::json::value* postal_code = object.if_contains("postal_code");
|
|
||||||
if (postal_code == nullptr || !postal_code->is_object()) {
|
|
||||||
throw std::runtime_error("Missing or invalid object field: postal_code");
|
|
||||||
}
|
|
||||||
|
|
||||||
locations.push_back(City{
|
|
||||||
.city = ReadRequiredString(object, "city"),
|
.city = ReadRequiredString(object, "city"),
|
||||||
.state_province = ReadRequiredString(object, "state_province"),
|
.state_province = ReadRequiredString(object, "state_province"),
|
||||||
.iso3166_2 = ReadRequiredString(object, "iso3166_2"),
|
.iso3166_2 = ReadRequiredString(object, "iso3166_2"),
|
||||||
.country = ReadRequiredString(object, "country"),
|
.country = ReadRequiredString(object, "country"),
|
||||||
.iso3166_1 = ReadRequiredString(object, "iso3166_1"),
|
.iso3166_1 = ReadRequiredString(object, "iso3166_1"),
|
||||||
.local_languages = ReadRequiredStringArray(object, "local_languages"),
|
.local_languages = ReadRequiredStringArray(object, "local_languages"),
|
||||||
.postal_regex =
|
.latitude = ReadRequiredNumber(object, "latitude"),
|
||||||
ReadRequiredStringArray(postal_code->as_object(), "city_regex"),
|
.longitude = ReadRequiredNumber(object, "longitude"),
|
||||||
.postal_code_examples =
|
|
||||||
ReadRequiredStringArray(postal_code->as_object(), "examples"),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto location : locations) {
|
return locations;
|
||||||
std::cout << "Location: " << location.city << ", "
|
|
||||||
<< location.state_province << ", " << location.iso3166_2 << ", "
|
|
||||||
<< location.country << ", " << location.iso3166_1 << "\n";
|
|
||||||
|
|
||||||
for (const auto& regex : location.postal_regex) {
|
|
||||||
std::cout << " Postal regex: " << regex << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& example : location.postal_code_examples) {
|
|
||||||
std::cout << " Postal example: " << example << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& lang : location.local_languages) {
|
|
||||||
std::cout << " Local language: " << lang << "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_.locations = std::move(locations);
|
|
||||||
return cache_.locations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PersonasList& CuratedJsonDataService::LoadPersonas() {
|
std::vector<UserPersona> JsonLoader::LoadPersonas(
|
||||||
if (!cache_.personas.empty()) {
|
const std::filesystem::path& filepath) {
|
||||||
return cache_.personas;
|
const boost::json::value root = ParseJsonFile(filepath, "personas");
|
||||||
}
|
|
||||||
|
|
||||||
const boost::json::value root =
|
|
||||||
ParseJsonFile(filepaths_.personas_path, "personas");
|
|
||||||
|
|
||||||
if (!root.is_array()) {
|
if (!root.is_array()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Invalid personas JSON: root element must be an array");
|
"Invalid personas JSON: root element must be an array");
|
||||||
}
|
}
|
||||||
|
|
||||||
PersonasList personas;
|
std::vector<UserPersona> personas;
|
||||||
const auto& items = root.as_array();
|
const auto& items = root.as_array();
|
||||||
personas.reserve(items.size());
|
personas.reserve(items.size());
|
||||||
|
|
||||||
@@ -206,30 +168,30 @@ const PersonasList& CuratedJsonDataService::LoadPersonas() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_.personas = std::move(personas);
|
return personas;
|
||||||
return cache_.personas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ForenamesByCountryMap& CuratedJsonDataService::LoadForenamesByCountry() {
|
NamesByCountry JsonLoader::LoadNamesByCountry(
|
||||||
if (!cache_.forenames_by_country.empty()) {
|
const std::filesystem::path& forenames_filepath,
|
||||||
return cache_.forenames_by_country;
|
const std::filesystem::path& surnames_filepath) {
|
||||||
}
|
const boost::json::value forenames_root =
|
||||||
|
ParseJsonFile(forenames_filepath, "forenames-by-country");
|
||||||
|
const boost::json::value surnames_root =
|
||||||
|
ParseJsonFile(surnames_filepath, "surnames-by-country");
|
||||||
|
|
||||||
const boost::json::value root =
|
if (!forenames_root.is_object() || !surnames_root.is_object()) {
|
||||||
ParseJsonFile(filepaths_.forenames_path, "forenames-by-country");
|
|
||||||
|
|
||||||
if (!root.is_object()) {
|
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Invalid forenames-by-country JSON: root element must be an object "
|
"Invalid names-by-country JSON: root element must be an object "
|
||||||
"keyed by ISO 3166-1 country code");
|
"keyed by ISO 3166-1 country code");
|
||||||
}
|
}
|
||||||
|
|
||||||
ForenamesByCountryMap forenames_by_country;
|
std::unordered_map<std::string, std::vector<ForenameEntry>>
|
||||||
for (const auto& [country_code, regions] : root.as_object()) {
|
forenames_by_country;
|
||||||
|
for (const auto& [country_code, regions] : forenames_root.as_object()) {
|
||||||
if (!regions.is_array()) {
|
if (!regions.is_array()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ForenameList entries;
|
std::vector<ForenameEntry> entries;
|
||||||
for (const auto& region : regions.as_array()) {
|
for (const auto& region : regions.as_array()) {
|
||||||
if (!region.is_object()) {
|
if (!region.is_object()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -243,7 +205,7 @@ const ForenamesByCountryMap& CuratedJsonDataService::LoadForenamesByCountry() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto& name_object = name_value.as_object();
|
const auto& name_object = name_value.as_object();
|
||||||
entries.emplace_back(ForenameEntry{
|
entries.push_back(ForenameEntry{
|
||||||
.name =
|
.name =
|
||||||
ReadFirstOfStringArray(name_object, "romanized", "localized"),
|
ReadFirstOfStringArray(name_object, "romanized", "localized"),
|
||||||
.gender = ReadRequiredString(name_object, "gender"),
|
.gender = ReadRequiredString(name_object, "gender"),
|
||||||
@@ -253,39 +215,22 @@ const ForenamesByCountryMap& CuratedJsonDataService::LoadForenamesByCountry() {
|
|||||||
forenames_by_country.emplace(country_code, std::move(entries));
|
forenames_by_country.emplace(country_code, std::move(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_.forenames_by_country = std::move(forenames_by_country);
|
std::unordered_map<std::string, std::vector<std::string>> surnames_by_country;
|
||||||
return cache_.forenames_by_country;
|
for (const auto& [country_code, name_entries] : surnames_root.as_object()) {
|
||||||
}
|
if (!name_entries.is_array()) {
|
||||||
|
continue;
|
||||||
const SurnamesByCountryMap& CuratedJsonDataService::LoadSurnamesByCountry() {
|
}
|
||||||
if (!cache_.surnames_by_country.empty()) {
|
std::vector<std::string> surnames;
|
||||||
return cache_.surnames_by_country;
|
|
||||||
}
|
|
||||||
|
|
||||||
const boost::json::value root =
|
|
||||||
ParseJsonFile(filepaths_.surnames_path, "surnames-by-country");
|
|
||||||
|
|
||||||
if (!root.is_object()) {
|
|
||||||
throw std::runtime_error(
|
|
||||||
"Invalid surnames-by-country JSON: root element must be an object "
|
|
||||||
"keyed by ISO 3166-1 country code");
|
|
||||||
}
|
|
||||||
|
|
||||||
SurnamesByCountryMap surnames_by_country;
|
|
||||||
for (const auto& [country_code, name_entries] : root.as_object()) {
|
|
||||||
if (!name_entries.is_array()) continue;
|
|
||||||
|
|
||||||
SurnameList surnames;
|
|
||||||
for (const auto& name_value : name_entries.as_array()) {
|
for (const auto& name_value : name_entries.as_array()) {
|
||||||
if (!name_value.is_object()) {
|
if (!name_value.is_object()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
surnames.emplace_back(ReadFirstOfStringArray(name_value.as_object(),
|
surnames.push_back(ReadFirstOfStringArray(name_value.as_object(),
|
||||||
"romanized", "localized"));
|
"romanized", "localized"));
|
||||||
}
|
}
|
||||||
surnames_by_country.emplace(country_code, std::move(surnames));
|
surnames_by_country.emplace(country_code, std::move(surnames));
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_.surnames_by_country = std::move(surnames_by_country);
|
return NamesByCountry(std::move(forenames_by_country),
|
||||||
return cache_.surnames_by_country;
|
std::move(surnames_by_country));
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,27 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "biergarten_pipeline.h"
|
#include "biergarten_pipeline_orchestrator.h"
|
||||||
|
#include "concurrency/bounded_channel.h"
|
||||||
|
#include "data_generation/llama_generator.h"
|
||||||
|
#include "data_generation/mock_generator.h"
|
||||||
|
#include "data_generation/prompt_formatting/gemma4_jinja_prompt_formatter.h"
|
||||||
|
#include "data_model/models.h"
|
||||||
|
#include "json_handling/json_loader.h"
|
||||||
|
#include "llama_backend_state.h"
|
||||||
|
#include "services/curated_data/curated_data_service.h"
|
||||||
|
#include "services/database/export_service.h"
|
||||||
|
#include "services/database/sqlite_export_service.h"
|
||||||
|
#include "services/datetime/timer.h"
|
||||||
|
#include "services/enrichment/enrichment_service.h"
|
||||||
|
#include "services/enrichment/mock_enrichment.h"
|
||||||
|
#include "services/enrichment/wikipedia_service.h"
|
||||||
|
#include "services/logging/log_dispatcher.h"
|
||||||
|
#include "services/logging/log_entry.h"
|
||||||
|
#include "services/logging/log_producer.h"
|
||||||
|
#include "services/logging/logger.h"
|
||||||
|
#include "services/prompting/prompt_directory.h"
|
||||||
|
#include "web_client/http_web_client.h"
|
||||||
|
|
||||||
namespace di = boost::di;
|
namespace di = boost::di;
|
||||||
|
|
||||||
@@ -29,7 +49,7 @@ int main(const int argc, char** argv) {
|
|||||||
spdlog::set_pattern("│ %Y-%m-%d %H:%M:%S.%e │ %^%-7l%$ │ %v");
|
spdlog::set_pattern("│ %Y-%m-%d %H:%M:%S.%e │ %^%-7l%$ │ %v");
|
||||||
BoundedChannel<LogEntry> log_channel(kLogMaxCount);
|
BoundedChannel<LogEntry> log_channel(kLogMaxCount);
|
||||||
|
|
||||||
auto log_dispatcher = //
|
auto log_dispatcher = //
|
||||||
std::make_unique<LogDispatcher>(log_channel);
|
std::make_unique<LogDispatcher>(log_channel);
|
||||||
std::shared_ptr<ILogger> log_producer =
|
std::shared_ptr<ILogger> log_producer =
|
||||||
std::make_shared<LogProducer>(log_channel);
|
std::make_shared<LogProducer>(log_channel);
|
||||||
@@ -86,45 +106,22 @@ int main(const int argc, char** argv) {
|
|||||||
di::bind<ApplicationOptions>().to(options),
|
di::bind<ApplicationOptions>().to(options),
|
||||||
di::bind<std::string>().to(model_path),
|
di::bind<std::string>().to(model_path),
|
||||||
di::bind<IExportService>().to<SqliteExportService>(),
|
di::bind<IExportService>().to<SqliteExportService>(),
|
||||||
di::bind<ICuratedDataService>().to(
|
di::bind<ICuratedDataService>().to<JsonLoader>(),
|
||||||
[options, &log_producer]() -> std::unique_ptr<ICuratedDataService> {
|
|
||||||
if (options.generator.use_mocked) {
|
|
||||||
log_producer->Log({.level = LogLevel::Info,
|
|
||||||
.phase = PipelinePhase::Startup,
|
|
||||||
.message = "Curated data: mock"});
|
|
||||||
|
|
||||||
return std::make_unique<MockCuratedDataService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
log_producer->Log({.level = LogLevel::Info,
|
|
||||||
.phase = PipelinePhase::Startup,
|
|
||||||
.message = "Curated data: JsonLoader"});
|
|
||||||
|
|
||||||
return std::make_unique<CuratedJsonDataService>(
|
|
||||||
CuratedDataFilePaths{
|
|
||||||
.cities_path = "cities.json",
|
|
||||||
.personas_path = "personas.json",
|
|
||||||
.forenames_path = "forenames-by-country.json",
|
|
||||||
.surnames_path = "surnames-by-country.json",
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
di::bind<IPostalCodeService>().to<MockPostalCodeService>(),
|
|
||||||
|
|
||||||
di::bind<IPromptFormatter>().to([options, log_producer] {
|
di::bind<IPromptFormatter>().to([options, log_producer] {
|
||||||
if (options.generator.use_mocked) {
|
if (options.generator.use_mocked) {
|
||||||
{
|
{
|
||||||
log_producer->Log(
|
log_producer->Log(
|
||||||
{.level = LogLevel::Info,
|
{.level = LogLevel::Info,
|
||||||
.phase = PipelinePhase::Startup,
|
.phase = PipelinePhase::Startup,
|
||||||
.message = "Prompt formatter: none (mock mode)"});
|
.message = "Prompt formatter: none (mock mode)"});
|
||||||
}
|
}
|
||||||
return std::unique_ptr<IPromptFormatter>(nullptr);
|
return std::unique_ptr<IPromptFormatter>(nullptr);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
log_producer->Log(
|
log_producer->Log(
|
||||||
{.level = LogLevel::Info,
|
{.level = LogLevel::Info,
|
||||||
.phase = PipelinePhase::Startup,
|
.phase = PipelinePhase::Startup,
|
||||||
.message = "Prompt formatter: Gemma4JinjaPromptFormatter"});
|
.message = "Prompt formatter: Gemma4JinjaPromptFormatter"});
|
||||||
}
|
}
|
||||||
return std::unique_ptr<IPromptFormatter>(
|
return std::unique_ptr<IPromptFormatter>(
|
||||||
std::make_unique<Gemma4JinjaPromptFormatter>());
|
std::make_unique<Gemma4JinjaPromptFormatter>());
|
||||||
@@ -148,7 +145,7 @@ int main(const int argc, char** argv) {
|
|||||||
}),
|
}),
|
||||||
di::bind<IEnrichmentService>().to(
|
di::bind<IEnrichmentService>().to(
|
||||||
[options, &log_producer](
|
[options, &log_producer](
|
||||||
const auto& inj) -> std::unique_ptr<IEnrichmentService> {
|
const auto& inj) -> std::unique_ptr<IEnrichmentService> {
|
||||||
if (options.generator.use_mocked) {
|
if (options.generator.use_mocked) {
|
||||||
log_producer->Log({.level = LogLevel::Info,
|
log_producer->Log({.level = LogLevel::Info,
|
||||||
.phase = PipelinePhase::Startup,
|
.phase = PipelinePhase::Startup,
|
||||||
@@ -167,7 +164,8 @@ int main(const int argc, char** argv) {
|
|||||||
}),
|
}),
|
||||||
di::bind<DataGenerator>().to(
|
di::bind<DataGenerator>().to(
|
||||||
[&options, &model_path, &sampling, &prompt_directory,
|
[&options, &model_path, &sampling, &prompt_directory,
|
||||||
&log_producer](const auto& inj) -> std::unique_ptr<DataGenerator> {
|
&log_producer
|
||||||
|
](const auto& inj) -> std::unique_ptr<DataGenerator> {
|
||||||
if (options.generator.use_mocked) {
|
if (options.generator.use_mocked) {
|
||||||
log_producer->Log({.level = LogLevel::Info,
|
log_producer->Log({.level = LogLevel::Info,
|
||||||
.phase = PipelinePhase::Startup,
|
.phase = PipelinePhase::Startup,
|
||||||
@@ -176,28 +174,19 @@ int main(const int argc, char** argv) {
|
|||||||
return std::make_unique<MockGenerator>();
|
return std::make_unique<MockGenerator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef BIERGARTEN_MOCK_ONLY
|
|
||||||
// Unreachable: ParseArguments requires --mocked when
|
|
||||||
// BIERGARTEN_MOCK_ONLY is compiled in, so LlamaGenerator is
|
|
||||||
// never constructed and is not linked into this binary.
|
|
||||||
throw std::runtime_error(
|
|
||||||
"LlamaGenerator is unavailable in a BIERGARTEN_MOCK_ONLY "
|
|
||||||
"build");
|
|
||||||
#else
|
|
||||||
log_producer->Log(
|
log_producer->Log(
|
||||||
{.level = LogLevel::Info,
|
{.level = LogLevel::Info,
|
||||||
.phase = PipelinePhase::Startup,
|
.phase = PipelinePhase::Startup,
|
||||||
.message = std::format(
|
.message = std::format(
|
||||||
"Generator: LlamaGenerator | model={} | "
|
"Generator: LlamaGenerator | model={} | "
|
||||||
"temp={:.2f} top_p={:.2f} top_k={} n_ctx={} seed={}",
|
"temp={:.2f} top_p={:.2f} top_k={} n_ctx={} seed={}",
|
||||||
model_path, sampling.temperature, sampling.top_p,
|
model_path, sampling.temperature, sampling.top_p,
|
||||||
sampling.top_k, sampling.n_ctx, sampling.seed)});
|
sampling.top_k, sampling.n_ctx, sampling.seed)});
|
||||||
|
|
||||||
return std::make_unique<LlamaGenerator>(
|
return std::make_unique<LlamaGenerator>(
|
||||||
options, model_path, log_producer,
|
options, model_path, log_producer,
|
||||||
inj.template create<std::unique_ptr<IPromptFormatter>>(),
|
inj.template create<std::unique_ptr<IPromptFormatter>>(),
|
||||||
std::move(prompt_directory));
|
std::move(prompt_directory));
|
||||||
#endif
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const auto orchestrator =
|
const auto orchestrator =
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file services/curated_data/mock_curated_data_service.cc
|
|
||||||
* @brief Fixed in-memory location, persona, and name dataset for mock mode.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "services/curated_data/mock_curated_data_service.h"
|
|
||||||
|
|
||||||
MockCuratedDataService::MockCuratedDataService()
|
|
||||||
: locations_{
|
|
||||||
{.city = "Portland",
|
|
||||||
.state_province = "Oregon",
|
|
||||||
.iso3166_2 = "US-OR",
|
|
||||||
.country = "United States",
|
|
||||||
.iso3166_1 = "US",
|
|
||||||
.local_languages = {"en"},
|
|
||||||
.postal_regex = {"^972[0-9]{2}(?:-[0-9]{4})?$"},
|
|
||||||
.postal_code_examples = {"97201", "97294"}},
|
|
||||||
{.city = "Munich",
|
|
||||||
.state_province = "Bavaria",
|
|
||||||
.iso3166_2 = "DE-BY",
|
|
||||||
.country = "Germany",
|
|
||||||
.iso3166_1 = "DE",
|
|
||||||
.local_languages = {"de"},
|
|
||||||
.postal_regex = {"^8[01][0-9]{3}$"},
|
|
||||||
.postal_code_examples = {"80331", "81929"}},
|
|
||||||
{.city = "Lyon",
|
|
||||||
.state_province = "Auvergne-Rhone-Alpes",
|
|
||||||
.iso3166_2 = "FR-ARA",
|
|
||||||
.country = "France",
|
|
||||||
.iso3166_1 = "FR",
|
|
||||||
.local_languages = {"fr"},
|
|
||||||
.postal_regex = {"^6900[1-9]$"},
|
|
||||||
.postal_code_examples = {"69001", "69009"}},
|
|
||||||
{.city = "Brussels",
|
|
||||||
.state_province = "Brussels-Capital",
|
|
||||||
.iso3166_2 = "BE-BRU",
|
|
||||||
.country = "Belgium",
|
|
||||||
.iso3166_1 = "BE",
|
|
||||||
.local_languages = {"nl", "fr"},
|
|
||||||
.postal_regex = {"^1(?:0[0-9]{2}|1[0-9]{2}|20[0-9]|210)$"},
|
|
||||||
.postal_code_examples = {"1000", "1210"}},
|
|
||||||
},
|
|
||||||
personas_{
|
|
||||||
UserPersona{.name = "Hophead Explorer",
|
|
||||||
.description = "Chases hop-forward IPAs and seeks out "
|
|
||||||
"taprooms with rotating drafts.",
|
|
||||||
.style_affinities = {"IPA", "Pale Ale"}},
|
|
||||||
UserPersona{.name = "Lager Traditionalist",
|
|
||||||
.description = "Prefers clean, balanced lagers and "
|
|
||||||
"classic pub styles.",
|
|
||||||
.style_affinities = {"Lager", "Pilsner"}},
|
|
||||||
UserPersona{.name = "Sour Curious",
|
|
||||||
.description = "Seeks out wild ales, sours, and "
|
|
||||||
"barrel-aged experiments.",
|
|
||||||
.style_affinities = {"Sour", "Wild Ale"}},
|
|
||||||
},
|
|
||||||
forenames_by_country_{
|
|
||||||
{"US",
|
|
||||||
ForenameList{
|
|
||||||
ForenameEntry{.name = "James", .gender = "M"},
|
|
||||||
ForenameEntry{.name = "Mary", .gender = "F"},
|
|
||||||
}},
|
|
||||||
{"DE",
|
|
||||||
ForenameList{
|
|
||||||
ForenameEntry{.name = "Lukas", .gender = "M"},
|
|
||||||
ForenameEntry{.name = "Anna", .gender = "F"},
|
|
||||||
}},
|
|
||||||
{"FR",
|
|
||||||
ForenameList{
|
|
||||||
ForenameEntry{.name = "Lucas", .gender = "M"},
|
|
||||||
ForenameEntry{.name = "Camille", .gender = "F"},
|
|
||||||
}},
|
|
||||||
{"BE",
|
|
||||||
ForenameList{
|
|
||||||
ForenameEntry{.name = "Noah", .gender = "M"},
|
|
||||||
ForenameEntry{.name = "Emma", .gender = "F"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
surnames_by_country_{
|
|
||||||
{"US", SurnameList{"Smith", "Johnson"}},
|
|
||||||
{"DE", SurnameList{"Muller", "Schmidt"}},
|
|
||||||
{"FR", SurnameList{"Martin", "Bernard"}},
|
|
||||||
{"BE", SurnameList{"Peeters", "Janssens"}},
|
|
||||||
} {}
|
|
||||||
|
|
||||||
const CityList& MockCuratedDataService::LoadCities() { return locations_; }
|
|
||||||
|
|
||||||
const PersonasList& MockCuratedDataService::LoadPersonas() { return personas_; }
|
|
||||||
|
|
||||||
const ForenamesByCountryMap& MockCuratedDataService::LoadForenamesByCountry() {
|
|
||||||
return forenames_by_country_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SurnamesByCountryMap& MockCuratedDataService::LoadSurnamesByCountry() {
|
|
||||||
return surnames_by_country_;
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
#include "services/enrichment/wikipedia_service.h"
|
#include "services/enrichment/wikipedia_service.h"
|
||||||
|
|
||||||
std::string WikipediaEnrichmentService::GetLocationContext(
|
std::string WikipediaEnrichmentService::GetLocationContext(
|
||||||
const City& loc) {
|
const Location& loc) {
|
||||||
using namespace std::literals::chrono_literals;
|
using namespace std::literals::chrono_literals;
|
||||||
if (!this->client_) {
|
if (!this->client_) {
|
||||||
if (logger_) {
|
if (logger_) {
|
||||||
|
|||||||
@@ -14,11 +14,9 @@ void SqliteExportService::Finalize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
insert_user_address_stmt_.reset();
|
|
||||||
insert_user_stmt_.reset();
|
insert_user_stmt_.reset();
|
||||||
insert_brewery_address_stmt_.reset();
|
|
||||||
insert_brewery_stmt_.reset();
|
insert_brewery_stmt_.reset();
|
||||||
insert_city_stmt_.reset();
|
insert_location_stmt_.reset();
|
||||||
if (transaction_open_) {
|
if (transaction_open_) {
|
||||||
sqlite_export_service_internal::ExecSql(
|
sqlite_export_service_internal::ExecSql(
|
||||||
db_handle_, "COMMIT;", "Failed to commit SQLite transaction");
|
db_handle_, "COMMIT;", "Failed to commit SQLite transaction");
|
||||||
@@ -26,7 +24,7 @@ void SqliteExportService::Finalize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db_handle_.reset();
|
db_handle_.reset();
|
||||||
city_cache_.clear();
|
location_cache_.clear();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
RollbackAndCloseNoThrow();
|
RollbackAndCloseNoThrow();
|
||||||
throw;
|
throw;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ void ResetStatement(SqliteStatementHandle& statement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Bind(const SqliteStatementHandle& statement,
|
void Bind(const SqliteStatementHandle& statement,
|
||||||
const BoundParam<std::string_view>& param) {
|
const BindParam<std::string_view>& param) {
|
||||||
const auto byte_count = param.value.size();
|
const auto byte_count = param.value.size();
|
||||||
if (byte_count > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
|
if (byte_count > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
|
||||||
ThrowSqliteError(sqlite3_db_handle(statement.get()), param.action);
|
ThrowSqliteError(sqlite3_db_handle(statement.get()), param.action);
|
||||||
@@ -60,7 +60,7 @@ void Bind(const SqliteStatementHandle& statement,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Bind(const SqliteStatementHandle& statement,
|
void Bind(const SqliteStatementHandle& statement,
|
||||||
const BoundParam<double>& param) {
|
const BindParam<double>& param) {
|
||||||
if (sqlite3_bind_double(statement.get(), param.index, param.value) !=
|
if (sqlite3_bind_double(statement.get(), param.index, param.value) !=
|
||||||
SQLITE_OK) {
|
SQLITE_OK) {
|
||||||
ThrowSqliteError(sqlite3_db_handle(statement.get()), param.action);
|
ThrowSqliteError(sqlite3_db_handle(statement.get()), param.action);
|
||||||
@@ -68,7 +68,7 @@ void Bind(const SqliteStatementHandle& statement,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Bind(const SqliteStatementHandle& statement,
|
void Bind(const SqliteStatementHandle& statement,
|
||||||
const BoundParam<sqlite3_int64>& param) {
|
const BindParam<sqlite3_int64>& param) {
|
||||||
if (sqlite3_bind_int64(statement.get(), param.index, param.value) !=
|
if (sqlite3_bind_int64(statement.get(), param.index, param.value) !=
|
||||||
SQLITE_OK) {
|
SQLITE_OK) {
|
||||||
ThrowSqliteError(sqlite3_db_handle(statement.get()), param.action);
|
ThrowSqliteError(sqlite3_db_handle(statement.get()), param.action);
|
||||||
|
|||||||
@@ -28,39 +28,26 @@ std::filesystem::path SqliteExportService::BuildDatabasePath() const {
|
|||||||
|
|
||||||
void SqliteExportService::InitializeSchema() const {
|
void SqliteExportService::InitializeSchema() const {
|
||||||
sqlite_export_service_internal::ExecSql(
|
sqlite_export_service_internal::ExecSql(
|
||||||
db_handle_, sqlite_export_service_internal::kCreateCitiesTableSql,
|
db_handle_, sqlite_export_service_internal::kCreateLocationsTableSql,
|
||||||
"Failed to create SQLite cities table");
|
"Failed to create SQLite locations table");
|
||||||
sqlite_export_service_internal::ExecSql(
|
sqlite_export_service_internal::ExecSql(
|
||||||
db_handle_, sqlite_export_service_internal::kCreateBreweriesTableSql,
|
db_handle_, sqlite_export_service_internal::kCreateBreweriesTableSql,
|
||||||
"Failed to create SQLite breweries table");
|
"Failed to create SQLite breweries table");
|
||||||
sqlite_export_service_internal::ExecSql(
|
sqlite_export_service_internal::ExecSql(
|
||||||
db_handle_, sqlite_export_service_internal::kCreateUsersTableSql,
|
db_handle_, sqlite_export_service_internal::kCreateUsersTableSql,
|
||||||
"Failed to create SQLite users table");
|
"Failed to create SQLite users table");
|
||||||
sqlite_export_service_internal::ExecSql(
|
|
||||||
db_handle_, sqlite_export_service_internal::kCreateBreweryAddressTableSql,
|
|
||||||
"Failed to create SQLite brewery addresses table");
|
|
||||||
sqlite_export_service_internal::ExecSql(
|
|
||||||
db_handle_, sqlite_export_service_internal::kCreateUserAddressTableSql,
|
|
||||||
"Failed to create SQLite user addresses table");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SqliteExportService::PrepareStatements() {
|
void SqliteExportService::PrepareStatements() {
|
||||||
insert_city_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
insert_location_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
||||||
db_handle_, sqlite_export_service_internal::kInsertCitySql,
|
db_handle_, sqlite_export_service_internal::kInsertLocationSql,
|
||||||
"Failed to prepare SQLite city insert statement");
|
"Failed to prepare SQLite location insert statement");
|
||||||
insert_brewery_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
insert_brewery_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
||||||
db_handle_, sqlite_export_service_internal::kInsertBrewerySql,
|
db_handle_, sqlite_export_service_internal::kInsertBrewerySql,
|
||||||
"Failed to prepare SQLite brewery insert statement");
|
"Failed to prepare SQLite brewery insert statement");
|
||||||
insert_brewery_address_stmt_ =
|
|
||||||
sqlite_export_service_internal::PrepareStatement(
|
|
||||||
db_handle_, sqlite_export_service_internal::kInsertBreweryAddressSql,
|
|
||||||
"Failed to prepare SQLite brewery address insert statement");
|
|
||||||
insert_user_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
insert_user_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
||||||
db_handle_, sqlite_export_service_internal::kInsertUserSql,
|
db_handle_, sqlite_export_service_internal::kInsertUserSql,
|
||||||
"Failed to prepare SQLite user insert statement");
|
"Failed to prepare SQLite user insert statement");
|
||||||
insert_user_address_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
|
||||||
db_handle_, sqlite_export_service_internal::kInsertUserAddressSql,
|
|
||||||
"Failed to prepare SQLite user address insert statement");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
|
void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
|
||||||
@@ -73,13 +60,11 @@ void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
|
|||||||
transaction_open_ = false;
|
transaction_open_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
insert_user_address_stmt_.reset();
|
|
||||||
insert_user_stmt_.reset();
|
insert_user_stmt_.reset();
|
||||||
insert_brewery_address_stmt_.reset();
|
|
||||||
insert_brewery_stmt_.reset();
|
insert_brewery_stmt_.reset();
|
||||||
insert_city_stmt_.reset();
|
insert_location_stmt_.reset();
|
||||||
db_handle_.reset();
|
db_handle_.reset();
|
||||||
city_cache_.clear();
|
location_cache_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SqliteExportService::Initialize() {
|
void SqliteExportService::Initialize() {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* @file services/sqlite/process_record.cc
|
* @file services/sqlite/process_record.cc
|
||||||
* @brief SqliteExportService::ProcessRecord() implementation
|
* @brief SqliteExportService::ProcessRecord() implementation
|
||||||
* and the shared city-resolution helper.
|
* and the shared location-resolution helper.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -11,74 +12,94 @@
|
|||||||
#include "services/database/sqlite_export_service.h"
|
#include "services/database/sqlite_export_service.h"
|
||||||
#include "services/database/sqlite_export_service_helpers.h"
|
#include "services/database/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
std::string SqliteExportService::BuildCityKey(const City& location) {
|
constexpr int kLocationPrecision = 17;
|
||||||
|
|
||||||
|
std::string SqliteExportService::BuildLocationKey(const Location& location) {
|
||||||
std::ostringstream key_stream;
|
std::ostringstream key_stream;
|
||||||
key_stream << location.city << '\n'
|
key_stream << location.city << '\n'
|
||||||
<< location.state_province << '\n'
|
<< location.state_province << '\n'
|
||||||
<< location.iso3166_2 << '\n'
|
<< location.iso3166_2 << '\n'
|
||||||
<< location.country << '\n'
|
<< location.country << '\n'
|
||||||
<< location.iso3166_1 << '\n'
|
<< location.iso3166_1 << '\n'
|
||||||
|
<< std::setprecision(kLocationPrecision) << location.latitude
|
||||||
|
<< '\n'
|
||||||
|
<< std::setprecision(kLocationPrecision) << location.longitude
|
||||||
|
<< '\n'
|
||||||
<< sqlite_export_service_internal::SerializeVector(
|
<< sqlite_export_service_internal::SerializeVector(
|
||||||
location.local_languages);
|
location.local_languages);
|
||||||
return key_stream.str();
|
return key_stream.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_int64 SqliteExportService::ResolveCityId(const City& location) {
|
sqlite3_int64 SqliteExportService::ResolveLocationId(const Location& location) {
|
||||||
const std::string city_key = BuildCityKey(location);
|
const std::string location_key = BuildLocationKey(location);
|
||||||
const auto cached_city = city_cache_.find(city_key);
|
const auto cached_location = location_cache_.find(location_key);
|
||||||
if (cached_city != city_cache_.end()) {
|
if (cached_location != location_cache_.end()) {
|
||||||
return cached_city->second;
|
return cached_location->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string local_languages_json =
|
const std::string local_languages_json =
|
||||||
sqlite_export_service_internal::SerializeVector(location.local_languages);
|
sqlite_export_service_internal::SerializeVector(location.local_languages);
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_city_stmt_,
|
insert_location_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kCityNameBindIndex,
|
.index = sqlite_export_service_internal::kLocationCityBindIndex,
|
||||||
.value = location.city,
|
.value = location.city,
|
||||||
.action = "Failed to bind SQLite city name"});
|
.action = "Failed to bind SQLite location city"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_city_stmt_,
|
insert_location_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kCityStateProvinceBindIndex,
|
.index =
|
||||||
|
sqlite_export_service_internal::kLocationStateProvinceBindIndex,
|
||||||
.value = location.state_province,
|
.value = location.state_province,
|
||||||
.action = "Failed to bind SQLite city state/province"});
|
.action = "Failed to bind SQLite location state/province"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_city_stmt_,
|
insert_location_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kCityIso31662BindIndex,
|
.index = sqlite_export_service_internal::kLocationIso31662BindIndex,
|
||||||
.value = location.iso3166_2,
|
.value = location.iso3166_2,
|
||||||
.action = "Failed to bind SQLite city ISO 3166-2 code"});
|
.action = "Failed to bind SQLite location ISO 3166-2 code"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_city_stmt_,
|
insert_location_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kCityCountryBindIndex,
|
.index = sqlite_export_service_internal::kLocationCountryBindIndex,
|
||||||
.value = location.country,
|
.value = location.country,
|
||||||
.action = "Failed to bind SQLite city country"});
|
.action = "Failed to bind SQLite location country"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_city_stmt_,
|
insert_location_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kCityIso31661BindIndex,
|
.index = sqlite_export_service_internal::kLocationIso31661BindIndex,
|
||||||
.value = location.iso3166_1,
|
.value = location.iso3166_1,
|
||||||
.action = "Failed to bind SQLite city ISO 3166-1 code"});
|
.action = "Failed to bind SQLite location ISO 3166-1 code"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_city_stmt_,
|
insert_location_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kCityLanguagesBindIndex,
|
.index = sqlite_export_service_internal::kLocationLanguagesBindIndex,
|
||||||
.value = local_languages_json,
|
.value = local_languages_json,
|
||||||
.action = "Failed to bind SQLite city languages"});
|
.action = "Failed to bind SQLite location languages"});
|
||||||
|
sqlite_export_service_internal::Bind(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::BindParam{
|
||||||
|
.index = sqlite_export_service_internal::kLocationLatitudeBindIndex,
|
||||||
|
.value = location.latitude,
|
||||||
|
.action = "Failed to bind SQLite location latitude"});
|
||||||
|
sqlite_export_service_internal::Bind(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::BindParam{
|
||||||
|
.index = sqlite_export_service_internal::kLocationLongitudeBindIndex,
|
||||||
|
.value = location.longitude,
|
||||||
|
.action = "Failed to bind SQLite location longitude"});
|
||||||
|
|
||||||
sqlite_export_service_internal::StepStatement(
|
sqlite_export_service_internal::StepStatement(
|
||||||
db_handle_, insert_city_stmt_, "Failed to insert SQLite city row");
|
db_handle_, insert_location_stmt_,
|
||||||
|
"Failed to insert SQLite location row");
|
||||||
|
|
||||||
const sqlite3_int64 city_id =
|
const sqlite3_int64 location_id =
|
||||||
sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
||||||
city_cache_.emplace(city_key, city_id);
|
location_cache_.emplace(location_key, location_id);
|
||||||
sqlite_export_service_internal::ResetStatement(insert_city_stmt_);
|
sqlite_export_service_internal::ResetStatement(insert_location_stmt_);
|
||||||
|
|
||||||
return city_id;
|
return location_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
|
uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
|
||||||
@@ -86,25 +107,25 @@ uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
|
|||||||
throw std::runtime_error("SQLite export service is not initialized");
|
throw std::runtime_error("SQLite export service is not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const sqlite3_int64 city_id = ResolveCityId(brewery.address.city);
|
const sqlite3_int64 location_id = ResolveLocationId(brewery.location);
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_brewery_stmt_,
|
insert_brewery_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
sqlite_export_service_internal::BindParam<sqlite3_int64>{
|
||||||
.index = sqlite_export_service_internal::kBreweryCityIdBindIndex,
|
.index = sqlite_export_service_internal::kBreweryLocationIdBindIndex,
|
||||||
.value = city_id,
|
.value = location_id,
|
||||||
.action = "Failed to bind SQLite brewery city id"});
|
.action = "Failed to bind SQLite brewery location id"});
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_brewery_stmt_,
|
insert_brewery_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kBreweryEnglishNameBindIndex,
|
.index = sqlite_export_service_internal::kBreweryEnglishNameBindIndex,
|
||||||
.value = brewery.brewery.name_en,
|
.value = brewery.brewery.name_en,
|
||||||
.action = "Failed to bind SQLite brewery English name"});
|
.action = "Failed to bind SQLite brewery English name"});
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_brewery_stmt_,
|
insert_brewery_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::
|
.index = sqlite_export_service_internal::
|
||||||
kBreweryEnglishDescriptionBindIndex,
|
kBreweryEnglishDescriptionBindIndex,
|
||||||
.value = brewery.brewery.description_en,
|
.value = brewery.brewery.description_en,
|
||||||
@@ -112,14 +133,14 @@ uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
|
|||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_brewery_stmt_,
|
insert_brewery_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kBreweryLocalNameBindIndex,
|
.index = sqlite_export_service_internal::kBreweryLocalNameBindIndex,
|
||||||
.value = brewery.brewery.name_local,
|
.value = brewery.brewery.name_local,
|
||||||
.action = "Failed to bind SQLite brewery local name"});
|
.action = "Failed to bind SQLite brewery local name"});
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_brewery_stmt_,
|
insert_brewery_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index =
|
.index =
|
||||||
sqlite_export_service_internal::kBreweryLocalDescriptionBindIndex,
|
sqlite_export_service_internal::kBreweryLocalDescriptionBindIndex,
|
||||||
.value = brewery.brewery.description_local,
|
.value = brewery.brewery.description_local,
|
||||||
@@ -128,37 +149,7 @@ uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
|
|||||||
sqlite_export_service_internal::StepStatement(
|
sqlite_export_service_internal::StepStatement(
|
||||||
db_handle_, insert_brewery_stmt_, "Failed to insert SQLite brewery row");
|
db_handle_, insert_brewery_stmt_, "Failed to insert SQLite brewery row");
|
||||||
|
|
||||||
const sqlite3_int64 brewery_id =
|
|
||||||
sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
|
||||||
sqlite_export_service_internal::ResetStatement(insert_brewery_stmt_);
|
sqlite_export_service_internal::ResetStatement(insert_brewery_stmt_);
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
return sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
||||||
insert_brewery_address_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
|
||||||
.index =
|
|
||||||
sqlite_export_service_internal::kBreweryAddressBreweryIdBindIndex,
|
|
||||||
.value = brewery_id,
|
|
||||||
.action = "Failed to bind SQLite brewery address brewery id"});
|
|
||||||
sqlite_export_service_internal::Bind(
|
|
||||||
insert_brewery_address_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
|
||||||
.index = sqlite_export_service_internal::
|
|
||||||
kBreweryAddressPostalCodeBindIndex,
|
|
||||||
.value = brewery.address.postal_code,
|
|
||||||
.action = "Failed to bind SQLite brewery address postal code"});
|
|
||||||
sqlite_export_service_internal::Bind(
|
|
||||||
insert_brewery_address_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
|
||||||
.index =
|
|
||||||
sqlite_export_service_internal::kBreweryAddressCityIdBindIndex,
|
|
||||||
.value = city_id,
|
|
||||||
.action = "Failed to bind SQLite brewery address city id"});
|
|
||||||
|
|
||||||
sqlite_export_service_internal::StepStatement(
|
|
||||||
db_handle_, insert_brewery_address_stmt_,
|
|
||||||
"Failed to insert SQLite brewery address row");
|
|
||||||
|
|
||||||
sqlite_export_service_internal::ResetStatement(insert_brewery_address_stmt_);
|
|
||||||
|
|
||||||
return brewery_id;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,53 +13,59 @@ uint64_t SqliteExportService::ProcessRecord(const UserRecord& user) {
|
|||||||
throw std::runtime_error("SQLite export service is not initialized");
|
throw std::runtime_error("SQLite export service is not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const sqlite3_int64 city_id = ResolveCityId(user.address.city);
|
const sqlite3_int64 location_id = ResolveLocationId(user.location);
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<sqlite3_int64>{
|
||||||
|
.index = sqlite_export_service_internal::kUserLocationIdBindIndex,
|
||||||
|
.value = location_id,
|
||||||
|
.action = "Failed to bind SQLite user location id"});
|
||||||
|
sqlite_export_service_internal::Bind(
|
||||||
|
insert_user_stmt_,
|
||||||
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kUserFirstNameBindIndex,
|
.index = sqlite_export_service_internal::kUserFirstNameBindIndex,
|
||||||
.value = user.user.first_name,
|
.value = user.user.first_name,
|
||||||
.action = "Failed to bind SQLite user first name"});
|
.action = "Failed to bind SQLite user first name"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kUserLastNameBindIndex,
|
.index = sqlite_export_service_internal::kUserLastNameBindIndex,
|
||||||
.value = user.user.last_name,
|
.value = user.user.last_name,
|
||||||
.action = "Failed to bind SQLite user last name"});
|
.action = "Failed to bind SQLite user last name"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kUserGenderBindIndex,
|
.index = sqlite_export_service_internal::kUserGenderBindIndex,
|
||||||
.value = user.user.gender,
|
.value = user.user.gender,
|
||||||
.action = "Failed to bind SQLite user gender"});
|
.action = "Failed to bind SQLite user gender"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kUserUsernameBindIndex,
|
.index = sqlite_export_service_internal::kUserUsernameBindIndex,
|
||||||
.value = user.user.username,
|
.value = user.user.username,
|
||||||
.action = "Failed to bind SQLite user username"});
|
.action = "Failed to bind SQLite user username"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kUserBioBindIndex,
|
.index = sqlite_export_service_internal::kUserBioBindIndex,
|
||||||
.value = user.user.bio,
|
.value = user.user.bio,
|
||||||
.action = "Failed to bind SQLite user bio"});
|
.action = "Failed to bind SQLite user bio"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<double>{
|
sqlite_export_service_internal::BindParam<double>{
|
||||||
.index = sqlite_export_service_internal::kUserActivityWeightBindIndex,
|
.index = sqlite_export_service_internal::kUserActivityWeightBindIndex,
|
||||||
.value = static_cast<double>(user.user.activity_weight),
|
.value = static_cast<double>(user.user.activity_weight),
|
||||||
.action = "Failed to bind SQLite user activity weight"});
|
.action = "Failed to bind SQLite user activity weight"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kUserEmailBindIndex,
|
.index = sqlite_export_service_internal::kUserEmailBindIndex,
|
||||||
.value = user.email,
|
.value = user.email,
|
||||||
.action = "Failed to bind SQLite user email"});
|
.action = "Failed to bind SQLite user email"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BindParam<std::string_view>{
|
||||||
.index = sqlite_export_service_internal::kUserDateOfBirthBindIndex,
|
.index = sqlite_export_service_internal::kUserDateOfBirthBindIndex,
|
||||||
.value = user.date_of_birth,
|
.value = user.date_of_birth,
|
||||||
.action = "Failed to bind SQLite user date of birth"});
|
.action = "Failed to bind SQLite user date of birth"});
|
||||||
@@ -67,28 +73,7 @@ uint64_t SqliteExportService::ProcessRecord(const UserRecord& user) {
|
|||||||
sqlite_export_service_internal::StepStatement(
|
sqlite_export_service_internal::StepStatement(
|
||||||
db_handle_, insert_user_stmt_, "Failed to insert SQLite user row");
|
db_handle_, insert_user_stmt_, "Failed to insert SQLite user row");
|
||||||
|
|
||||||
const sqlite3_int64 user_id =
|
|
||||||
sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
|
||||||
sqlite_export_service_internal::ResetStatement(insert_user_stmt_);
|
sqlite_export_service_internal::ResetStatement(insert_user_stmt_);
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
return sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
||||||
insert_user_address_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
|
||||||
.index = sqlite_export_service_internal::kUserAddressUserIdBindIndex,
|
|
||||||
.value = user_id,
|
|
||||||
.action = "Failed to bind SQLite user address user id"});
|
|
||||||
sqlite_export_service_internal::Bind(
|
|
||||||
insert_user_address_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
|
||||||
.index = sqlite_export_service_internal::kUserAddressCityIdBindIndex,
|
|
||||||
.value = city_id,
|
|
||||||
.action = "Failed to bind SQLite user address city id"});
|
|
||||||
|
|
||||||
sqlite_export_service_internal::StepStatement(
|
|
||||||
db_handle_, insert_user_address_stmt_,
|
|
||||||
"Failed to insert SQLite user address row");
|
|
||||||
|
|
||||||
sqlite_export_service_internal::ResetStatement(insert_user_address_stmt_);
|
|
||||||
|
|
||||||
return user_id;
|
|
||||||
}
|
}
|
||||||
|
|||||||