mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-06-01 10:04:00 +00:00
Compare commits
64 Commits
feat/pipel
...
ee917d0f1b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee917d0f1b | ||
|
|
13785f67e7 | ||
|
|
9a2ecfea82 | ||
|
|
385dcd2095 | ||
|
|
2fd2a35233 | ||
|
|
1b242e86b5 | ||
|
|
8a6cbe5efd | ||
|
|
056fb47b93 | ||
|
|
88527f7709 | ||
|
|
49f4ed6787 | ||
|
|
4d4b897d02 | ||
|
|
f71e4ddc83 | ||
|
|
212077793e | ||
|
|
e6d1954506 | ||
|
|
ce56532728 | ||
|
|
9649c993e8 | ||
|
|
f782fdb51d | ||
|
|
fcc7a5dc8b | ||
|
|
44a74ed2ad | ||
|
|
6682b5de01 | ||
|
|
62dfb5e14a | ||
|
|
ddf4bcb981 | ||
|
|
15853c62fd | ||
|
|
ff4b7f2578 | ||
|
|
3c70c46957 | ||
|
|
c7abc808ea | ||
|
|
ef4f47d415 | ||
|
|
035b30abba | ||
|
|
1cd30488eb | ||
|
|
823599a96f | ||
|
|
56ec728ba7 | ||
|
|
7ca651a886 | ||
|
|
b53f9e5582 | ||
|
|
824f5b2b4f | ||
|
|
5d93d76e99 | ||
|
|
028786b8b5 | ||
|
|
d7a31b5264 | ||
|
|
b31be494d7 | ||
|
|
7807f0bc2a | ||
|
|
772ef0cdfb | ||
|
|
a6e2ea21d0 | ||
|
|
a7cbf7507f | ||
|
|
3c7e74e3c1 | ||
|
|
b1ac3a6068 | ||
|
|
06d329cac5 | ||
|
|
54c403526b | ||
|
|
b8e96a6d45 | ||
|
|
60ee2ecf74 | ||
|
|
e4e16a5084 | ||
|
|
8d306bf691 | ||
|
|
077f6ab4ae | ||
|
|
534403734a | ||
|
|
3af053f0eb | ||
|
|
ba165d8aa7 | ||
|
|
eb9a2767b4 | ||
|
|
29ea47fdb6 | ||
|
|
52e2333304 | ||
|
|
a1f0ca5b20 | ||
|
|
2ea8aa52b4 | ||
|
|
98083ab40c | ||
|
|
ac136f7179 | ||
|
|
280c9c61bd | ||
|
|
248a51b35f | ||
|
|
35aa7bc0df |
@@ -60,6 +60,28 @@ endif()
|
|||||||
# Require system Boost for JSON and Program Options to speed up build times
|
# Require system Boost for JSON and Program Options to speed up build times
|
||||||
find_package(Boost REQUIRED COMPONENTS json program_options)
|
find_package(Boost REQUIRED COMPONENTS json program_options)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
sqlite_amalgamation
|
||||||
|
URL https://www.sqlite.org/2026/sqlite-amalgamation-3530000.zip
|
||||||
|
URL_HASH SHA3_256=c2325c53b3b41761469f91cfb078e96882ac5d85bac10c11b0bd8f253b031e5b
|
||||||
|
)
|
||||||
|
FetchContent_GetProperties(sqlite_amalgamation)
|
||||||
|
if(NOT sqlite_amalgamation_POPULATED)
|
||||||
|
FetchContent_Populate(sqlite_amalgamation)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET sqlite3)
|
||||||
|
add_library(sqlite3 STATIC
|
||||||
|
${sqlite_amalgamation_SOURCE_DIR}/sqlite3.c
|
||||||
|
)
|
||||||
|
target_include_directories(sqlite3 PUBLIC
|
||||||
|
${sqlite_amalgamation_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
target_compile_definitions(sqlite3 PUBLIC
|
||||||
|
SQLITE_THREADSAFE=1
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
llama-cpp
|
llama-cpp
|
||||||
GIT_REPOSITORY https://github.com/ggml-org/llama.cpp.git
|
GIT_REPOSITORY https://github.com/ggml-org/llama.cpp.git
|
||||||
@@ -97,6 +119,16 @@ set(SOURCES
|
|||||||
src/services/wikipedia/wikipedia_service.cc
|
src/services/wikipedia/wikipedia_service.cc
|
||||||
src/services/wikipedia/get_summary.cc
|
src/services/wikipedia/get_summary.cc
|
||||||
src/services/wikipedia/fetch_extract.cc
|
src/services/wikipedia/fetch_extract.cc
|
||||||
|
src/services/sqlite/sqlite_export_service.cc
|
||||||
|
src/services/sqlite/build_database_path.cc
|
||||||
|
src/services/sqlite/build_location_key.cc
|
||||||
|
src/services/sqlite/initialize_schema.cc
|
||||||
|
src/services/sqlite/prepare_statements.cc
|
||||||
|
src/services/sqlite/initialize.cc
|
||||||
|
src/services/sqlite/process_record.cc
|
||||||
|
src/services/sqlite/finalize_statements.cc
|
||||||
|
src/services/sqlite/rollback_and_close_no_throw.cc
|
||||||
|
src/services/sqlite/finalize.cc
|
||||||
src/web_client/curl_global_state.cc
|
src/web_client/curl_global_state.cc
|
||||||
src/web_client/curl_web_client_get.cc
|
src/web_client/curl_web_client_get.cc
|
||||||
src/web_client/curl_web_client_url_encode.cc
|
src/web_client/curl_web_client_url_encode.cc
|
||||||
@@ -129,6 +161,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
|
|||||||
Boost::json
|
Boost::json
|
||||||
Boost::program_options
|
Boost::program_options
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
|
sqlite3
|
||||||
CURL::libcurl
|
CURL::libcurl
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ A C++20 command-line pipeline that samples city records from local JSON, enriche
|
|||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [How It Fits the Main App](#how-it-fits-the-main-app)
|
- [How It Fits The Main App](#how-it-fits-the-main-app)
|
||||||
- [Tech Stack](#tech-stack)
|
- [Tech Stack](#tech-stack)
|
||||||
- [Build](#build)
|
- [Build](#build)
|
||||||
- [Model](#model)
|
- [Model](#model)
|
||||||
@@ -26,7 +26,7 @@ A C++20 command-line pipeline that samples city records from local JSON, enriche
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How It Fits the Main App
|
## How It Fits The Main App
|
||||||
|
|
||||||
The pipeline is a data ingestion layer. It sits outside the web app runtime and produces seed records the app imports at startup or during a dedicated seed step.
|
The pipeline is a data ingestion layer. It sits outside the web app runtime and produces seed records the app imports at startup or during a dedicated seed step.
|
||||||
|
|
||||||
@@ -46,17 +46,19 @@ The pipeline is a data ingestion layer. It sits outside the web app runtime and
|
|||||||
- Boost.JSON, Boost.ProgramOptions, Boost.DI
|
- Boost.JSON, Boost.ProgramOptions, Boost.DI
|
||||||
- spdlog
|
- spdlog
|
||||||
- libcurl
|
- libcurl
|
||||||
|
- SQLite amalgamation fetched and compiled via CMake FetchContent
|
||||||
- llama.cpp
|
- llama.cpp
|
||||||
|
|
||||||
The build fetches Boost.DI, spdlog, and llama.cpp via CMake. Metal is enabled on Apple Silicon; CUDA or HIP/ROCm is detected on Linux when the toolkit is present.
|
The build fetches Boost.DI, spdlog, llama.cpp, and SQLite via CMake. Metal is enabled on Apple Silicon; CUDA or HIP/ROCm is detected on Linux when the toolkit is present.
|
||||||
|
|
||||||
> **Code Style:** Modern C++20 throughout — RAII for ownership, `std::unique_ptr` for injected dependencies, `std::optional` for parse outcomes, `std::span` for read-only views over generated city data, structured bindings in pipeline loops. Formatting follows the Google C++ Style Guide via `.clang-format` with a narrow column limit and two-space indentation.
|
> **Code Style:** Modern C++20 throughout - RAII for ownership, `std::unique_ptr` for injected dependencies, `std::optional` for parse outcomes, `std::span` for read-only views over generated city data, structured bindings in pipeline loops. Formatting follows the Google C++ Style Guide via `.clang-format` with a narrow column limit and two-space indentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Requirements: C++20 compiler, CMake 3.24+, libcurl, Boost (JSON and ProgramOptions).
|
Requirements: C++20 compiler, CMake 3.24+, libcurl, Boost (JSON and ProgramOptions).
|
||||||
|
SQLite is fetched from the upstream amalgamation, so no system SQLite package is required.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cmake -S . -B build
|
cmake -S . -B build
|
||||||
@@ -80,7 +82,7 @@ curl -L \
|
|||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
Run from `build/` so the copied `locations.json` and `prompts/` are available.
|
Run from `build/` so the copied `locations.json` and `prompts/` are available. Each run also writes a fresh dated SQLite file such as `biergarten_seed_2026-04-19T15-30-45.123456Z.sqlite` into the working directory.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./biergarten-pipeline --mocked
|
./biergarten-pipeline --mocked
|
||||||
@@ -102,7 +104,7 @@ Run from `build/` so the copied `locations.json` and `prompts/` are available.
|
|||||||
|
|
||||||
`--mocked` and `--model` are mutually exclusive. Omitting both exits with an error before the pipeline starts. Sampling flags are ignored when `--mocked` is set.
|
`--mocked` and `--model` are mutually exclusive. Omitting both exits with an error before the pipeline starts. Sampling flags are ignored when `--mocked` is set.
|
||||||
|
|
||||||
The post-build step copies `prompts/` into `build/prompts/`. Rebuild after editing [prompts/system.md](prompts/system.md).
|
The post-build step copies `prompts/` into `build/prompts/`. Rebuild after editing `prompts/system.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -110,23 +112,25 @@ The post-build step copies `prompts/` into `build/prompts/`. Rebuild after editi
|
|||||||
|
|
||||||
### Pipeline Stages
|
### Pipeline Stages
|
||||||
|
|
||||||
| Stage | Implementation |
|
| Stage | Implementation |
|
||||||
| -------- | -------------------------------------------------------------------------------------------------------------- |
|
| -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Load | `JsonLoader::LoadLocations()` reads `locations.json` into typed `Location` records. |
|
| Load | `JsonLoader::LoadLocations()` reads `locations.json` into typed `Location` records. |
|
||||||
| Sample | `BiergartenDataGenerator::QueryCitiesWithCountries()` samples up to 50 locations per run. |
|
| Sample | `BiergartenDataGenerator::QueryCitiesWithCountries()` samples up to 50 locations per run. |
|
||||||
| Enrich | `WikipediaService` fetches city and beer context. Keeps going when a lookup fails. |
|
| Enrich | `WikipediaService` fetches city and beer context. Keeps going when a lookup fails. |
|
||||||
| Generate | `MockGenerator` or `LlamaGenerator` produces brewery names and descriptions in English and the local language. |
|
| Generate | `MockGenerator` or `LlamaGenerator` produces brewery names and descriptions in English and the local language. |
|
||||||
| Log | `spdlog` writes results and warnings to the console. |
|
| Store | `SqliteExportService` writes each successful brewery into a fresh dated `.sqlite` database with normalized location and brewery tables. |
|
||||||
|
| Log | `spdlog` writes results and warnings to the console. |
|
||||||
|
|
||||||
If enrichment or generation fails for a city, that city is skipped and the pipeline continues.
|
If enrichment or generation fails for a city, that city is skipped and the pipeline continues.
|
||||||
|
|
||||||
### Key Components
|
### Key Components
|
||||||
|
|
||||||
- `src/main.cc` — argument parsing and Boost.DI composition root.
|
- `src/main.cc` - argument parsing and Boost.DI composition root.
|
||||||
- `JsonLoader` — validates curated location input.
|
- `JsonLoader` - validates curated location input.
|
||||||
- `WikipediaService` — queries Wikipedia extracts, caches results, returns empty context on failure.
|
- `WikipediaService` - queries Wikipedia extracts, caches results, returns empty context on failure.
|
||||||
- `LlamaGenerator` — formats prompts for Gemma 4, validates JSON output, retries malformed responses up to three times. If output looks truncated, the retry raises the token budget before trying again.
|
- `LlamaGenerator` - formats prompts for Gemma 4, validates JSON output, retries malformed responses up to three times. If output looks truncated, the retry raises the token budget before trying again.
|
||||||
- `MockGenerator` — stable hash-based output so the same city input always produces the same brewery.
|
- `MockGenerator` - stable hash-based output so the same city input always produces the same brewery.
|
||||||
|
- `SqliteExportService` - creates a dated SQLite file per run and persists each successful brewery into normalized tables.
|
||||||
- Brewery payloads include English and local-language name and description fields.
|
- Brewery payloads include English and local-language name and description fields.
|
||||||
|
|
||||||
### Runtime Behaviour
|
### Runtime Behaviour
|
||||||
@@ -139,11 +143,11 @@ If enrichment or generation fails for a city, that city is skipped and the pipel
|
|||||||
|
|
||||||
`MockGenerator` uses stable hashes for repeatable output in demos and Storybook runs.
|
`MockGenerator` uses stable hashes for repeatable output in demos and Storybook runs.
|
||||||
|
|
||||||
### Process Flow — Activity Diagram
|
### Process Flow - Activity Diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Architectural Overview — Class Diagram
|
### Architectural Overview - Class Diagram
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -151,7 +155,7 @@ If enrichment or generation fails for a city, that city is skipped and the pipel
|
|||||||
|
|
||||||
## Generated Output
|
## Generated Output
|
||||||
|
|
||||||
Each successful run stores a `GeneratedBrewery` pair with the source location and a `BreweryResult` payload.
|
Each successful run stores a `GeneratedBrewery` pair with the source location and a `BreweryResult` payload. The same generated records are also written to a fresh SQLite export file named with the current UTC timestamp.
|
||||||
|
|
||||||
| Field | Meaning |
|
| Field | Meaning |
|
||||||
| ------------------- | ------------------------------------------ |
|
| ------------------- | ------------------------------------------ |
|
||||||
@@ -255,7 +259,7 @@ For languages such as Welsh (Wales), Maori (Aotearoa/New Zealand), or Sicilian (
|
|||||||
|
|
||||||
## Tested Hardware
|
## Tested Hardware
|
||||||
|
|
||||||
### ARM macOS — M1 Pro
|
### ARM macOS - M1 Pro
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| --------- | --------------------------------- |
|
| --------- | --------------------------------- |
|
||||||
@@ -266,7 +270,7 @@ For languages such as Welsh (Wales), Maori (Aotearoa/New Zealand), or Sicilian (
|
|||||||
| Model | Gemma 4 E4B |
|
| Model | Gemma 4 E4B |
|
||||||
| Inference | llama.cpp with Metal |
|
| Inference | llama.cpp with Metal |
|
||||||
|
|
||||||
### x86_64 Linux — NVIDIA RTX 2000
|
### x86_64 Linux - NVIDIA RTX 2000
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| --------- | ------------------------------ |
|
| --------- | ------------------------------ |
|
||||||
@@ -293,11 +297,12 @@ For languages such as Welsh (Wales), Maori (Aotearoa/New Zealand), or Sicilian (
|
|||||||
|
|
||||||
## Code Tour
|
## Code Tour
|
||||||
|
|
||||||
- `src/main.cc` — argument parsing and DI composition root.
|
- `src/main.cc` - argument parsing and DI composition root.
|
||||||
- `src/biergarten_data_generator/` — orchestration, sampling, logging.
|
- `src/biergarten_data_generator/` - orchestration, sampling, logging, and export.
|
||||||
- `src/services/wikipedia/` — enrichment service and cache.
|
- `src/services/wikipedia/` - enrichment service and cache.
|
||||||
- `src/data_generation/llama/` — local inference, prompt loading, output validation.
|
- `src/services/sqlite/` - SQLite export implementation.
|
||||||
- `src/data_generation/mock/` — deterministic fallback.
|
- `src/data_generation/llama/` - local inference, prompt loading, output validation.
|
||||||
|
- `src/data_generation/mock/` - deterministic fallback.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -312,11 +317,7 @@ For languages such as Welsh (Wales), Maori (Aotearoa/New Zealand), or Sicilian (
|
|||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
The pipeline currently produces city-aware brewery records. The next passes add SQLite output and additional fixture types so the app can exercise the full brewery domain without live data.
|
The pipeline currently produces city-aware brewery records and dated SQLite exports. The next passes add additional fixture types so the app can exercise the full brewery domain without live data.
|
||||||
|
|
||||||
### SQLite Output _(Highest Importance)_
|
|
||||||
|
|
||||||
Write generated records to a SQLite database for downstream OLTP seeding. Normalized schema with foreign keys between locations and breweries. Output replaces the current log-only result so the pipeline functions as a proper ingestion layer.
|
|
||||||
|
|
||||||
### Testing _(Very High Importance)_
|
### Testing _(Very High Importance)_
|
||||||
|
|
||||||
@@ -336,7 +337,7 @@ Generate user profiles with stable names, bios, locale hints, and preference sig
|
|||||||
|
|
||||||
### Check-In System
|
### Check-In System
|
||||||
|
|
||||||
Produce timestamped check-in events between users and breweries. Use a J-curve activity profile — a small set of users accounts for most check-ins, the rest appear occasionally. Add bursty behaviour around weekends and travel periods.
|
Produce timestamped check-in events between users and breweries. Use a J-curve activity profile - a small set of users accounts for most check-ins, the rest appear occasionally. Add bursty behaviour around weekends and travel periods.
|
||||||
|
|
||||||
### Beer Ratings
|
### Beer Ratings
|
||||||
|
|
||||||
|
|||||||
@@ -15,19 +15,14 @@ skinparam ActivityBorderColor #547461
|
|||||||
skinparam ActivityDiamondBackgroundColor #FAFCF9
|
skinparam ActivityDiamondBackgroundColor #FAFCF9
|
||||||
skinparam ActivityDiamondBorderColor #628A5B
|
skinparam ActivityDiamondBorderColor #628A5B
|
||||||
skinparam ActivityBarColor #628A5B
|
skinparam ActivityBarColor #628A5B
|
||||||
skinparam SwimlaneBorderColor transparent
|
skinparam SwimlaneBorderColor #547461
|
||||||
skinparam SwimlaneBorderThickness 0
|
skinparam SwimlaneBorderThickness 0.3
|
||||||
|
|
||||||
title The Biergarten Data Pipeline
|
title The Biergarten Data Pipeline (Streaming Architecture)
|
||||||
|
|
||||||
|#F2F6F0|main.cc|
|
|#F2F6F0|main.cc|
|
||||||
start
|
start
|
||||||
:ParseArguments(argc, argv);
|
:ParseArguments(argc, argv);
|
||||||
note right
|
|
||||||
Validates --mocked, --model,
|
|
||||||
--temperature, --top-p, etc.
|
|
||||||
end note
|
|
||||||
|
|
||||||
if (Are arguments valid?) then (no)
|
if (Are arguments valid?) then (no)
|
||||||
:spdlog::error usage info;
|
:spdlog::error usage info;
|
||||||
stop
|
stop
|
||||||
@@ -36,14 +31,23 @@ endif
|
|||||||
|
|
||||||
:Init CurlGlobalState & LlamaBackendState;
|
:Init CurlGlobalState & LlamaBackendState;
|
||||||
:di::make_injector(...);
|
:di::make_injector(...);
|
||||||
note right
|
:injector.create<std::unique_ptr<BiergartenDataGenerator>>();
|
||||||
Binds CURLWebClient, WikipediaService,
|
|
||||||
Gemma4JinjaPromptFormatter, and
|
|
||||||
either MockGenerator or LlamaGenerator
|
|
||||||
end note
|
|
||||||
:injector.create<BiergartenDataGenerator>();
|
|
||||||
:BiergartenDataGenerator::Run();
|
:BiergartenDataGenerator::Run();
|
||||||
|
|
||||||
|
|#EAF0E8|BiergartenDataGenerator|
|
||||||
|
:Initialize SQLite export;
|
||||||
|
|
||||||
|
|#E0EAE0|SqliteExportService|
|
||||||
|
:GetUtcTimestamp() from SystemDateTimeProvider;
|
||||||
|
:Initialize();
|
||||||
|
note right
|
||||||
|
Builds a fresh biergarten_seed_<UTC datetime>.sqlite filename
|
||||||
|
Appends a numeric suffix if the timestamp already exists
|
||||||
|
Opens DB Connection
|
||||||
|
Executes Schema DDL
|
||||||
|
Begins Transaction
|
||||||
|
end note
|
||||||
|
|
||||||
|#EAF0E8|BiergartenDataGenerator|
|
|#EAF0E8|BiergartenDataGenerator|
|
||||||
:QueryCitiesWithCountries();
|
:QueryCitiesWithCountries();
|
||||||
|
|
||||||
@@ -55,71 +59,64 @@ end note
|
|||||||
while (For each sampled Location?) is (Remaining cities)
|
while (For each sampled Location?) is (Remaining cities)
|
||||||
|#DCE8D8|WikipediaService|
|
|#DCE8D8|WikipediaService|
|
||||||
:GetLocationContext(loc);
|
:GetLocationContext(loc);
|
||||||
:FetchExtract("City, Country");
|
:FetchExtracts(City, Country, Beer);
|
||||||
:FetchExtract("beer in Country");
|
|
||||||
:FetchExtract("beer in City");
|
|
||||||
note right: Backed by CURLWebClient::Get
|
|
||||||
|
|
||||||
|#EAF0E8|BiergartenDataGenerator|
|
|#EAF0E8|BiergartenDataGenerator|
|
||||||
if (Lookup failed?) then (yes)
|
:Store EnrichedCity{Location, region_context};
|
||||||
:spdlog::warn "context lookup failed";
|
|
||||||
else (no)
|
|
||||||
:Store EnrichedCity{Location, region_context};
|
|
||||||
endif
|
|
||||||
endwhile (Done)
|
endwhile (Done)
|
||||||
|
|
||||||
|
|#EAF0E8|BiergartenDataGenerator|
|
||||||
:GenerateBreweries(enriched_cities);
|
:GenerateBreweries(enriched_cities);
|
||||||
|
|
||||||
|#E5EDE1|DataGenerator|
|
|#E5EDE1|DataGenerator|
|
||||||
while (For each EnrichedCity?) is (Remaining cities)
|
while (For each EnrichedCity?) is (Remaining cities)
|
||||||
if (Generator Mode) then (MockGenerator)
|
if (Generator Mode) then (MockGenerator)
|
||||||
:DeterministicHash(location);
|
:DeterministicHash & Format;
|
||||||
:Select from kBreweryAdjectives, kBreweryNouns,\nkBreweryDescriptions;
|
|
||||||
:Format BreweryResult;
|
|
||||||
else (LlamaGenerator)
|
else (LlamaGenerator)
|
||||||
:PrepareRegionContext(region_context);
|
:PrepareRegionContext;
|
||||||
:LoadBrewerySystemPrompt("prompts/system.md");
|
:LoadBrewerySystemPrompt("prompts/system.md");
|
||||||
:Format user_prompt;
|
|
||||||
:Attempt = 0;
|
|
||||||
repeat
|
repeat
|
||||||
:Infer(system_prompt, user_prompt, max_tokens, kBreweryJsonGrammar);
|
:Infer(system_prompt, user_prompt, max_tokens, kBreweryJsonGrammar);
|
||||||
note right
|
|
||||||
Uses Gemma4JinjaPromptFormatter,
|
|
||||||
llama_tokenize, and llama_sampler_sample
|
|
||||||
end note
|
|
||||||
:ValidateBreweryJson(raw, brewery);
|
:ValidateBreweryJson(raw, brewery);
|
||||||
|
|
||||||
if (Is JSON Valid?) then (yes)
|
if (Is JSON Valid?) then (yes)
|
||||||
break
|
break
|
||||||
else (no)
|
else (no)
|
||||||
if (Error == "incomplete JSON") then (yes)
|
|
||||||
:max_tokens += 700;
|
|
||||||
endif
|
|
||||||
:Update user_prompt with validation error;
|
|
||||||
:Attempt++;
|
:Attempt++;
|
||||||
endif
|
endif
|
||||||
|
|
||||||
repeat while (Attempt < 3?) is (yes)
|
repeat while (Attempt < 3?) is (yes)
|
||||||
|
|
||||||
if (Still Invalid?) then (yes)
|
|
||||||
:throw std::runtime_error;
|
|
||||||
else (no)
|
|
||||||
:Return BreweryResult;
|
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|#EAF0E8|BiergartenDataGenerator|
|
|#EAF0E8|BiergartenDataGenerator|
|
||||||
if (Exception thrown?) then (yes)
|
if (Generation successful?) then (yes)
|
||||||
:spdlog::warn "brewery generation failed";
|
|#E0EAE0|SqliteExportService|
|
||||||
|
:ProcessRecord(GeneratedBrewery);
|
||||||
|
if (Location in cache?) then (yes)
|
||||||
|
:Reuse location_id;
|
||||||
|
else (no)
|
||||||
|
:Insert Location & Cache ID;
|
||||||
|
endif
|
||||||
|
:Insert Brewery (FK: location_id);
|
||||||
|
|
||||||
|
if (Exception caught during insert?) then (yes)
|
||||||
|
|#EAF0E8|BiergartenDataGenerator|
|
||||||
|
:spdlog::warn "Failed to stream record to SQLite export";
|
||||||
|
note right
|
||||||
|
Data loss is prevented per-record.
|
||||||
|
The pipeline continues running.
|
||||||
|
end note
|
||||||
|
else (no)
|
||||||
|
endif
|
||||||
else (no)
|
else (no)
|
||||||
:Store GeneratedBrewery;
|
:spdlog::warn "Generation failed, skipping...";
|
||||||
endif
|
endif
|
||||||
|#E5EDE1|DataGenerator|
|
|#E5EDE1|DataGenerator|
|
||||||
endwhile (Done)
|
endwhile (Done)
|
||||||
|
|
||||||
|#EAF0E8|BiergartenDataGenerator|
|
|#E0EAE0|SqliteExportService|
|
||||||
:LogResults();
|
:Finalize();
|
||||||
note right: spdlog::info dump of generated JSON fields
|
note right
|
||||||
|
Commits Transaction
|
||||||
|
Closes Database Connection
|
||||||
|
end note
|
||||||
|
|
||||||
|#F2F6F0|main.cc|
|
|#F2F6F0|main.cc|
|
||||||
:Return 0;
|
:Return 0;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ title The Biergarten Data Pipeline - Class Diagram
|
|||||||
class BiergartenDataGenerator {
|
class BiergartenDataGenerator {
|
||||||
- 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>
|
||||||
- generated_breweries_ : std::vector<GeneratedBrewery>
|
- generated_breweries_ : std::vector<GeneratedBrewery>
|
||||||
+ Run() : bool
|
+ Run() : bool
|
||||||
- QueryCitiesWithCountries() : std::vector<Location>
|
- QueryCitiesWithCountries() : std::vector<Location>
|
||||||
@@ -92,9 +93,39 @@ class JsonLoader {
|
|||||||
+ {static} LoadLocations(filepath : const std::filesystem::path&) : std::vector<Location>
|
+ {static} LoadLocations(filepath : const std::filesystem::path&) : std::vector<Location>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IExportService <<interface>> {
|
||||||
|
+ Initialize() : void
|
||||||
|
+ ProcessRecord(brewery : const GeneratedBrewery&) : void
|
||||||
|
+ Finalize() : void
|
||||||
|
}
|
||||||
|
|
||||||
|
class SqliteExportService {
|
||||||
|
- date_time_provider_ : std::unique_ptr<IDateTimeProvider>
|
||||||
|
- run_timestamp_utc_ : std::string
|
||||||
|
- database_path_ : std::filesystem::path
|
||||||
|
- db_handle_ : sqlite3*
|
||||||
|
- insert_location_stmt_ : sqlite3_stmt*
|
||||||
|
- insert_brewery_stmt_ : sqlite3_stmt*
|
||||||
|
- transaction_open_ : bool
|
||||||
|
- location_cache_ : std::unordered_map<std::string, sqlite3_int64>
|
||||||
|
+ Initialize() : void
|
||||||
|
+ ProcessRecord(brewery : const GeneratedBrewery&) : void
|
||||||
|
+ Finalize() : void
|
||||||
|
- InitializeSchema() : void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDateTimeProvider <<interface>> {
|
||||||
|
+ GetUtcTimestamp() : std::string
|
||||||
|
}
|
||||||
|
|
||||||
|
class SystemDateTimeProvider {
|
||||||
|
+ GetUtcTimestamp() : std::string
|
||||||
|
}
|
||||||
|
|
||||||
' Structural Relationships / Dependency Injection
|
' Structural Relationships / Dependency Injection
|
||||||
BiergartenDataGenerator *-- IEnrichmentService : owns
|
BiergartenDataGenerator *-- IEnrichmentService : owns
|
||||||
BiergartenDataGenerator *-- DataGenerator : owns
|
BiergartenDataGenerator *-- DataGenerator : owns
|
||||||
|
BiergartenDataGenerator *-- IExportService : owns
|
||||||
|
|
||||||
IEnrichmentService <|.. WikipediaService : implements
|
IEnrichmentService <|.. WikipediaService : implements
|
||||||
WikipediaService *-- WebClient : owns
|
WikipediaService *-- WebClient : owns
|
||||||
@@ -109,4 +140,9 @@ LlamaGenerator *-- IPromptFormatter : uses
|
|||||||
IPromptFormatter <|.. Gemma4JinjaPromptFormatter : implements
|
IPromptFormatter <|.. Gemma4JinjaPromptFormatter : implements
|
||||||
|
|
||||||
BiergartenDataGenerator ..> JsonLoader : uses
|
BiergartenDataGenerator ..> JsonLoader : uses
|
||||||
|
|
||||||
|
IExportService <|.. SqliteExportService : implements
|
||||||
|
SqliteExportService *-- IDateTimeProvider : owns
|
||||||
|
IDateTimeProvider <|.. SystemDateTimeProvider : implements
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
38
pipeline/format.sh
Executable file
38
pipeline/format.sh
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check for -y flag
|
||||||
|
SKIP_CONFIRM=false
|
||||||
|
if [[ "$1" == "-y" ]]; then
|
||||||
|
SKIP_CONFIRM=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "WARNING: This script will format all .cpp, .h, .cxx, .cc .c, .hpp files in the src and includes directories."
|
||||||
|
echo "This script will overwrite the files with the formatted version."
|
||||||
|
|
||||||
|
if [[ "$SKIP_CONFIRM" == false ]]; then
|
||||||
|
read -p "Do you want to continue? (y/n) " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f .clang-format ]; then
|
||||||
|
echo "ERROR: .clang-format file not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v clang-format &>/dev/null; then
|
||||||
|
echo "ERROR: clang-format not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Formatting files..."
|
||||||
|
|
||||||
|
find includes src \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" -o -name "*.cc" -o -name "*.cxx" \) | xargs clang-format -i
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "data_model/generated_brewery.h"
|
#include "data_model/generated_brewery.h"
|
||||||
#include "data_model/location.h"
|
#include "data_model/location.h"
|
||||||
#include "services/enrichment_service.h"
|
#include "services/enrichment_service.h"
|
||||||
|
#include "services/export_service.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Main data generator class for the Biergarten pipeline.
|
* @brief Main data generator class for the Biergarten pipeline.
|
||||||
@@ -29,9 +30,11 @@ class BiergartenDataGenerator {
|
|||||||
*
|
*
|
||||||
* @param context_service Context provider for sampled locations.
|
* @param context_service Context provider for sampled locations.
|
||||||
* @param generator Brewery and user data generator.
|
* @param generator Brewery and user data generator.
|
||||||
|
* @param exporter Storage backend for generated brewery data.
|
||||||
*/
|
*/
|
||||||
BiergartenDataGenerator(std::unique_ptr<IEnrichmentService> context_service,
|
BiergartenDataGenerator(std::unique_ptr<IEnrichmentService> context_service,
|
||||||
std::unique_ptr<DataGenerator> generator);
|
std::unique_ptr<DataGenerator> generator,
|
||||||
|
std::unique_ptr<IExportService> exporter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Run the data generation pipeline.
|
* @brief Run the data generation pipeline.
|
||||||
@@ -52,6 +55,9 @@ class BiergartenDataGenerator {
|
|||||||
/// @brief Generator dependency selected in the composition root.
|
/// @brief Generator dependency selected in the composition root.
|
||||||
std::unique_ptr<DataGenerator> generator_;
|
std::unique_ptr<DataGenerator> generator_;
|
||||||
|
|
||||||
|
/// @brief Storage backend for generated brewery records.
|
||||||
|
std::unique_ptr<IExportService> exporter_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Load locations from JSON and sample cities.
|
* @brief Load locations from JSON and sample cities.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class LlamaGenerator final : public DataGenerator {
|
|||||||
*
|
*
|
||||||
* @param options Parsed application options.
|
* @param options Parsed application options.
|
||||||
* @param model_path Filesystem path to GGUF model assets.
|
* @param model_path Filesystem path to GGUF model assets.
|
||||||
* @param prompt_formatter Formatter that produces model-specific prompts.
|
* @param prompt_formatter Formatter that produces model-specific prompts.
|
||||||
*/
|
*/
|
||||||
LlamaGenerator(const ApplicationOptions& options,
|
LlamaGenerator(const ApplicationOptions& options,
|
||||||
const std::string& model_path,
|
const std::string& model_path,
|
||||||
@@ -100,24 +100,24 @@ class LlamaGenerator final : public DataGenerator {
|
|||||||
* @param system_prompt System role prompt.
|
* @param system_prompt System role prompt.
|
||||||
* @param prompt User prompt.
|
* @param prompt User prompt.
|
||||||
* @param max_tokens Maximum tokens to generate.
|
* @param max_tokens Maximum tokens to generate.
|
||||||
* @param grammar Optional GBNF grammar constraining generated output.
|
* @param grammar Optional GBNF grammar constraining generated output.
|
||||||
* @return Generated text.
|
* @return Generated text.
|
||||||
*/
|
*/
|
||||||
std::string Infer(const std::string& system_prompt, const std::string& prompt,
|
std::string Infer(const std::string& system_prompt, const std::string& prompt,
|
||||||
int max_tokens = kDefaultMaxTokens,
|
int max_tokens = kDefaultMaxTokens,
|
||||||
std::string_view grammar = {});
|
std::string_view grammar = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Runs inference on an already-formatted prompt.
|
* @brief Runs inference on an already-formatted prompt.
|
||||||
*
|
*
|
||||||
* @param formatted_prompt Prompt preformatted for model chat template.
|
* @param formatted_prompt Prompt preformatted for model chat template.
|
||||||
* @param max_tokens Maximum tokens to generate.
|
* @param max_tokens Maximum tokens to generate.
|
||||||
* @param grammar Optional GBNF grammar constraining generated output.
|
* @param grammar Optional GBNF grammar constraining generated output.
|
||||||
* @return Generated text.
|
* @return Generated text.
|
||||||
*/
|
*/
|
||||||
std::string InferFormatted(const std::string& formatted_prompt,
|
std::string InferFormatted(const std::string& formatted_prompt,
|
||||||
int max_tokens = kDefaultMaxTokens,
|
int max_tokens = kDefaultMaxTokens,
|
||||||
std::string_view grammar = {});
|
std::string_view grammar = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Loads the brewery system prompt from disk.
|
* @brief Loads the brewery system prompt from disk.
|
||||||
@@ -125,7 +125,8 @@ class LlamaGenerator final : public DataGenerator {
|
|||||||
* @param prompt_file_path Prompt file path to try first.
|
* @param prompt_file_path Prompt file path to try first.
|
||||||
* @return Loaded prompt text.
|
* @return Loaded prompt text.
|
||||||
*/
|
*/
|
||||||
std::string LoadBrewerySystemPrompt(const std::filesystem::path& prompt_file_path);
|
std::string LoadBrewerySystemPrompt(
|
||||||
|
const std::filesystem::path& prompt_file_path);
|
||||||
|
|
||||||
ModelHandle model_;
|
ModelHandle model_;
|
||||||
ContextHandle context_;
|
ContextHandle context_;
|
||||||
|
|||||||
@@ -13,6 +13,5 @@ class IPromptFormatter {
|
|||||||
virtual ~IPromptFormatter() = default;
|
virtual ~IPromptFormatter() = default;
|
||||||
|
|
||||||
[[nodiscard]] virtual std::string Format(
|
[[nodiscard]] virtual std::string Format(
|
||||||
std::string_view system_prompt,
|
std::string_view system_prompt, std::string_view user_prompt) const = 0;
|
||||||
std::string_view user_prompt) const = 0;
|
|
||||||
};
|
};
|
||||||
|
|||||||
66
pipeline/includes/services/date_time_provider.h
Normal file
66
pipeline/includes/services/date_time_provider.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_DATE_TIME_PROVIDER_H_
|
||||||
|
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_DATE_TIME_PROVIDER_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file services/date_time_provider.h
|
||||||
|
* @brief Abstraction for UTC timestamp generation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <ctime>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interface for UTC timestamp providers.
|
||||||
|
*/
|
||||||
|
class IDateTimeProvider {
|
||||||
|
public:
|
||||||
|
/// @brief Virtual destructor for polymorphic cleanup.
|
||||||
|
virtual ~IDateTimeProvider() = default;
|
||||||
|
|
||||||
|
IDateTimeProvider() = default;
|
||||||
|
IDateTimeProvider(const IDateTimeProvider&) = delete;
|
||||||
|
IDateTimeProvider& operator=(const IDateTimeProvider&) = delete;
|
||||||
|
IDateTimeProvider(IDateTimeProvider&&) = delete;
|
||||||
|
IDateTimeProvider& operator=(IDateTimeProvider&&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the current UTC timestamp in a file-safe format.
|
||||||
|
*
|
||||||
|
* @return UTC timestamp string.
|
||||||
|
*/
|
||||||
|
virtual std::string GetUtcTimestamp() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Timestamp provider backed by the system clock.
|
||||||
|
*/
|
||||||
|
class SystemDateTimeProvider final : public IDateTimeProvider {
|
||||||
|
public:
|
||||||
|
std::string GetUtcTimestamp() override {
|
||||||
|
constexpr int kFractionalSecondWidth = 6;
|
||||||
|
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
const auto now_time = std::chrono::system_clock::to_time_t(now);
|
||||||
|
const std::tm* utc_time_ptr = std::gmtime(&now_time);
|
||||||
|
if (utc_time_ptr == nullptr) {
|
||||||
|
throw std::runtime_error("Failed to format UTC timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fractional_seconds =
|
||||||
|
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
now.time_since_epoch()) %
|
||||||
|
std::chrono::seconds(1);
|
||||||
|
|
||||||
|
std::ostringstream output;
|
||||||
|
output << std::put_time(utc_time_ptr, "%Y-%m-%dT%H-%M-%S");
|
||||||
|
output << '.' << std::setw(kFractionalSecondWidth) << std::setfill('0')
|
||||||
|
<< fractional_seconds.count() << 'Z';
|
||||||
|
return output.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_DATE_TIME_PROVIDER_H_
|
||||||
40
pipeline/includes/services/export_service.h
Normal file
40
pipeline/includes/services/export_service.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_EXPORT_SERVICE_H_
|
||||||
|
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_EXPORT_SERVICE_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file services/export_service.h
|
||||||
|
* @brief Abstraction for persisting generated brewery data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "data_model/generated_brewery.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interface for services that persist generated brewery records.
|
||||||
|
*/
|
||||||
|
class IExportService {
|
||||||
|
public:
|
||||||
|
IExportService() = default;
|
||||||
|
|
||||||
|
/// @brief Virtual destructor for polymorphic cleanup.
|
||||||
|
virtual ~IExportService() = default;
|
||||||
|
|
||||||
|
IExportService(const IExportService&) = delete;
|
||||||
|
IExportService& operator=(const IExportService&) = delete;
|
||||||
|
IExportService(IExportService&&) = delete;
|
||||||
|
IExportService& operator=(IExportService&&) = delete;
|
||||||
|
|
||||||
|
/// @brief Prepares the export destination for a new run.
|
||||||
|
virtual void Initialize() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Persists one generated brewery record.
|
||||||
|
*
|
||||||
|
* @param brewery Generated brewery payload to store.
|
||||||
|
*/
|
||||||
|
virtual void ProcessRecord(const GeneratedBrewery& brewery) = 0;
|
||||||
|
|
||||||
|
/// @brief Finalizes the export destination.
|
||||||
|
virtual void Finalize() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_EXPORT_SERVICE_H_
|
||||||
59
pipeline/includes/services/sqlite_export_service.h
Normal file
59
pipeline/includes/services/sqlite_export_service.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_SQLITE_EXPORT_SERVICE_H_
|
||||||
|
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_SQLITE_EXPORT_SERVICE_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file services/sqlite_export_service.h
|
||||||
|
* @brief SQLite-backed export service for generated brewery data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "services/date_time_provider.h"
|
||||||
|
#include "services/export_service.h"
|
||||||
|
#include "services/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Persists generated brewery records into a fresh SQLite database.
|
||||||
|
*/
|
||||||
|
class SqliteExportService final : public IExportService {
|
||||||
|
public:
|
||||||
|
SqliteExportService();
|
||||||
|
~SqliteExportService() override;
|
||||||
|
|
||||||
|
SqliteExportService(const SqliteExportService&) = delete;
|
||||||
|
SqliteExportService& operator=(const SqliteExportService&) = delete;
|
||||||
|
SqliteExportService(SqliteExportService&&) = delete;
|
||||||
|
SqliteExportService& operator=(SqliteExportService&&) = delete;
|
||||||
|
|
||||||
|
void Initialize() override;
|
||||||
|
void ProcessRecord(const GeneratedBrewery& brewery) override;
|
||||||
|
void Finalize() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using SqliteDatabaseHandle =
|
||||||
|
sqlite_export_service_internal::SqliteDatabaseHandle;
|
||||||
|
using SqliteStatementHandle =
|
||||||
|
sqlite_export_service_internal::SqliteStatementHandle;
|
||||||
|
|
||||||
|
void InitializeSchema();
|
||||||
|
void PrepareStatements();
|
||||||
|
void RollbackAndCloseNoThrow() noexcept;
|
||||||
|
void FinalizeStatements() noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] std::filesystem::path BuildDatabasePath() const;
|
||||||
|
[[nodiscard]] static std::string BuildLocationKey(const Location& location);
|
||||||
|
|
||||||
|
std::unique_ptr<IDateTimeProvider> date_time_provider_;
|
||||||
|
std::string run_timestamp_utc_;
|
||||||
|
std::filesystem::path database_path_;
|
||||||
|
SqliteDatabaseHandle db_handle_;
|
||||||
|
SqliteStatementHandle insert_location_stmt_;
|
||||||
|
SqliteStatementHandle insert_brewery_stmt_;
|
||||||
|
bool transaction_open_ = false;
|
||||||
|
std::unordered_map<std::string, sqlite3_int64> location_cache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_SQLITE_EXPORT_SERVICE_H_
|
||||||
250
pipeline/includes/services/sqlite_export_service_helpers.h
Normal file
250
pipeline/includes/services/sqlite_export_service_helpers.h
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_SQLITE_EXPORT_SERVICE_HELPERS_H_
|
||||||
|
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_SQLITE_EXPORT_SERVICE_HELPERS_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file services/sqlite_export_service_helpers.h
|
||||||
|
* @brief Internal SQLite export helpers shared across per-method translation
|
||||||
|
* units.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sqlite_export_service_internal {
|
||||||
|
|
||||||
|
struct SqliteDatabaseDeleter {
|
||||||
|
void operator()(sqlite3* handle) const noexcept {
|
||||||
|
if (handle != nullptr) {
|
||||||
|
sqlite3_close(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SqliteStatementDeleter {
|
||||||
|
void operator()(sqlite3_stmt* statement) const noexcept {
|
||||||
|
if (statement != nullptr) {
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using SqliteDatabaseHandle = std::unique_ptr<sqlite3, SqliteDatabaseDeleter>;
|
||||||
|
using SqliteStatementHandle =
|
||||||
|
std::unique_ptr<sqlite3_stmt, SqliteStatementDeleter>;
|
||||||
|
|
||||||
|
inline constexpr std::string_view kCreateLocationsTableSql = R"sql(
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS locations (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
city TEXT NOT NULL,
|
||||||
|
state_province TEXT NOT NULL,
|
||||||
|
iso3166_2 TEXT NOT NULL,
|
||||||
|
country TEXT NOT NULL,
|
||||||
|
iso3166_1 TEXT NOT NULL,
|
||||||
|
local_languages_json TEXT NOT NULL,
|
||||||
|
latitude REAL NOT NULL,
|
||||||
|
longitude REAL NOT NULL,
|
||||||
|
UNIQUE(city, state_province, iso3166_2, country, latitude, longitude)
|
||||||
|
);
|
||||||
|
|
||||||
|
)sql";
|
||||||
|
|
||||||
|
inline constexpr std::string_view kCreateBreweriesTableSql = R"sql(
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS breweries (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
location_id INTEGER NOT NULL,
|
||||||
|
name_en TEXT NOT NULL,
|
||||||
|
description_en TEXT NOT NULL,
|
||||||
|
name_local TEXT NOT NULL,
|
||||||
|
description_local TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(location_id) REFERENCES locations(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_breweries_location_id ON breweries(location_id);
|
||||||
|
|
||||||
|
)sql";
|
||||||
|
|
||||||
|
inline constexpr std::string_view kInsertLocationSql = R"sql(
|
||||||
|
INSERT INTO locations (
|
||||||
|
city,
|
||||||
|
state_province,
|
||||||
|
iso3166_2,
|
||||||
|
country,
|
||||||
|
iso3166_1,
|
||||||
|
local_languages_json,
|
||||||
|
latitude,
|
||||||
|
longitude
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
|
)sql";
|
||||||
|
|
||||||
|
inline constexpr std::string_view kInsertBrewerySql = R"sql(
|
||||||
|
INSERT INTO breweries (
|
||||||
|
location_id,
|
||||||
|
name_en,
|
||||||
|
description_en,
|
||||||
|
name_local,
|
||||||
|
description_local
|
||||||
|
) VALUES (?, ?, ?, ?, ?);
|
||||||
|
)sql";
|
||||||
|
|
||||||
|
inline constexpr int kLocationCityBindIndex = 1;
|
||||||
|
inline constexpr int kLocationStateProvinceBindIndex = 2;
|
||||||
|
inline constexpr int kLocationIso31662BindIndex = 3;
|
||||||
|
inline constexpr int kLocationCountryBindIndex = 4;
|
||||||
|
inline constexpr int kLocationIso31661BindIndex = 5;
|
||||||
|
inline constexpr int kLocationLanguagesBindIndex = 6;
|
||||||
|
inline constexpr int kLocationLatitudeBindIndex = 7;
|
||||||
|
inline constexpr int kLocationLongitudeBindIndex = 8;
|
||||||
|
|
||||||
|
inline constexpr int kBreweryLocationIdBindIndex = 1;
|
||||||
|
inline constexpr int kBreweryEnglishNameBindIndex = 2;
|
||||||
|
inline constexpr int kBreweryEnglishDescriptionBindIndex = 3;
|
||||||
|
inline constexpr int kBreweryLocalNameBindIndex = 4;
|
||||||
|
inline constexpr int kBreweryLocalDescriptionBindIndex = 5;
|
||||||
|
|
||||||
|
inline void ThrowSqliteError(sqlite3* db_handle, std::string_view action) {
|
||||||
|
const std::string message =
|
||||||
|
db_handle != nullptr ? sqlite3_errmsg(db_handle) : "unknown SQLite error";
|
||||||
|
throw std::runtime_error(std::string(action) + ": " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline SqliteDatabaseHandle OpenDatabase(const std::filesystem::path& path) {
|
||||||
|
sqlite3* raw_handle = nullptr;
|
||||||
|
const std::string path_string = path.string();
|
||||||
|
const int result = sqlite3_open(path_string.c_str(), &raw_handle);
|
||||||
|
SqliteDatabaseHandle handle(raw_handle);
|
||||||
|
if (result != SQLITE_OK) {
|
||||||
|
const std::string message = raw_handle != nullptr
|
||||||
|
? sqlite3_errmsg(raw_handle)
|
||||||
|
: "unknown SQLite error";
|
||||||
|
throw std::runtime_error("Failed to open SQLite export database: " +
|
||||||
|
message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ExecSql(const SqliteDatabaseHandle& db_handle, std::string_view sql,
|
||||||
|
const char* action) {
|
||||||
|
char* error_message = nullptr;
|
||||||
|
const std::string sql_text(sql);
|
||||||
|
const int result = sqlite3_exec(db_handle.get(), sql_text.c_str(), nullptr,
|
||||||
|
nullptr, &error_message);
|
||||||
|
if (result != SQLITE_OK) {
|
||||||
|
const std::string message = error_message != nullptr
|
||||||
|
? error_message
|
||||||
|
: sqlite3_errmsg(db_handle.get());
|
||||||
|
sqlite3_free(error_message);
|
||||||
|
throw std::runtime_error(std::string(action) + ": " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline SqliteStatementHandle PrepareStatement(
|
||||||
|
const SqliteDatabaseHandle& db_handle, std::string_view sql,
|
||||||
|
const char* action) {
|
||||||
|
sqlite3_stmt* raw_statement = nullptr;
|
||||||
|
const std::string sql_text(sql);
|
||||||
|
const int result = sqlite3_prepare_v2(db_handle.get(), sql_text.c_str(), -1,
|
||||||
|
&raw_statement, nullptr);
|
||||||
|
SqliteStatementHandle statement(raw_statement);
|
||||||
|
if (result != SQLITE_OK) {
|
||||||
|
ThrowSqliteError(db_handle.get(), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ResetStatement(SqliteStatementHandle& statement) {
|
||||||
|
if (statement != nullptr) {
|
||||||
|
sqlite3_reset(statement.get());
|
||||||
|
sqlite3_clear_bindings(statement.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void DeleteCharArray(void* data) noexcept {
|
||||||
|
delete[] static_cast<char*>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void BindText(const SqliteStatementHandle& statement, int index,
|
||||||
|
std::string_view value, const char* action) {
|
||||||
|
const auto byte_count = value.size();
|
||||||
|
if (byte_count > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
|
||||||
|
ThrowSqliteError(sqlite3_db_handle(statement.get()), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buffer = std::make_unique<char[]>(byte_count + 1);
|
||||||
|
std::memcpy(buffer.get(), value.data(), byte_count);
|
||||||
|
buffer[byte_count] = '\0';
|
||||||
|
|
||||||
|
char* raw_buffer = buffer.release();
|
||||||
|
const int result =
|
||||||
|
sqlite3_bind_text(statement.get(), index, raw_buffer,
|
||||||
|
static_cast<int>(byte_count), DeleteCharArray);
|
||||||
|
if (result != SQLITE_OK) {
|
||||||
|
DeleteCharArray(raw_buffer);
|
||||||
|
ThrowSqliteError(sqlite3_db_handle(statement.get()), action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void BindDouble(const SqliteStatementHandle& statement, int index,
|
||||||
|
double value, std::string_view action) {
|
||||||
|
const int result = sqlite3_bind_double(statement.get(), index, value);
|
||||||
|
if (result != SQLITE_OK) {
|
||||||
|
ThrowSqliteError(sqlite3_db_handle(statement.get()), action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void BindInt64(const SqliteStatementHandle& statement, int index,
|
||||||
|
sqlite3_int64 value, std::string_view action) {
|
||||||
|
const int result = sqlite3_bind_int64(statement.get(), index, value);
|
||||||
|
if (result != SQLITE_OK) {
|
||||||
|
ThrowSqliteError(sqlite3_db_handle(statement.get()), action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void StepStatement(const SqliteDatabaseHandle& db_handle,
|
||||||
|
const SqliteStatementHandle& statement,
|
||||||
|
std::string_view action) {
|
||||||
|
const int result = sqlite3_step(statement.get());
|
||||||
|
if (result != SQLITE_DONE) {
|
||||||
|
ThrowSqliteError(db_handle.get(), action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline sqlite3_int64 LastInsertRowId(const SqliteDatabaseHandle& db_handle) {
|
||||||
|
return sqlite3_last_insert_rowid(db_handle.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void RollbackTransactionNoThrow(
|
||||||
|
const SqliteDatabaseHandle& db_handle) noexcept {
|
||||||
|
if (!db_handle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_exec(db_handle.get(), "ROLLBACK;", nullptr, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string SerializeLocalLanguages(
|
||||||
|
const std::vector<std::string>& local_languages) {
|
||||||
|
boost::json::array array;
|
||||||
|
array.reserve(local_languages.size());
|
||||||
|
for (const auto& language : local_languages) {
|
||||||
|
array.emplace_back(language);
|
||||||
|
}
|
||||||
|
return boost::json::serialize(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sqlite_export_service_internal
|
||||||
|
|
||||||
|
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_SQLITE_EXPORT_SERVICE_HELPERS_H_
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
BiergartenDataGenerator::BiergartenDataGenerator(
|
BiergartenDataGenerator::BiergartenDataGenerator(
|
||||||
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)
|
||||||
: context_service_(std::move(context_service)),
|
: context_service_(std::move(context_service)),
|
||||||
generator_(std::move(generator)) {}
|
generator_(std::move(generator)),
|
||||||
|
exporter_(std::move(exporter)) {}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ void BiergartenDataGenerator::GenerateBreweries(
|
|||||||
|
|
||||||
generated_breweries_.clear();
|
generated_breweries_.clear();
|
||||||
size_t skipped_count = 0;
|
size_t skipped_count = 0;
|
||||||
|
size_t export_failed_count = 0;
|
||||||
|
|
||||||
for (const auto& [location, region_context] : cities) {
|
for (const auto& [location, region_context] : cities) {
|
||||||
try {
|
try {
|
||||||
@@ -22,6 +23,17 @@ void BiergartenDataGenerator::GenerateBreweries(
|
|||||||
const GeneratedBrewery gen{.location = location, .brewery = brewery};
|
const GeneratedBrewery gen{.location = location, .brewery = brewery};
|
||||||
|
|
||||||
generated_breweries_.push_back(gen);
|
generated_breweries_.push_back(gen);
|
||||||
|
|
||||||
|
try {
|
||||||
|
exporter_->ProcessRecord(gen);
|
||||||
|
} catch (const std::exception& export_exception) {
|
||||||
|
++export_failed_count;
|
||||||
|
|
||||||
|
spdlog::warn(
|
||||||
|
"[Pipeline] Generated brewery for '{}' ({}) but SQLite export "
|
||||||
|
"failed: {}",
|
||||||
|
location.city, location.country, export_exception.what());
|
||||||
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
++skipped_count;
|
++skipped_count;
|
||||||
|
|
||||||
@@ -36,4 +48,11 @@ void BiergartenDataGenerator::GenerateBreweries(
|
|||||||
spdlog::warn("[Pipeline] Skipped {} city/cities due to generation errors",
|
spdlog::warn("[Pipeline] Skipped {} city/cities due to generation errors",
|
||||||
skipped_count);
|
skipped_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (export_failed_count > 0) {
|
||||||
|
spdlog::warn(
|
||||||
|
"[Pipeline] Failed to export {} generated brewery/breweries to "
|
||||||
|
"SQLite",
|
||||||
|
export_failed_count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ void BiergartenDataGenerator::LogResults() const {
|
|||||||
index, location.city, location.country, location.state_province,
|
index, location.city, location.country, location.state_province,
|
||||||
location.iso3166_2, location.latitude, location.longitude);
|
location.iso3166_2, location.latitude, location.longitude);
|
||||||
spdlog::info(" brewery_name_en=\"{}\"", brewery.name_en);
|
spdlog::info(" brewery_name_en=\"{}\"", brewery.name_en);
|
||||||
spdlog::info(" brewery_description_en=\"{}\"",
|
spdlog::info(" brewery_description_en=\"{}\"", brewery.description_en);
|
||||||
brewery.description_en);
|
|
||||||
spdlog::info(" brewery_name_local=\"{}\"", brewery.name_local);
|
spdlog::info(" brewery_name_local=\"{}\"", brewery.name_local);
|
||||||
spdlog::info(" brewery_description_local=\"{}\"",
|
spdlog::info(" brewery_description_local=\"{}\"",
|
||||||
brewery.description_local);
|
brewery.description_local);
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
* @brief BiergartenDataGenerator::Run() implementation.
|
* @brief BiergartenDataGenerator::Run() implementation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "biergarten_data_generator.h"
|
#include "biergarten_data_generator.h"
|
||||||
|
|
||||||
bool BiergartenDataGenerator::Run() {
|
bool BiergartenDataGenerator::Run() {
|
||||||
try {
|
try {
|
||||||
|
exporter_->Initialize();
|
||||||
|
|
||||||
std::vector<Location> cities = QueryCitiesWithCountries();
|
std::vector<Location> cities = QueryCitiesWithCountries();
|
||||||
std::vector<EnrichedCity> enriched;
|
std::vector<EnrichedCity> enriched;
|
||||||
enriched.reserve(cities.size());
|
enriched.reserve(cities.size());
|
||||||
@@ -20,7 +22,7 @@ bool BiergartenDataGenerator::Run() {
|
|||||||
try {
|
try {
|
||||||
std::string region_context = context_service_->GetLocationContext(city);
|
std::string region_context = context_service_->GetLocationContext(city);
|
||||||
spdlog::debug("[Pipeline] Context for '{}' ({}) gathered:\n{}",
|
spdlog::debug("[Pipeline] Context for '{}' ({}) gathered:\n{}",
|
||||||
city.city, city.country, region_context);
|
city.city, city.country, region_context);
|
||||||
|
|
||||||
enriched.push_back(
|
enriched.push_back(
|
||||||
EnrichedCity{.location = std::move(city),
|
EnrichedCity{.location = std::move(city),
|
||||||
@@ -40,6 +42,7 @@ bool BiergartenDataGenerator::Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->GenerateBreweries(enriched);
|
this->GenerateBreweries(enriched);
|
||||||
|
exporter_->Finalize();
|
||||||
this->LogResults();
|
this->LogResults();
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ static bool ReadRequiredTrimmedStringField(const boost::json::object& obj,
|
|||||||
const boost::json::value* field = obj.if_contains(key);
|
const boost::json::value* field = obj.if_contains(key);
|
||||||
if (field == nullptr || !field->is_string()) {
|
if (field == nullptr || !field->is_string()) {
|
||||||
if (error_out != nullptr) {
|
if (error_out != nullptr) {
|
||||||
*error_out = "JSON field '" + std::string(key) +
|
*error_out =
|
||||||
"' is missing or not a string";
|
"JSON field '" + std::string(key) + "' is missing or not a string";
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -192,8 +192,7 @@ std::optional<std::string> ValidateBreweryJson(const std::string& raw,
|
|||||||
return validation_error;
|
return validation_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ReadRequiredTrimmedStringField(obj, "name_local",
|
if (!ReadRequiredTrimmedStringField(obj, "name_local", brewery_out.name_local,
|
||||||
brewery_out.name_local,
|
|
||||||
&validation_error)) {
|
&validation_error)) {
|
||||||
return validation_error;
|
return validation_error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ static constexpr size_t kPromptTokenSlack = 8;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using SamplerHandle = std::unique_ptr<llama_sampler, decltype(&llama_sampler_free)>;
|
using SamplerHandle =
|
||||||
|
std::unique_ptr<llama_sampler, decltype(&llama_sampler_free)>;
|
||||||
|
|
||||||
struct SamplerConfig {
|
struct SamplerConfig {
|
||||||
float temperature;
|
float temperature;
|
||||||
@@ -117,17 +118,10 @@ std::string LlamaGenerator::InferFormatted(const std::string& formatted_prompt,
|
|||||||
std::vector<llama_token> prompt_tokens(formatted_prompt.size() +
|
std::vector<llama_token> prompt_tokens(formatted_prompt.size() +
|
||||||
kPromptTokenSlack);
|
kPromptTokenSlack);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int32_t token_count = llama_tokenize(
|
int32_t token_count = llama_tokenize(
|
||||||
vocab,
|
vocab, formatted_prompt.c_str(),
|
||||||
formatted_prompt.c_str(),
|
static_cast<int32_t>(formatted_prompt.size()), prompt_tokens.data(),
|
||||||
static_cast<int32_t>(formatted_prompt.size()),
|
static_cast<int32_t>(prompt_tokens.size()), true, true);
|
||||||
prompt_tokens.data(),
|
|
||||||
static_cast<int32_t>(prompt_tokens.size()),
|
|
||||||
true,
|
|
||||||
true);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If buffer too small, negative return indicates required size
|
* If buffer too small, negative return indicates required size
|
||||||
@@ -135,7 +129,6 @@ std::string LlamaGenerator::InferFormatted(const std::string& formatted_prompt,
|
|||||||
if (token_count < 0) {
|
if (token_count < 0) {
|
||||||
prompt_tokens.resize(static_cast<size_t>(-token_count));
|
prompt_tokens.resize(static_cast<size_t>(-token_count));
|
||||||
|
|
||||||
|
|
||||||
token_count = llama_tokenize(
|
token_count = llama_tokenize(
|
||||||
vocab, formatted_prompt.c_str(),
|
vocab, formatted_prompt.c_str(),
|
||||||
static_cast<int32_t>(formatted_prompt.size()), prompt_tokens.data(),
|
static_cast<int32_t>(formatted_prompt.size()), prompt_tokens.data(),
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
#include "data_generation/llama_generator.h"
|
#include "data_generation/llama_generator.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "data_model/application_options.h"
|
#include "data_model/application_options.h"
|
||||||
#include "llama.h"
|
#include "llama.h"
|
||||||
@@ -30,9 +30,9 @@ void LlamaGenerator::ContextDeleter::operator()(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LlamaGenerator::LlamaGenerator(const ApplicationOptions& options,
|
LlamaGenerator::LlamaGenerator(
|
||||||
const std::string& model_path,
|
const ApplicationOptions& options, const std::string& model_path,
|
||||||
std::unique_ptr<IPromptFormatter> prompt_formatter)
|
std::unique_ptr<IPromptFormatter> prompt_formatter)
|
||||||
: rng_(std::random_device{}()),
|
: rng_(std::random_device{}()),
|
||||||
prompt_formatter_(std::move(prompt_formatter)) {
|
prompt_formatter_(std::move(prompt_formatter)) {
|
||||||
if (model_path.empty()) {
|
if (model_path.empty()) {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ std::string LlamaGenerator::LoadBrewerySystemPrompt(
|
|||||||
return brewery_system_prompt_;
|
return brewery_system_prompt_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::ifstream prompt_file(prompt_file_path);
|
std::ifstream prompt_file(prompt_file_path);
|
||||||
if (!prompt_file.is_open()) {
|
if (!prompt_file.is_open()) {
|
||||||
spdlog::error(
|
spdlog::error(
|
||||||
|
|||||||
@@ -6,15 +6,15 @@
|
|||||||
|
|
||||||
#include "json_handling/json_loader.h"
|
#include "json_handling/json_loader.h"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <boost/json.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include <boost/json.hpp>
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
|
|
||||||
static std::string ReadRequiredString(const boost::json::object& object,
|
static std::string ReadRequiredString(const boost::json::object& object,
|
||||||
const char* key) {
|
const char* key) {
|
||||||
const boost::json::value* value = object.if_contains(key);
|
const boost::json::value* value = object.if_contains(key);
|
||||||
@@ -40,8 +40,8 @@ static std::vector<std::string> ReadRequiredStringArray(
|
|||||||
const boost::json::object& object, const char* key) {
|
const boost::json::object& object, const char* key) {
|
||||||
const boost::json::value* value = object.if_contains(key);
|
const boost::json::value* value = object.if_contains(key);
|
||||||
if (value == nullptr || !value->is_array()) {
|
if (value == nullptr || !value->is_array()) {
|
||||||
throw std::runtime_error(std::string("Missing or invalid string array field: ") +
|
throw std::runtime_error(
|
||||||
key);
|
std::string("Missing or invalid string array field: ") + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& array = value->as_array();
|
const auto& array = value->as_array();
|
||||||
@@ -49,8 +49,8 @@ static std::vector<std::string> ReadRequiredStringArray(
|
|||||||
items.reserve(array.size());
|
items.reserve(array.size());
|
||||||
for (const auto& item : array) {
|
for (const auto& item : array) {
|
||||||
if (!item.is_string()) {
|
if (!item.is_string()) {
|
||||||
throw std::runtime_error(std::string("Missing or invalid string array field: ") +
|
throw std::runtime_error(
|
||||||
key);
|
std::string("Missing or invalid string array field: ") + key);
|
||||||
}
|
}
|
||||||
items.emplace_back(item.as_string());
|
items.emplace_back(item.as_string());
|
||||||
}
|
}
|
||||||
@@ -98,8 +98,7 @@ std::vector<Location> JsonLoader::LoadLocations(
|
|||||||
.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 =
|
.local_languages = ReadRequiredStringArray(object, "local_languages"),
|
||||||
ReadRequiredStringArray(object, "local_languages"),
|
|
||||||
.latitude = ReadRequiredNumber(object, "latitude"),
|
.latitude = ReadRequiredNumber(object, "latitude"),
|
||||||
.longitude = ReadRequiredNumber(object, "longitude"),
|
.longitude = ReadRequiredNumber(object, "longitude"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#include "data_model/application_options.h"
|
#include "data_model/application_options.h"
|
||||||
#include "llama_backend_state.h"
|
#include "llama_backend_state.h"
|
||||||
#include "services/enrichment_service.h"
|
#include "services/enrichment_service.h"
|
||||||
|
#include "services/export_service.h"
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
#include "services/wikipedia_service.h"
|
#include "services/wikipedia_service.h"
|
||||||
#include "web_client/curl_web_client.h"
|
#include "web_client/curl_web_client.h"
|
||||||
|
|
||||||
@@ -160,6 +162,7 @@ int main(const int argc, char** argv) {
|
|||||||
di::bind<WebClient>().to<CURLWebClient>(),
|
di::bind<WebClient>().to<CURLWebClient>(),
|
||||||
di::bind<ApplicationOptions>().to(options),
|
di::bind<ApplicationOptions>().to(options),
|
||||||
di::bind<IEnrichmentService>().to<WikipediaService>(),
|
di::bind<IEnrichmentService>().to<WikipediaService>(),
|
||||||
|
di::bind<IExportService>().to<SqliteExportService>(),
|
||||||
di::bind<IPromptFormatter>().to<Gemma4JinjaPromptFormatter>(),
|
di::bind<IPromptFormatter>().to<Gemma4JinjaPromptFormatter>(),
|
||||||
di::bind<std::string>().to(options.model_path),
|
di::bind<std::string>().to(options.model_path),
|
||||||
di::bind<DataGenerator>().to(
|
di::bind<DataGenerator>().to(
|
||||||
@@ -178,9 +181,10 @@ int main(const int argc, char** argv) {
|
|||||||
return inj.template create<std::unique_ptr<LlamaGenerator>>();
|
return inj.template create<std::unique_ptr<LlamaGenerator>>();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
auto generator = injector.create<BiergartenDataGenerator>();
|
auto generator =
|
||||||
|
injector.create<std::unique_ptr<BiergartenDataGenerator>>();
|
||||||
|
|
||||||
if (!generator.Run()) {
|
if (!generator->Run()) {
|
||||||
spdlog::error("Pipeline execution failed");
|
spdlog::error("Pipeline execution failed");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
24
pipeline/src/services/sqlite/build_database_path.cc
Normal file
24
pipeline/src/services/sqlite/build_database_path.cc
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/build_database_path.cc
|
||||||
|
* @brief SqliteExportService::BuildDatabasePath() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
|
||||||
|
std::filesystem::path SqliteExportService::BuildDatabasePath() const {
|
||||||
|
std::filesystem::path base_filename("biergarten_seed_" + run_timestamp_utc_ +
|
||||||
|
".sqlite");
|
||||||
|
std::filesystem::path candidate =
|
||||||
|
std::filesystem::current_path() / base_filename;
|
||||||
|
|
||||||
|
for (int suffix = 1; std::filesystem::exists(candidate); ++suffix) {
|
||||||
|
candidate = std::filesystem::current_path() /
|
||||||
|
std::filesystem::path("biergarten_seed_" + run_timestamp_utc_ +
|
||||||
|
"-" + std::to_string(suffix) + ".sqlite");
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
28
pipeline/src/services/sqlite/build_location_key.cc
Normal file
28
pipeline/src/services/sqlite/build_location_key.cc
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/build_location_key.cc
|
||||||
|
* @brief SqliteExportService::BuildLocationKey() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
#include "services/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
|
constexpr int kLocationPrecision = 17;
|
||||||
|
|
||||||
|
std::string SqliteExportService::BuildLocationKey(const Location& location) {
|
||||||
|
std::ostringstream key_stream;
|
||||||
|
key_stream << location.city << '\n'
|
||||||
|
<< location.state_province << '\n'
|
||||||
|
<< location.iso3166_2 << '\n'
|
||||||
|
<< location.country << '\n'
|
||||||
|
<< location.iso3166_1 << '\n'
|
||||||
|
<< std::setprecision(kLocationPrecision) << location.latitude
|
||||||
|
<< '\n'
|
||||||
|
<< std::setprecision(kLocationPrecision) << location.longitude
|
||||||
|
<< '\n'
|
||||||
|
<< sqlite_export_service_internal::SerializeLocalLanguages(
|
||||||
|
location.local_languages);
|
||||||
|
return key_stream.str();
|
||||||
|
}
|
||||||
30
pipeline/src/services/sqlite/finalize.cc
Normal file
30
pipeline/src/services/sqlite/finalize.cc
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/finalize.cc
|
||||||
|
* @brief SqliteExportService::Finalize() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
#include "services/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
|
void SqliteExportService::Finalize() {
|
||||||
|
if (db_handle_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
FinalizeStatements();
|
||||||
|
if (transaction_open_) {
|
||||||
|
sqlite_export_service_internal::ExecSql(
|
||||||
|
db_handle_, "COMMIT;", "Failed to commit SQLite transaction");
|
||||||
|
transaction_open_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_handle_.reset();
|
||||||
|
location_cache_.clear();
|
||||||
|
} catch (...) {
|
||||||
|
RollbackAndCloseNoThrow();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
pipeline/src/services/sqlite/finalize_statements.cc
Normal file
11
pipeline/src/services/sqlite/finalize_statements.cc
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/finalize_statements.cc
|
||||||
|
* @brief SqliteExportService::FinalizeStatements() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
|
||||||
|
void SqliteExportService::FinalizeStatements() noexcept {
|
||||||
|
insert_brewery_stmt_.reset();
|
||||||
|
insert_location_stmt_.reset();
|
||||||
|
}
|
||||||
39
pipeline/src/services/sqlite/initialize.cc
Normal file
39
pipeline/src/services/sqlite/initialize.cc
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/initialize.cc
|
||||||
|
* @brief SqliteExportService::Initialize() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
#include "services/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
|
void SqliteExportService::Initialize() {
|
||||||
|
if (db_handle_ != nullptr) {
|
||||||
|
throw std::runtime_error("SQLite export service is already initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
run_timestamp_utc_ = date_time_provider_->GetUtcTimestamp();
|
||||||
|
database_path_ = BuildDatabasePath();
|
||||||
|
std::filesystem::create_directories(database_path_.parent_path());
|
||||||
|
|
||||||
|
db_handle_ = sqlite_export_service_internal::OpenDatabase(database_path_);
|
||||||
|
|
||||||
|
try {
|
||||||
|
sqlite_export_service_internal::ExecSql(
|
||||||
|
db_handle_, "PRAGMA foreign_keys = ON;",
|
||||||
|
"Failed to enable SQLite foreign keys");
|
||||||
|
InitializeSchema();
|
||||||
|
PrepareStatements();
|
||||||
|
sqlite_export_service_internal::ExecSql(
|
||||||
|
db_handle_, "BEGIN IMMEDIATE TRANSACTION;",
|
||||||
|
"Failed to begin SQLite transaction");
|
||||||
|
transaction_open_ = true;
|
||||||
|
} catch (...) {
|
||||||
|
RollbackAndCloseNoThrow();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
pipeline/src/services/sqlite/initialize_schema.cc
Normal file
16
pipeline/src/services/sqlite/initialize_schema.cc
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/initialize_schema.cc
|
||||||
|
* @brief SqliteExportService::InitializeSchema() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
#include "services/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
|
void SqliteExportService::InitializeSchema() {
|
||||||
|
sqlite_export_service_internal::ExecSql(
|
||||||
|
db_handle_, sqlite_export_service_internal::kCreateLocationsTableSql,
|
||||||
|
"Failed to create SQLite locations table");
|
||||||
|
sqlite_export_service_internal::ExecSql(
|
||||||
|
db_handle_, sqlite_export_service_internal::kCreateBreweriesTableSql,
|
||||||
|
"Failed to create SQLite breweries table");
|
||||||
|
}
|
||||||
16
pipeline/src/services/sqlite/prepare_statements.cc
Normal file
16
pipeline/src/services/sqlite/prepare_statements.cc
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/prepare_statements.cc
|
||||||
|
* @brief SqliteExportService::PrepareStatements() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
#include "services/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
|
void SqliteExportService::PrepareStatements() {
|
||||||
|
insert_location_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
||||||
|
db_handle_, sqlite_export_service_internal::kInsertLocationSql,
|
||||||
|
"Failed to prepare SQLite location insert statement");
|
||||||
|
insert_brewery_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
||||||
|
db_handle_, sqlite_export_service_internal::kInsertBrewerySql,
|
||||||
|
"Failed to prepare SQLite brewery insert statement");
|
||||||
|
}
|
||||||
100
pipeline/src/services/sqlite/process_record.cc
Normal file
100
pipeline/src/services/sqlite/process_record.cc
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/process_record.cc
|
||||||
|
* @brief SqliteExportService::ProcessRecord() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
#include "services/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
|
void SqliteExportService::ProcessRecord(const GeneratedBrewery& brewery) {
|
||||||
|
if (db_handle_ == nullptr || !transaction_open_) {
|
||||||
|
throw std::runtime_error("SQLite export service is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string location_key = BuildLocationKey(brewery.location);
|
||||||
|
const auto cached_location = location_cache_.find(location_key);
|
||||||
|
sqlite3_int64 location_id = 0;
|
||||||
|
|
||||||
|
if (cached_location != location_cache_.end()) {
|
||||||
|
location_id = cached_location->second;
|
||||||
|
} else {
|
||||||
|
const std::string local_languages_json =
|
||||||
|
sqlite_export_service_internal::SerializeLocalLanguages(
|
||||||
|
brewery.location.local_languages);
|
||||||
|
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::kLocationCityBindIndex,
|
||||||
|
brewery.location.city, "Failed to bind SQLite location city");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::kLocationStateProvinceBindIndex,
|
||||||
|
brewery.location.state_province,
|
||||||
|
"Failed to bind SQLite location state/province");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::kLocationIso31662BindIndex,
|
||||||
|
brewery.location.iso3166_2,
|
||||||
|
"Failed to bind SQLite location ISO 3166-2 code");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::kLocationCountryBindIndex,
|
||||||
|
brewery.location.country, "Failed to bind SQLite location country");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::kLocationIso31661BindIndex,
|
||||||
|
brewery.location.iso3166_1,
|
||||||
|
"Failed to bind SQLite location ISO 3166-1 code");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::kLocationLanguagesBindIndex,
|
||||||
|
local_languages_json, "Failed to bind SQLite location languages");
|
||||||
|
sqlite_export_service_internal::BindDouble(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::kLocationLatitudeBindIndex,
|
||||||
|
brewery.location.latitude, "Failed to bind SQLite location latitude");
|
||||||
|
sqlite_export_service_internal::BindDouble(
|
||||||
|
insert_location_stmt_,
|
||||||
|
sqlite_export_service_internal::kLocationLongitudeBindIndex,
|
||||||
|
brewery.location.longitude, "Failed to bind SQLite location longitude");
|
||||||
|
|
||||||
|
sqlite_export_service_internal::StepStatement(
|
||||||
|
db_handle_, insert_location_stmt_,
|
||||||
|
"Failed to insert SQLite location row");
|
||||||
|
|
||||||
|
location_id = sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
||||||
|
location_cache_.emplace(location_key, location_id);
|
||||||
|
sqlite_export_service_internal::ResetStatement(insert_location_stmt_);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite_export_service_internal::BindInt64(
|
||||||
|
insert_brewery_stmt_,
|
||||||
|
sqlite_export_service_internal::kBreweryLocationIdBindIndex, location_id,
|
||||||
|
"Failed to bind SQLite brewery location id");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_brewery_stmt_,
|
||||||
|
sqlite_export_service_internal::kBreweryEnglishNameBindIndex,
|
||||||
|
brewery.brewery.name_en, "Failed to bind SQLite brewery English name");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_brewery_stmt_,
|
||||||
|
sqlite_export_service_internal::kBreweryEnglishDescriptionBindIndex,
|
||||||
|
brewery.brewery.description_en,
|
||||||
|
"Failed to bind SQLite brewery English description");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_brewery_stmt_,
|
||||||
|
sqlite_export_service_internal::kBreweryLocalNameBindIndex,
|
||||||
|
brewery.brewery.name_local, "Failed to bind SQLite brewery local name");
|
||||||
|
sqlite_export_service_internal::BindText(
|
||||||
|
insert_brewery_stmt_,
|
||||||
|
sqlite_export_service_internal::kBreweryLocalDescriptionBindIndex,
|
||||||
|
brewery.brewery.description_local,
|
||||||
|
"Failed to bind SQLite brewery local description");
|
||||||
|
|
||||||
|
sqlite_export_service_internal::StepStatement(
|
||||||
|
db_handle_, insert_brewery_stmt_, "Failed to insert SQLite brewery row");
|
||||||
|
|
||||||
|
sqlite_export_service_internal::ResetStatement(insert_brewery_stmt_);
|
||||||
|
}
|
||||||
21
pipeline/src/services/sqlite/rollback_and_close_no_throw.cc
Normal file
21
pipeline/src/services/sqlite/rollback_and_close_no_throw.cc
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/rollback_and_close_no_throw.cc
|
||||||
|
* @brief SqliteExportService::RollbackAndCloseNoThrow() implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
|
||||||
|
void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
|
||||||
|
if (db_handle_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction_open_) {
|
||||||
|
sqlite_export_service_internal::RollbackTransactionNoThrow(db_handle_);
|
||||||
|
transaction_open_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FinalizeStatements();
|
||||||
|
db_handle_.reset();
|
||||||
|
location_cache_.clear();
|
||||||
|
}
|
||||||
17
pipeline/src/services/sqlite/sqlite_export_service.cc
Normal file
17
pipeline/src/services/sqlite/sqlite_export_service.cc
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @file services/sqlite/sqlite_export_service.cc
|
||||||
|
* @brief SqliteExportService constructor and destructor implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "services/sqlite_export_service.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
SqliteExportService::SqliteExportService()
|
||||||
|
: date_time_provider_(std::make_unique<SystemDateTimeProvider>()) {}
|
||||||
|
|
||||||
|
SqliteExportService::~SqliteExportService() {
|
||||||
|
if (db_handle_ != nullptr) {
|
||||||
|
RollbackAndCloseNoThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
* @brief CURLWebClient::Get() implementation.
|
* @brief CURLWebClient::Get() implementation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "web_client/curl_web_client.h"
|
#include <curl/curl.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include "web_client/curl_web_client.h"
|
||||||
|
|
||||||
using CurlHandle = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>;
|
using CurlHandle = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>;
|
||||||
|
|
||||||
|
|||||||
1415
updates.diff
Normal file
1415
updates.diff
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user