mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
Pipeline: rename Location to City, wire postal code into breweries
This commit is contained in:
@@ -64,6 +64,10 @@
|
||||
#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"
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "services/database/export_service.h"
|
||||
#include "services/enrichment/enrichment_service.h"
|
||||
#include "services/logging/logger.h"
|
||||
|
||||
#include "services/postal_code/postal_code_service.h"
|
||||
/**
|
||||
* @brief Main orchestrator for the Biergarten data generation pipeline.
|
||||
*
|
||||
@@ -34,6 +34,7 @@ class BiergartenPipelineOrchestrator {
|
||||
* @param exporter Database backend for persisting generated records.
|
||||
* @param curated_data_service Loads curated location, persona, and name
|
||||
* data used to seed generation.
|
||||
* @param postal_code_service
|
||||
* @param application_options CLI configuration and paths.
|
||||
*/
|
||||
BiergartenPipelineOrchestrator(
|
||||
@@ -42,6 +43,7 @@ class BiergartenPipelineOrchestrator {
|
||||
std::unique_ptr<DataGenerator> generator,
|
||||
std::unique_ptr<IExportService> exporter,
|
||||
std::unique_ptr<ICuratedDataService> curated_data_service,
|
||||
std::unique_ptr<IPostalCodeService> postal_code_service,
|
||||
const ApplicationOptions& application_options);
|
||||
|
||||
/**
|
||||
@@ -78,6 +80,7 @@ class BiergartenPipelineOrchestrator {
|
||||
*/
|
||||
std::unique_ptr<IExportService> exporter_;
|
||||
std::unique_ptr<ICuratedDataService> curated_data_service_;
|
||||
std::unique_ptr<IPostalCodeService> postal_code_service_;
|
||||
|
||||
ApplicationOptions application_options_;
|
||||
|
||||
@@ -87,7 +90,7 @@ class BiergartenPipelineOrchestrator {
|
||||
* @return Vector of locations randomly sampled per
|
||||
* PipelineOptions::location_count.
|
||||
*/
|
||||
std::vector<Location> QueryCitiesWithCountries();
|
||||
std::vector<City> QueryCitiesWithCountries();
|
||||
|
||||
/**
|
||||
* @brief Generate breweries for enriched cities.
|
||||
|
||||
@@ -24,7 +24,7 @@ class DataGenerator {
|
||||
* @param region_context Additional regional context text.
|
||||
* @return Brewery generation result.
|
||||
*/
|
||||
virtual BreweryResult GenerateBrewery(const Location& location,
|
||||
virtual BreweryResult GenerateBrewery(const City& location,
|
||||
const std::string& region_context) = 0;
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,7 +55,7 @@ class LlamaGenerator final : public DataGenerator {
|
||||
* @param region_context Additional regional context.
|
||||
* @return Generated brewery result.
|
||||
*/
|
||||
BreweryResult GenerateBrewery(const Location& location,
|
||||
BreweryResult GenerateBrewery(const City& location,
|
||||
const std::string& region_context) override;
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,7 @@ class MockGenerator final : public DataGenerator {
|
||||
* @param region_context Unused for mock generation.
|
||||
* @return Generated brewery result.
|
||||
*/
|
||||
BreweryResult GenerateBrewery(const Location& location,
|
||||
BreweryResult GenerateBrewery(const City& location,
|
||||
const std::string& region_context) override;
|
||||
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ class MockGenerator final : public DataGenerator {
|
||||
* @param location City and country names.
|
||||
* @return Deterministic hash value.
|
||||
*/
|
||||
static size_t DeterministicHash(const Location& location);
|
||||
static size_t DeterministicHash(const City& location);
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @return Deterministic hash value.
|
||||
*/
|
||||
static size_t DeterministicHash(const Location& location,
|
||||
static size_t DeterministicHash(const City& location,
|
||||
const UserPersona& persona, const Name& name);
|
||||
|
||||
// Hash stride constants for deterministic distribution across fixed-size
|
||||
|
||||
@@ -87,15 +87,27 @@ struct UserResult {
|
||||
* @brief Enriched city data with Wikipedia context.
|
||||
*/
|
||||
struct EnrichedCity {
|
||||
Location location;
|
||||
City location;
|
||||
std::string region_context{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A brewery's street-level address, distinct from the shared `City`
|
||||
* it belongs to. Mirrors the backend's `BreweryPostLocation` shape.
|
||||
*
|
||||
* Only `postal_code` is populated today -- street-address generation has no
|
||||
* fixture data or service yet.
|
||||
*/
|
||||
struct Address {
|
||||
std::string postal_code{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper struct to store generated brewery data.
|
||||
*/
|
||||
struct BreweryRecord {
|
||||
Location location;
|
||||
City location;
|
||||
Address address;
|
||||
BreweryResult brewery;
|
||||
};
|
||||
|
||||
@@ -107,7 +119,7 @@ struct BreweryRecord {
|
||||
* consumer can register real accounts from the pipeline's SQLite export.
|
||||
*/
|
||||
struct UserRecord {
|
||||
Location location;
|
||||
City location;
|
||||
UserResult user;
|
||||
std::string email{};
|
||||
std::string date_of_birth{};
|
||||
|
||||
@@ -22,13 +22,13 @@ class ILogger;
|
||||
namespace prog_opts = boost::program_options;
|
||||
|
||||
// ============================================================================
|
||||
// Location Models
|
||||
// City Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Canonical location record for city-level generation.
|
||||
*/
|
||||
struct Location {
|
||||
struct City {
|
||||
std::string city{};
|
||||
std::string state_province{};
|
||||
|
||||
@@ -44,6 +44,18 @@ struct Location {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
using ForenameList = std::vector<ForenameEntry>;
|
||||
using SurnameList = std::vector<std::string>;
|
||||
using LocationsList = std::vector<Location>;
|
||||
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>;
|
||||
@@ -37,7 +37,7 @@ class ICuratedDataService {
|
||||
/**
|
||||
* @brief Loads all curated location records.
|
||||
*/
|
||||
virtual const LocationsList& LoadLocations() = 0;
|
||||
virtual const CityList& LoadCities() = 0;
|
||||
|
||||
/**
|
||||
* @brief Loads all curated persona records.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* CuratedJsonDataService.
|
||||
*/
|
||||
struct CuratedDataFilePaths {
|
||||
std::filesystem::path locations_path;
|
||||
std::filesystem::path cities_path;
|
||||
std::filesystem::path personas_path;
|
||||
std::filesystem::path forenames_path;
|
||||
std::filesystem::path surnames_path;
|
||||
@@ -27,7 +27,7 @@ struct CuratedDataFilePaths {
|
||||
*/
|
||||
class CuratedJsonDataService final : public ICuratedDataService {
|
||||
struct cache {
|
||||
LocationsList locations;
|
||||
CityList locations;
|
||||
PersonasList personas;
|
||||
ForenamesByCountryMap forenames_by_country;
|
||||
SurnamesByCountryMap surnames_by_country;
|
||||
@@ -43,9 +43,9 @@ class CuratedJsonDataService final : public ICuratedDataService {
|
||||
explicit CuratedJsonDataService(CuratedDataFilePaths filepaths);
|
||||
|
||||
/**
|
||||
* @brief Parses a JSON array file and returns all location records.
|
||||
* @brief Parses a JSON array file and returns all city records.
|
||||
*/
|
||||
const LocationsList& LoadLocations() override;
|
||||
const CityList& LoadCities() override;
|
||||
|
||||
/**
|
||||
* @brief Parses a JSON array file and returns all persona records.
|
||||
|
||||
@@ -20,7 +20,7 @@ class MockCuratedDataService final : public ICuratedDataService {
|
||||
public:
|
||||
MockCuratedDataService();
|
||||
|
||||
const LocationsList& LoadLocations() override;
|
||||
const CityList& LoadCities() override;
|
||||
|
||||
const PersonasList& LoadPersonas() override;
|
||||
|
||||
@@ -29,7 +29,7 @@ class MockCuratedDataService final : public ICuratedDataService {
|
||||
const SurnamesByCountryMap& LoadSurnamesByCountry() override;
|
||||
|
||||
private:
|
||||
LocationsList locations_;
|
||||
CityList locations_;
|
||||
PersonasList personas_;
|
||||
ForenamesByCountryMap forenames_by_country_;
|
||||
SurnamesByCountryMap surnames_by_country_;
|
||||
|
||||
@@ -45,7 +45,7 @@ class SqliteExportService final : public IExportService {
|
||||
void RollbackAndCloseNoThrow() noexcept;
|
||||
|
||||
[[nodiscard]] std::filesystem::path BuildDatabasePath() const;
|
||||
[[nodiscard]] static std::string BuildLocationKey(const Location& location);
|
||||
[[nodiscard]] static std::string BuildCityKey(const City& location);
|
||||
|
||||
/**
|
||||
* @brief Returns the row id for @p location, inserting it first if it has
|
||||
@@ -54,18 +54,18 @@ class SqliteExportService final : public IExportService {
|
||||
* Shared by both ProcessRecord() overloads so breweries and users
|
||||
* referencing the same location resolve to the same row.
|
||||
*/
|
||||
[[nodiscard]] sqlite3_int64 ResolveLocationId(const Location& location);
|
||||
[[nodiscard]] sqlite3_int64 ResolveCityId(const City& location);
|
||||
|
||||
std::unique_ptr<IDateTimeProvider> date_time_provider_;
|
||||
std::filesystem::path output_path_;
|
||||
std::string run_timestamp_utc_;
|
||||
std::filesystem::path database_path_;
|
||||
SqliteDatabaseHandle db_handle_;
|
||||
SqliteStatementHandle insert_location_stmt_;
|
||||
SqliteStatementHandle insert_city_stmt_;
|
||||
SqliteStatementHandle insert_brewery_stmt_;
|
||||
SqliteStatementHandle insert_user_stmt_;
|
||||
bool transaction_open_ = false;
|
||||
std::unordered_map<std::string, sqlite3_int64> location_cache_;
|
||||
std::unordered_map<std::string, sqlite3_int64> city_cache_;
|
||||
};
|
||||
|
||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_DATABASE_SQLITE_EXPORT_SERVICE_H_
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
namespace sqlite_export_service_internal {
|
||||
|
||||
inline constexpr std::string_view kCreateLocationsTableSql = R"sql(
|
||||
inline constexpr std::string_view kCreateCitiesTableSql = R"sql(
|
||||
|
||||
CREATE TABLE IF NOT EXISTS locations (
|
||||
CREATE TABLE IF NOT EXISTS cities (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
city TEXT NOT NULL,
|
||||
state_province TEXT NOT NULL,
|
||||
@@ -38,15 +38,16 @@ inline constexpr std::string_view kCreateBreweriesTableSql = R"sql(
|
||||
|
||||
CREATE TABLE IF NOT EXISTS breweries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
location_id INTEGER NOT NULL,
|
||||
city_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
|
||||
postal_code TEXT NOT NULL,
|
||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_breweries_location_id ON breweries(location_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_breweries_city_id ON breweries(city_id);
|
||||
|
||||
)sql";
|
||||
|
||||
@@ -54,7 +55,7 @@ inline constexpr std::string_view kCreateUsersTableSql = R"sql(
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
location_id INTEGER NOT NULL,
|
||||
city_id INTEGER NOT NULL,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
gender TEXT NOT NULL,
|
||||
@@ -63,15 +64,15 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
activity_weight REAL NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
date_of_birth TEXT NOT NULL,
|
||||
FOREIGN KEY(location_id) REFERENCES locations(id) ON DELETE CASCADE
|
||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_location_id ON users(location_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_city_id ON users(city_id);
|
||||
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kInsertLocationSql = R"sql(
|
||||
INSERT INTO locations (
|
||||
inline constexpr std::string_view kInsertCitySql = R"sql(
|
||||
INSERT INTO cities (
|
||||
city,
|
||||
state_province,
|
||||
iso3166_2,
|
||||
@@ -85,17 +86,18 @@ INSERT INTO locations (
|
||||
|
||||
inline constexpr std::string_view kInsertBrewerySql = R"sql(
|
||||
INSERT INTO breweries (
|
||||
location_id,
|
||||
city_id,
|
||||
name_en,
|
||||
description_en,
|
||||
name_local,
|
||||
description_local
|
||||
) VALUES (?, ?, ?, ?, ?);
|
||||
description_local,
|
||||
postal_code
|
||||
) VALUES (?, ?, ?, ?, ?, ?);
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kInsertUserSql = R"sql(
|
||||
INSERT INTO users (
|
||||
location_id,
|
||||
city_id,
|
||||
first_name,
|
||||
last_name,
|
||||
gender,
|
||||
@@ -109,27 +111,28 @@ INSERT INTO users (
|
||||
|
||||
// sqlite3_bind_*() parameter indices are 1-based, matching the "?"
|
||||
// placeholder order in the SQL above.
|
||||
enum LocationBindIndex {
|
||||
kLocationCityBindIndex = 1,
|
||||
kLocationStateProvinceBindIndex,
|
||||
kLocationIso31662BindIndex,
|
||||
kLocationCountryBindIndex,
|
||||
kLocationIso31661BindIndex,
|
||||
kLocationLanguagesBindIndex,
|
||||
kLocationLatitudeBindIndex,
|
||||
kLocationLongitudeBindIndex,
|
||||
enum CityBindIndex {
|
||||
kCityNameBindIndex = 1,
|
||||
kCityStateProvinceBindIndex,
|
||||
kCityIso31662BindIndex,
|
||||
kCityCountryBindIndex,
|
||||
kCityIso31661BindIndex,
|
||||
kCityLanguagesBindIndex,
|
||||
kCityLatitudeBindIndex,
|
||||
kCityLongitudeBindIndex,
|
||||
};
|
||||
|
||||
enum BreweryBindIndex {
|
||||
kBreweryLocationIdBindIndex = 1,
|
||||
kBreweryCityIdBindIndex = 1,
|
||||
kBreweryEnglishNameBindIndex,
|
||||
kBreweryEnglishDescriptionBindIndex,
|
||||
kBreweryLocalNameBindIndex,
|
||||
kBreweryLocalDescriptionBindIndex,
|
||||
kBreweryPostalCodeBindIndex,
|
||||
};
|
||||
|
||||
enum UserBindIndex {
|
||||
kUserLocationIdBindIndex = 1,
|
||||
kUserCityIdBindIndex = 1,
|
||||
kUserFirstNameBindIndex,
|
||||
kUserLastNameBindIndex,
|
||||
kUserGenderBindIndex,
|
||||
|
||||
@@ -23,7 +23,7 @@ class IEnrichmentService {
|
||||
* @param loc Location to enrich.
|
||||
* @return Context text, or an empty string if unavailable.
|
||||
*/
|
||||
virtual std::string GetLocationContext(const Location& loc) = 0;
|
||||
virtual std::string GetLocationContext(const City& loc) = 0;
|
||||
};
|
||||
|
||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_ENRICHMENT_ENRICHMENT_SERVICE_H_
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
class MockEnrichmentService final : public IEnrichmentService {
|
||||
public:
|
||||
std::string GetLocationContext(const Location& /*loc*/) override {
|
||||
std::string GetLocationContext(const City& /*loc*/) override {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ class WikipediaEnrichmentService final : public IEnrichmentService {
|
||||
/**
|
||||
* @brief Returns the Wikipedia-derived context for a location.
|
||||
*/
|
||||
[[nodiscard]] std::string GetLocationContext(const Location& loc) override;
|
||||
[[nodiscard]] std::string GetLocationContext(const City& loc) override;
|
||||
|
||||
private:
|
||||
std::string FetchExtract(std::string_view query);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#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_
|
||||
@@ -0,0 +1,30 @@
|
||||
#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_
|
||||
Reference in New Issue
Block a user