From 3f978f4de484287175b71bf910ca728232874a8b Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Sun, 21 Jun 2026 23:09:29 -0400 Subject: [PATCH] Cleanup user and brewery generation exception logic --- .../biergarten_pipeline_orchestrator.h | 4 +- .../includes/data_model/generated_models.h | 4 +- tooling/pipeline/includes/data_model/models.h | 11 -- .../services/database/export_service.h | 4 +- .../services/database/sqlite_export_service.h | 4 +- .../generate_breweries.cc | 53 ++++++--- .../generate_users.cc | 108 +++++++++++------- .../src/services/sqlite/process_record.cc | 8 +- .../services/sqlite/process_user_record.cc | 4 +- 9 files changed, 114 insertions(+), 86 deletions(-) diff --git a/tooling/pipeline/includes/biergarten_pipeline_orchestrator.h b/tooling/pipeline/includes/biergarten_pipeline_orchestrator.h index c4f9d72..f3f7c8d 100644 --- a/tooling/pipeline/includes/biergarten_pipeline_orchestrator.h +++ b/tooling/pipeline/includes/biergarten_pipeline_orchestrator.h @@ -110,9 +110,9 @@ class BiergartenPipelineOrchestrator { void LogResults() const; /// @brief Stores generated brewery data. - std::vector generated_breweries_; + std::vector generated_breweries_; /// @brief Stores generated user data. - std::vector generated_users_; + std::vector generated_users_; }; #endif // BIERGARTEN_PIPELINE_INCLUDES_BIERGARTEN_DATA_GENERATOR_H_ diff --git a/tooling/pipeline/includes/data_model/generated_models.h b/tooling/pipeline/includes/data_model/generated_models.h index 2d54fc6..aca59b2 100644 --- a/tooling/pipeline/includes/data_model/generated_models.h +++ b/tooling/pipeline/includes/data_model/generated_models.h @@ -74,7 +74,7 @@ struct EnrichedCity { /** * @brief Helper struct to store generated brewery data. */ -struct GeneratedBrewery { +struct BreweryRecord { Location location; BreweryResult brewery; }; @@ -86,7 +86,7 @@ struct GeneratedBrewery { * the orchestrator (never LLM-authored) so a downstream auth-account seeding * consumer can register real accounts from the pipeline's SQLite export. */ -struct GeneratedUser { +struct UserRecord { Location location; UserResult user; std::string email{}; diff --git a/tooling/pipeline/includes/data_model/models.h b/tooling/pipeline/includes/data_model/models.h index 5c39f66..28ca9df 100644 --- a/tooling/pipeline/includes/data_model/models.h +++ b/tooling/pipeline/includes/data_model/models.h @@ -53,17 +53,6 @@ struct Location { double longitude{}; }; -/** - * @brief Non-owning brewery location input. - */ -struct BreweryLocation { - /// @brief City name. - std::string_view city_name; - - /// @brief Country name. - std::string_view country_name; -}; - // ============================================================================ // Name / Persona Models // ============================================================================ diff --git a/tooling/pipeline/includes/services/database/export_service.h b/tooling/pipeline/includes/services/database/export_service.h index 0463f84..3958b2f 100644 --- a/tooling/pipeline/includes/services/database/export_service.h +++ b/tooling/pipeline/includes/services/database/export_service.h @@ -33,14 +33,14 @@ class IExportService { * * @param brewery Generated brewery payload to store. */ - virtual uint64_t ProcessRecord(const GeneratedBrewery& brewery) = 0; + virtual uint64_t ProcessRecord(const BreweryRecord& brewery) = 0; /** * @brief Persists one generated user record. * * @param user Generated user payload to store. */ - virtual uint64_t ProcessRecord(const GeneratedUser& user) = 0; + virtual uint64_t ProcessRecord(const UserRecord& user) = 0; /// @brief Finalizes the export destination. virtual void Finalize() = 0; diff --git a/tooling/pipeline/includes/services/database/sqlite_export_service.h b/tooling/pipeline/includes/services/database/sqlite_export_service.h index 9e44055..de16445 100644 --- a/tooling/pipeline/includes/services/database/sqlite_export_service.h +++ b/tooling/pipeline/includes/services/database/sqlite_export_service.h @@ -30,8 +30,8 @@ class SqliteExportService final : public IExportService { SqliteExportService& operator=(SqliteExportService&&) = delete; void Initialize() override; - uint64_t ProcessRecord(const GeneratedBrewery& brewery) override; - uint64_t ProcessRecord(const GeneratedUser& user) override; + uint64_t ProcessRecord(const BreweryRecord& brewery) override; + uint64_t ProcessRecord(const UserRecord& user) override; void Finalize() override; private: diff --git a/tooling/pipeline/src/biergarten_pipeline_orchestrator/generate_breweries.cc b/tooling/pipeline/src/biergarten_pipeline_orchestrator/generate_breweries.cc index 3834593..2e0eded 100644 --- a/tooling/pipeline/src/biergarten_pipeline_orchestrator/generate_breweries.cc +++ b/tooling/pipeline/src/biergarten_pipeline_orchestrator/generate_breweries.cc @@ -5,6 +5,7 @@ #include #include +#include #include "biergarten_pipeline_orchestrator.h" #include "services/logging/logger.h" @@ -19,28 +20,14 @@ void BiergartenPipelineOrchestrator::GenerateBreweries( size_t skipped_count = 0; size_t export_failed_count = 0; - for (const auto& [location, region_context] : cities) { + const auto generate_record = + [this, &skipped_count]( + const Location& location, + const std::string& region_context) -> std::optional { try { const BreweryResult brewery = generator_->GenerateBrewery(location, region_context); - - const GeneratedBrewery gen{.location = location, .brewery = brewery}; - - generated_breweries_.push_back(gen); - - try { - exporter_->ProcessRecord(gen); - } catch (const std::exception& export_exception) { - ++export_failed_count; - - logger_->Log( - {.level = LogLevel::Warn, - .phase = PipelinePhase::BreweryAndBeerGeneration, - .message = std::format( - "[Pipeline] Generated brewery for '{}' ({}) " - "but SQLite export failed: {}", - location.city, location.country, export_exception.what())}); - } + return BreweryRecord{.location = location, .brewery = brewery}; } catch (const std::exception& e) { ++skipped_count; @@ -50,7 +37,35 @@ void BiergartenPipelineOrchestrator::GenerateBreweries( .message = std::format("[Pipeline] Skipping city '{}' ({}): brewery " "generation failed: {}", location.city, location.country, e.what())}); + return std::nullopt; } + }; + + const auto export_record = [this, &export_failed_count]( + const BreweryRecord& record) { + try { + exporter_->ProcessRecord(record); + } catch (const std::exception& export_exception) { + ++export_failed_count; + logger_->Log( + {.level = LogLevel::Warn, + .phase = PipelinePhase::BreweryAndBeerGeneration, + .message = std::format("[Pipeline] Generated brewery for '{}' ({}) " + "but SQLite export failed: {}", + record.location.city, record.location.country, + export_exception.what())}); + } + }; + + for (const auto& [location, region_context] : cities) { + const std::optional record = + generate_record(location, region_context); + if (!record.has_value()) { + continue; + } + + generated_breweries_.push_back(*record); + export_record(*record); } if (skipped_count > 0) { diff --git a/tooling/pipeline/src/biergarten_pipeline_orchestrator/generate_users.cc b/tooling/pipeline/src/biergarten_pipeline_orchestrator/generate_users.cc index 31abb78..3c00bbe 100644 --- a/tooling/pipeline/src/biergarten_pipeline_orchestrator/generate_users.cc +++ b/tooling/pipeline/src/biergarten_pipeline_orchestrator/generate_users.cc @@ -37,9 +37,13 @@ std::string BuildEmail(const Name& name, std::format("{}.{}", Sanitize(name.first_name), Sanitize(name.last_name)); std::string local_part = base; - for (int suffix = 1; used_local_parts.contains(local_part); ++suffix) { + uint32_t suffix = 1; + + while (used_local_parts.contains(local_part)) { local_part = std::format("{}{}", base, suffix); + ++suffix; } + used_local_parts.insert(local_part); return std::format("{}@thebiergarten.app", local_part); @@ -68,17 +72,17 @@ std::string GenerateDateOfBirth(std::mt19937& rng) { } std::string GenerateRandomPassword(std::mt19937& rng) { - constexpr size_t kPasswordLength = 32; - constexpr std::string_view kCharset = + constexpr size_t k_password_length = 32; + constexpr std::string_view k_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&" "*"; - std::uniform_int_distribution char_dist(0, kCharset.size() - 1); + std::uniform_int_distribution char_dist(0, k_charset.size() - 1); std::string password; - password.reserve(kPasswordLength); - for (size_t i = 0; i < kPasswordLength; ++i) { - password.push_back(kCharset[char_dist(rng)]); + password.reserve(k_password_length); + for (size_t i = 0; i < k_password_length; ++i) { + password.push_back(k_charset[char_dist(rng)]); } return password; } @@ -93,6 +97,7 @@ void BiergartenPipelineOrchestrator::GenerateUsers( const std::vector personas = JsonLoader::LoadPersonas("personas.json", logger_); + if (personas.empty()) { throw std::runtime_error( "No personas available in personas.json for user generation"); @@ -109,6 +114,50 @@ void BiergartenPipelineOrchestrator::GenerateUsers( size_t skipped_count = 0; size_t export_failed_count = 0; + const auto generate_record = + [this, &rng, &used_email_local_parts, &skipped_count]( + const EnrichedCity& city, const UserPersona& persona, + const Name& sampled_name) -> std::optional { + try { + const UserResult user = + generator_->GenerateUser(city, persona, sampled_name); + + return UserRecord{ + .location = city.location, + .user = user, + .email = BuildEmail(sampled_name, used_email_local_parts), + .date_of_birth = GenerateDateOfBirth(rng), + .password = GenerateRandomPassword(rng), + }; + } catch (const std::exception& e) { + ++skipped_count; + logger_->Log({.level = LogLevel::Warn, + .phase = PipelinePhase::UserGeneration, + .message = std::format( + "[Pipeline] Skipping city '{}' ({}): " + "user generation failed: {}", + city.location.city, city.location.country, e.what())}); + return std::nullopt; + } + }; + + const auto export_record = [this, + &export_failed_count](const UserRecord& record) { + try { + exporter_->ProcessRecord(record); + } catch (const std::exception& export_exception) { + ++export_failed_count; + + logger_->Log( + {.level = LogLevel::Warn, + .phase = PipelinePhase::UserGeneration, + .message = std::format("[Pipeline] Generated user for '{}' ({}) but " + "SQLite export failed: {}", + record.location.city, record.location.country, + export_exception.what())}); + } + }; + for (const auto& city : cities) { const std::optional sampled_name = names_by_country.SampleName(city.location.iso3166_1, rng); @@ -119,8 +168,7 @@ void BiergartenPipelineOrchestrator::GenerateUsers( .phase = PipelinePhase::UserGeneration, .message = std::format( "[Pipeline] Skipping city '{}' ({}): no names " - "available for " - "country '{}'", + "available for country '{}'", city.location.city, city.location.country, city.location.iso3166_1)}); continue; @@ -128,42 +176,14 @@ void BiergartenPipelineOrchestrator::GenerateUsers( const UserPersona& persona = personas[persona_dist(rng)]; - try { - const UserResult user = - generator_->GenerateUser(city, persona, *sampled_name); - - const GeneratedUser generated_user{ - .location = city.location, - .user = user, - .email = BuildEmail(*sampled_name, used_email_local_parts), - .date_of_birth = GenerateDateOfBirth(rng), - .password = GenerateRandomPassword(rng), - }; - - generated_users_.push_back(generated_user); - - try { - exporter_->ProcessRecord(generated_user); - } catch (const std::exception& export_exception) { - ++export_failed_count; - - logger_->Log({.level = LogLevel::Warn, - .phase = PipelinePhase::UserGeneration, - .message = std::format( - "[Pipeline] Generated user for '{}' ({}) but " - "SQLite export failed: {}", - city.location.city, city.location.country, - export_exception.what())}); - } - } catch (const std::exception& e) { - ++skipped_count; - logger_->Log({.level = LogLevel::Warn, - .phase = PipelinePhase::UserGeneration, - .message = std::format( - "[Pipeline] Skipping city '{}' ({}): " - "user generation failed: {}", - city.location.city, city.location.country, e.what())}); + const std::optional record = + generate_record(city, persona, *sampled_name); + if (!record.has_value()) { + continue; } + + generated_users_.push_back(*record); + export_record(*record); } if (skipped_count > 0) { diff --git a/tooling/pipeline/src/services/sqlite/process_record.cc b/tooling/pipeline/src/services/sqlite/process_record.cc index dc64cb5..1b265a0 100644 --- a/tooling/pipeline/src/services/sqlite/process_record.cc +++ b/tooling/pipeline/src/services/sqlite/process_record.cc @@ -1,6 +1,6 @@ /** * @file services/sqlite/process_record.cc - * @brief SqliteExportService::ProcessRecord(GeneratedBrewery) implementation + * @brief SqliteExportService::ProcessRecord() implementation * and the shared location-resolution helper. */ @@ -102,7 +102,7 @@ sqlite3_int64 SqliteExportService::ResolveLocationId(const Location& location) { return location_id; } -uint64_t SqliteExportService::ProcessRecord(const GeneratedBrewery& brewery) { +uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) { if (db_handle_ == nullptr || !transaction_open_) { throw std::runtime_error("SQLite export service is not initialized"); } @@ -115,12 +115,14 @@ uint64_t SqliteExportService::ProcessRecord(const GeneratedBrewery& brewery) { .index = sqlite_export_service_internal::kBreweryLocationIdBindIndex, .value = location_id, .action = "Failed to bind SQLite brewery location id"}); + sqlite_export_service_internal::Bind( insert_brewery_stmt_, sqlite_export_service_internal::BindParam{ .index = sqlite_export_service_internal::kBreweryEnglishNameBindIndex, .value = brewery.brewery.name_en, .action = "Failed to bind SQLite brewery English name"}); + sqlite_export_service_internal::Bind( insert_brewery_stmt_, sqlite_export_service_internal::BindParam{ @@ -128,12 +130,14 @@ uint64_t SqliteExportService::ProcessRecord(const GeneratedBrewery& brewery) { kBreweryEnglishDescriptionBindIndex, .value = brewery.brewery.description_en, .action = "Failed to bind SQLite brewery English description"}); + sqlite_export_service_internal::Bind( insert_brewery_stmt_, sqlite_export_service_internal::BindParam{ .index = sqlite_export_service_internal::kBreweryLocalNameBindIndex, .value = brewery.brewery.name_local, .action = "Failed to bind SQLite brewery local name"}); + sqlite_export_service_internal::Bind( insert_brewery_stmt_, sqlite_export_service_internal::BindParam{ diff --git a/tooling/pipeline/src/services/sqlite/process_user_record.cc b/tooling/pipeline/src/services/sqlite/process_user_record.cc index a35d95b..840cc2c 100644 --- a/tooling/pipeline/src/services/sqlite/process_user_record.cc +++ b/tooling/pipeline/src/services/sqlite/process_user_record.cc @@ -1,6 +1,6 @@ /** * @file services/sqlite/process_user_record.cc - * @brief SqliteExportService::ProcessRecord(GeneratedUser) implementation. + * @brief SqliteExportService::ProcessRecord(UserRecord) implementation. */ #include @@ -8,7 +8,7 @@ #include "services/database/sqlite_export_service.h" #include "services/database/sqlite_export_service_helpers.h" -uint64_t SqliteExportService::ProcessRecord(const GeneratedUser& user) { +uint64_t SqliteExportService::ProcessRecord(const UserRecord& user) { if (db_handle_ == nullptr || !transaction_open_) { throw std::runtime_error("SQLite export service is not initialized"); }