mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 10:04:44 +00:00
Pipeline: rename Location to City, add address tables + postal codes
This commit is contained in:
@@ -58,6 +58,10 @@
|
||||
#include "services/enrichment/mock_enrichment.h"
|
||||
#include "services/enrichment/wikipedia_service.h"
|
||||
|
||||
// --- services: postal_code ---
|
||||
#include "services/postal_code/mock_postal_code_service.h"
|
||||
#include "services/postal_code/postal_code_service.h"
|
||||
|
||||
// --- services: logging ---
|
||||
#include "services/logging/log_dispatcher.h"
|
||||
#include "services/logging/log_entry.h"
|
||||
|
||||
@@ -16,6 +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 +35,8 @@ 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 Resolves a postal code for a city from its
|
||||
* curated postal-code data.
|
||||
* @param application_options CLI configuration and paths.
|
||||
*/
|
||||
BiergartenPipelineOrchestrator(
|
||||
@@ -42,6 +45,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 +82,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 +92,7 @@ class BiergartenPipelineOrchestrator {
|
||||
* @return Vector of locations randomly sampled per
|
||||
* PipelineOptions::location_count.
|
||||
*/
|
||||
std::vector<Location> QueryLocations();
|
||||
std::vector<City> QueryLocations();
|
||||
|
||||
/**
|
||||
* @brief Generate breweries for enriched cities.
|
||||
|
||||
@@ -51,7 +51,7 @@ class LlamaGenerator final : public DataGenerator {
|
||||
/**
|
||||
* @brief Generates brewery data for a specific location.
|
||||
*
|
||||
* @param location Location object.
|
||||
* @param location City object.
|
||||
* @param region_context Additional regional context.
|
||||
* @return Generated brewery result.
|
||||
*/
|
||||
|
||||
@@ -41,7 +41,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.
|
||||
@@ -51,7 +51,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,33 @@ struct UserResult {
|
||||
* @brief Enriched city data with Wikipedia context.
|
||||
*/
|
||||
struct EnrichedCity {
|
||||
Location location;
|
||||
City location;
|
||||
std::string region_context{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A physical address: the city plus a concrete postal code selected
|
||||
* from that city's curated postal-code examples.
|
||||
*/
|
||||
struct BreweryAddress {
|
||||
City city;
|
||||
std::string postal_code{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A physical address for a generated user: the city plus a concrete
|
||||
* postal code selected from that city's curated postal-code examples.
|
||||
*/
|
||||
struct UserAddress {
|
||||
City city;
|
||||
std::string postal_code{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper struct to store generated brewery data.
|
||||
*/
|
||||
struct BreweryRecord {
|
||||
Location location;
|
||||
BreweryAddress address;
|
||||
BreweryResult brewery;
|
||||
};
|
||||
|
||||
@@ -107,7 +125,7 @@ struct BreweryRecord {
|
||||
* consumer can register real accounts from the pipeline's SQLite export.
|
||||
*/
|
||||
struct UserRecord {
|
||||
Location location;
|
||||
UserAddress address;
|
||||
UserResult user;
|
||||
std::string email{};
|
||||
std::string date_of_birth{};
|
||||
|
||||
@@ -22,16 +22,39 @@ class ILogger;
|
||||
namespace prog_opts = boost::program_options;
|
||||
|
||||
// ============================================================================
|
||||
// Location Models
|
||||
// City Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Canonical location record for city-level generation.
|
||||
* @brief Curated postal-code metadata for a city, mirroring the
|
||||
* `postal_code` object in the locations dataset.
|
||||
*/
|
||||
struct Location {
|
||||
struct PostalCodeSpec {
|
||||
/**
|
||||
* @brief Permissive country-wide postal-code format regex, used as the
|
||||
* validation fallback for a selected example.
|
||||
*/
|
||||
std::string country_format_regex{};
|
||||
|
||||
/**
|
||||
* @brief City-specific postal-code regexes (more precise than the country
|
||||
* format).
|
||||
*/
|
||||
std::vector<std::string> city_regexes{};
|
||||
|
||||
/**
|
||||
* @brief Concrete example postal codes for this city. The postal code
|
||||
* service selects from these rather than synthesizing codes.
|
||||
*/
|
||||
std::vector<std::string> examples{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Canonical city record for city-level generation.
|
||||
*/
|
||||
struct City {
|
||||
std::string city{};
|
||||
std::string state_province{};
|
||||
std::string postal_code{};
|
||||
|
||||
/**
|
||||
* @brief ISO 3166-2 subdivision code.
|
||||
@@ -51,14 +74,10 @@ struct Location {
|
||||
std::vector<std::string> local_languages{};
|
||||
|
||||
/**
|
||||
* @brief Latitude in decimal degrees.
|
||||
* @brief Postal-code metadata (format regex, city regexes, and concrete
|
||||
* examples) sourced directly from the curated locations dataset.
|
||||
*/
|
||||
double latitude{};
|
||||
|
||||
/**
|
||||
* @brief Longitude in decimal degrees.
|
||||
*/
|
||||
double longitude{};
|
||||
PostalCodeSpec postal_code{};
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
using ForenameList = std::vector<ForenameEntry>;
|
||||
using SurnameList = std::vector<std::string>;
|
||||
using LocationsList = std::vector<Location>;
|
||||
using LocationsList = std::vector<City>;
|
||||
using PersonasList = std::vector<UserPersona>;
|
||||
using ForenamesByCountryMap = std::unordered_map<std::string, ForenameList>;
|
||||
using SurnamesByCountryMap = std::unordered_map<std::string, SurnameList>;
|
||||
|
||||
@@ -45,27 +45,29 @@ 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& city);
|
||||
|
||||
/**
|
||||
* @brief Returns the row id for @p location, inserting it first if it has
|
||||
* @brief Returns the row id for @p city, inserting it first if it has
|
||||
* not already been seen during this run.
|
||||
*
|
||||
* Shared by both ProcessRecord() overloads so breweries and users
|
||||
* referencing the same location resolve to the same row.
|
||||
* Shared by both ProcessRecord() overloads so brewery and user addresses
|
||||
* referencing the same city resolve to the same row.
|
||||
*/
|
||||
[[nodiscard]] sqlite3_int64 ResolveLocationId(const Location& location);
|
||||
[[nodiscard]] sqlite3_int64 ResolveCityId(const City& city);
|
||||
|
||||
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_brewery_address_stmt_;
|
||||
SqliteStatementHandle insert_user_stmt_;
|
||||
SqliteStatementHandle insert_user_address_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,
|
||||
@@ -27,9 +27,9 @@ CREATE TABLE IF NOT EXISTS locations (
|
||||
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)
|
||||
postal_code_country_format_regex TEXT NOT NULL,
|
||||
postal_code_city_regex_json TEXT NOT NULL,
|
||||
UNIQUE(city, state_province, iso3166_2, country)
|
||||
);
|
||||
|
||||
)sql";
|
||||
@@ -38,15 +38,29 @@ 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
|
||||
description_local TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_breweries_location_id ON breweries(location_id);
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kCreateBreweryAddressesTableSql = R"sql(
|
||||
|
||||
CREATE TABLE IF NOT EXISTS brewery_addresses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
brewery_id INTEGER NOT NULL,
|
||||
city_id INTEGER NOT NULL,
|
||||
postal_code TEXT NOT NULL,
|
||||
FOREIGN KEY(brewery_id) REFERENCES breweries(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_brewery_addresses_brewery_id
|
||||
ON brewery_addresses(brewery_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_brewery_addresses_city_id
|
||||
ON brewery_addresses(city_id);
|
||||
|
||||
)sql";
|
||||
|
||||
@@ -54,7 +68,6 @@ inline constexpr std::string_view kCreateUsersTableSql = R"sql(
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
location_id INTEGER NOT NULL,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
gender TEXT NOT NULL,
|
||||
@@ -62,40 +75,61 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
bio TEXT NOT NULL,
|
||||
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
|
||||
date_of_birth TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_location_id ON users(location_id);
|
||||
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kInsertLocationSql = R"sql(
|
||||
INSERT INTO locations (
|
||||
inline constexpr std::string_view kCreateUserAddressesTableSql = R"sql(
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_addresses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
city_id INTEGER NOT NULL,
|
||||
postal_code TEXT NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_addresses_user_id
|
||||
ON user_addresses(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_addresses_city_id
|
||||
ON user_addresses(city_id);
|
||||
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kInsertCitySql = R"sql(
|
||||
INSERT INTO cities (
|
||||
city,
|
||||
state_province,
|
||||
iso3166_2,
|
||||
country,
|
||||
iso3166_1,
|
||||
local_languages_json,
|
||||
latitude,
|
||||
longitude
|
||||
postal_code_country_format_regex,
|
||||
postal_code_city_regex_json
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kInsertBrewerySql = R"sql(
|
||||
INSERT INTO breweries (
|
||||
location_id,
|
||||
name_en,
|
||||
description_en,
|
||||
name_local,
|
||||
description_local
|
||||
) VALUES (?, ?, ?, ?, ?);
|
||||
) VALUES (?, ?, ?, ?);
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kInsertBreweryAddressSql = R"sql(
|
||||
INSERT INTO brewery_addresses (
|
||||
brewery_id,
|
||||
city_id,
|
||||
postal_code
|
||||
) VALUES (?, ?, ?);
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kInsertUserSql = R"sql(
|
||||
INSERT INTO users (
|
||||
location_id,
|
||||
first_name,
|
||||
last_name,
|
||||
gender,
|
||||
@@ -104,33 +138,45 @@ INSERT INTO users (
|
||||
activity_weight,
|
||||
email,
|
||||
date_of_birth
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||
)sql";
|
||||
|
||||
inline constexpr std::string_view kInsertUserAddressSql = R"sql(
|
||||
INSERT INTO user_addresses (
|
||||
user_id,
|
||||
city_id,
|
||||
postal_code
|
||||
) VALUES (?, ?, ?);
|
||||
)sql";
|
||||
|
||||
// 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 {
|
||||
kCityCityBindIndex = 1,
|
||||
kCityStateProvinceBindIndex,
|
||||
kCityIso31662BindIndex,
|
||||
kCityCountryBindIndex,
|
||||
kCityIso31661BindIndex,
|
||||
kCityLanguagesBindIndex,
|
||||
kCityPostalCodeCountryFormatRegexBindIndex,
|
||||
kCityPostalCodeCityRegexJsonBindIndex,
|
||||
};
|
||||
|
||||
enum BreweryBindIndex {
|
||||
kBreweryLocationIdBindIndex = 1,
|
||||
kBreweryEnglishNameBindIndex,
|
||||
kBreweryEnglishNameBindIndex = 1,
|
||||
kBreweryEnglishDescriptionBindIndex,
|
||||
kBreweryLocalNameBindIndex,
|
||||
kBreweryLocalDescriptionBindIndex,
|
||||
};
|
||||
|
||||
enum BreweryAddressBindIndex {
|
||||
kBreweryAddressBreweryIdBindIndex = 1,
|
||||
kBreweryAddressCityIdBindIndex,
|
||||
kBreweryAddressPostalCodeBindIndex,
|
||||
};
|
||||
|
||||
enum UserBindIndex {
|
||||
kUserLocationIdBindIndex = 1,
|
||||
kUserFirstNameBindIndex,
|
||||
kUserFirstNameBindIndex = 1,
|
||||
kUserLastNameBindIndex,
|
||||
kUserGenderBindIndex,
|
||||
kUserUsernameBindIndex,
|
||||
@@ -140,6 +186,12 @@ enum UserBindIndex {
|
||||
kUserDateOfBirthBindIndex,
|
||||
};
|
||||
|
||||
enum UserAddressBindIndex {
|
||||
kUserAddressUserIdBindIndex = 1,
|
||||
kUserAddressCityIdBindIndex,
|
||||
kUserAddressPostalCodeBindIndex,
|
||||
};
|
||||
|
||||
SqliteStatementHandle PrepareStatement(const SqliteDatabaseHandle& db_handle,
|
||||
std::string_view sql,
|
||||
const char* action);
|
||||
|
||||
@@ -20,10 +20,10 @@ class IEnrichmentService {
|
||||
/**
|
||||
* @brief Resolves contextual enrichment for a location.
|
||||
*
|
||||
* @param loc Location to enrich.
|
||||
* @param loc City 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);
|
||||
|
||||
@@ -51,7 +51,7 @@ enum class PipelinePhase {
|
||||
*/
|
||||
Startup,
|
||||
/**
|
||||
* @brief Location/context enrichment (e.g. Wikipedia lookups).
|
||||
* @brief City/context enrichment (e.g. Wikipedia lookups).
|
||||
*/
|
||||
Enrichment,
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#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 Data-driven postal code service that selects the first curated
|
||||
* example for a city and validates it against the city's postal-code regex.
|
||||
*/
|
||||
|
||||
#include <format>
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "services/postal_code/postal_code_service.h"
|
||||
|
||||
/**
|
||||
* @brief Postal code service backed entirely by the curated locations
|
||||
* dataset: it returns the city's first example postal code after checking it
|
||||
* against the curated postal-code regex.
|
||||
*
|
||||
* A precise city regex is preferred when present; otherwise the permissive
|
||||
* country format regex is used as the fallback. A city with no examples, or an
|
||||
* example that fails validation, throws so the caller can skip the city.
|
||||
*/
|
||||
class MockPostalCodeService final : public IPostalCodeService {
|
||||
public:
|
||||
std::string GeneratePostalCode(const City& city) override {
|
||||
const PostalCodeSpec& spec = city.postal_code;
|
||||
|
||||
if (spec.examples.empty()) {
|
||||
throw std::runtime_error(std::format(
|
||||
"No postal-code examples available for city '{}'", city.city));
|
||||
}
|
||||
|
||||
const std::string& candidate = spec.examples.front();
|
||||
|
||||
if (!MatchesAnyPattern(candidate, spec)) {
|
||||
throw std::runtime_error(
|
||||
std::format("Postal code '{}' for city '{}' does not match the "
|
||||
"curated postal-code regex",
|
||||
candidate, city.city));
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Returns true if @p candidate matches any city regex, or the
|
||||
* country format regex when no city regex matches. When the spec carries no
|
||||
* usable regex at all, validation is skipped (returns true).
|
||||
*/
|
||||
static bool MatchesAnyPattern(const std::string& candidate,
|
||||
const PostalCodeSpec& spec) {
|
||||
bool has_pattern = false;
|
||||
|
||||
for (const std::string& pattern : spec.city_regexes) {
|
||||
if (pattern.empty()) {
|
||||
continue;
|
||||
}
|
||||
has_pattern = true;
|
||||
if (std::regex_match(candidate, std::regex(pattern))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!spec.country_format_regex.empty()) {
|
||||
has_pattern = true;
|
||||
if (std::regex_match(candidate, std::regex(spec.country_format_regex))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return !has_pattern;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_POSTAL_CODE_MOCK_POSTAL_CODE_SERVICE_H_
|
||||
@@ -0,0 +1,35 @@
|
||||
#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 resolving a postal code for a city.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "data_model/models.h"
|
||||
|
||||
/**
|
||||
* @brief Interface for services that resolve a postal code for a city.
|
||||
*/
|
||||
class IPostalCodeService {
|
||||
public:
|
||||
IPostalCodeService() = default;
|
||||
virtual ~IPostalCodeService() = default;
|
||||
|
||||
IPostalCodeService(const IPostalCodeService&) = delete;
|
||||
IPostalCodeService& operator=(const IPostalCodeService&) = delete;
|
||||
IPostalCodeService(IPostalCodeService&&) = delete;
|
||||
IPostalCodeService& operator=(IPostalCodeService&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Resolves a postal code for @p city.
|
||||
*
|
||||
* @param city City whose curated postal-code examples supply the result.
|
||||
* @return A postal code string, or an empty string if none is available.
|
||||
*/
|
||||
virtual std::string GeneratePostalCode(const City& city) = 0;
|
||||
};
|
||||
|
||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_POSTAL_CODE_POSTAL_CODE_SERVICE_H_
|
||||
Reference in New Issue
Block a user