252 psg& operator=(
psg&&) noexcept = default;
280 return _from.
country->has_data() && _from.
city->has_data() && _from.
birth->has_data()
281 && _from.
biodata->has_data() && _from.
name->has_resources()
283 && _from.
trait->has_data();
289 [[nodiscard]]
bool load(dataset tier)
291 bool ok = _from.
country->load(tier);
292 ok = _from.
city->load(tier) && ok;
293 ok = _from.
birth->load(tier) && ok;
294 ok = _from.
biodata->load(tier) && ok;
295 ok = _from.
name->load(tier) && ok;
296 ok = _from.
interest->load(tier) && ok;
298 ok = _from.
trait->load(tier) && ok;
310 for (
const auto part : {_from.
country->data_hash(), _from.
city->data_hash(),
315 h = core::hash_combine(h, part);
338 std::uint64_t call_seed)
const
340 if (!
has_data())
throw std::runtime_error(
"persona-generator: a library has no data loaded");
341 if (request.
ages && request.
ages->min > request.
ages->max)
342 throw std::invalid_argument(
"persona_request: ages.min must be <= ages.max");
345 std::string country_code;
348 country_code = *request.
country;
349 std::ranges::transform(country_code, country_code.begin(), [](
char c) {
350 return static_cast<char>(c >=
'a' && c <=
'z' ? c -
'a' +
'A' : c);
352 er.inputs[
"country"] = country_code;
355 er.inputs[
"sex"] = std::string{core::sex_name(*request.
person_sex)};
358 er.inputs[
"age_min"] =
static_cast<std::int64_t
>(request.
ages->min);
359 er.inputs[
"age_max"] =
static_cast<std::int64_t
>(request.
ages->max);
361 er.inputs[
"reference_date"] =
362 static_cast<std::int64_t
>(
static_cast<int>(request.
reference_date.year())) * 10000
363 +
static_cast<std::int64_t
>(
static_cast<unsigned>(request.
reference_date.month())) * 100
364 +
static_cast<std::int64_t
>(
static_cast<unsigned>(request.
reference_date.day()));
365 er.inputs[
"traits"] =
static_cast<std::int64_t
>(request.
traits);
368 throw std::runtime_error(
"persona-generator: relationship-generator has no data loaded");
370 const auto e = _pipeline.generate(er, call_seed);
374 p.
city_info = e.get<dasmig::city>(L
"city");
375 p.
birthday = e.get<dasmig::birth>(L
"birth");
376 p.
body = e.get<dasmig::biodata>(L
"biodata");
377 const auto& named = e.get<named_>(L
"name");
379 p.
traits = e.get<dasmig::personality>(L
"traits");
380 p.
interests = e.get<dasmig::interest>(L
"interest");
381 p.
job = e.get<dasmig::occupation>(L
"occupation");
382 if (_from.
city->has_admin1())
384 p._signature = e.signature();
388 family.anchor = anchor_spec{.country_code = p.
country_info.cca2,
390 .birth = std::chrono::year_month_day{
392 std::chrono::month{p.
birthday.month},
394 .name = named.parts};
396 p.
relatives = _from.
relationship->get_family(family, core::rng{call_seed}.split(
"relations").seed());
413 const core::batch_options& options)
434 const core::batch_options& options,
435 std::uint64_t batch_seed)
const
437 return core::run_batch(options, batch_seed, [&](std::uint64_t item_seed)
454 template <
typename Sink>
456 std::uint64_t batch_seed, Sink&& sink)
const
458 core::for_each_batch(options, batch_seed,
459 [&](std::uint64_t item_seed) {
return get_persona(request, item_seed); },
460 std::forward<Sink>(sink));
469 template <
typename Sink>
478 static constexpr std::array<std::string_view, 79> supported_ = {
479 "AE",
"AL",
"AR",
"AT",
"AZ",
"BD",
"BE",
"BG",
"BH",
"BO",
"BR",
"CA",
"CH",
"CL",
"CM",
"CN",
480 "CO",
"CR",
"CY",
"CZ",
"DE",
"DK",
"DZ",
"EC",
"EE",
"EG",
"ES",
"ET",
"FI",
"FR",
"GB",
"GE",
481 "GH",
"GR",
"HN",
"HR",
"HU",
"ID",
"IE",
"IL",
"IN",
"IQ",
"IR",
"IS",
"IT",
"JM",
"JO",
"JP",
482 "KZ",
"LB",
"LT",
"LU",
"LY",
"MA",
"MD",
"MT",
"MX",
"MY",
"NG",
"NL",
"NO",
"PE",
"PH",
483 "PL",
"PT",
"RS",
"RU",
"SA",
"SD",
"SE",
"SG",
"SI",
"SV",
"SY",
"TN",
"TR",
"US",
"YE",
"ZA"};
485 static constexpr int max_country_attempts_ = 10000;
496 core::rng _engine{core::rng::from_entropy()};
503 template <
typename Lib,
typename Fn>
504 class step_ final :
public component
507 step_(std::wstring key, std::vector<std::wstring> depends,
const Lib* lib, Fn fn)
508 : _key{std::move(key)}, _depends{std::move(depends)}, _lib{lib}, _fn{std::move(fn)}
512 [[nodiscard]] std::wstring key()
const override {
return _key; }
513 [[nodiscard]] std::vector<std::wstring> depends_on()
const override {
return _depends; }
514 [[nodiscard]] std::uint64_t data_hash()
const override {
return _lib->data_hash(); }
515 [[nodiscard]] std::any generate(
const generation_context& ctx)
const override {
return std::any{_fn(ctx)}; }
516 [[nodiscard]] std::wstring to_string(
const std::any& )
const override {
return {}; }
517 [[nodiscard]] json_value to_json(
const std::any& )
const override {
return {}; }
521 std::vector<std::wstring> _depends;
526 template <
typename Lib,
typename Fn>
527 void add_(std::wstring key, std::vector<std::wstring> depends,
const Lib* lib, Fn fn)
529 _pipeline.add(std::make_unique<step_<Lib, Fn>>(std::move(key), std::move(depends), lib, std::move(fn)));
532 [[nodiscard]]
static bool is_supported_(std::string_view cca2)
534 return std::ranges::binary_search(supported_, cca2);
537 [[nodiscard]]
static std::optional<sex> sex_input_(
const generation_context& ctx)
539 if (!ctx.has_input(
"sex"))
return std::nullopt;
540 return core::parse_sex(ctx.input<std::string>(
"sex")).value_or(sex::male);
546 const auto from = _from;
548 add_(L
"country", {}, from.country, [from](
const generation_context& ctx) {
549 if (ctx.has_input(
"country"))
550 return from.country->find_country(ctx.input<std::string>(
"country"));
551 for (
int i = 0; i < max_country_attempts_; ++i)
553 auto c = from.country->get_country({}, ctx.random()());
554 if (is_supported_(c.cca2))
return c;
556 throw std::runtime_error(
"persona-generator: no supported country in the country data");
559 add_(L
"city", {L
"country"}, from.city, [from](
const generation_context& ctx) {
560 return from.city->get_city(
561 {.country = ctx.get<
const dasmig::country&>(L
"country").cca2}, ctx.random()());
564 add_(L
"birth", {L
"country"}, from.birth, [from](
const generation_context& ctx) {
566 r.country_code = ctx.get<
const dasmig::country&>(L
"country").cca2;
567 r.bio_sex = sex_input_(ctx);
568 if (ctx.has_input(
"age_min"))
569 r.ages = age_range{.min =
static_cast<std::uint8_t
>(ctx.input<std::int64_t>(
"age_min")),
570 .max =
static_cast<std::uint8_t
>(ctx.input<std::int64_t>(
"age_max"))};
571 const auto d = ctx.input<std::int64_t>(
"reference_date");
572 r.reference_date = std::chrono::year_month_day{
573 std::chrono::year{
static_cast<int>(d / 10000)},
574 std::chrono::month{
static_cast<unsigned>(d / 100 % 100)},
575 std::chrono::day{
static_cast<unsigned>(d % 100)}};
576 return from.birth->get_birth(r, ctx.random()());
579 add_(L
"biodata", {L
"country", L
"birth"}, from.biodata, [from](
const generation_context& ctx) {
580 const auto& cca2 = ctx.get<
const dasmig::country&>(L
"country").cca2;
581 const auto& b = ctx.get<
const dasmig::birth&>(L
"birth");
582 return from.biodata->get_biodata(
583 {.country = cca2, .bio_sex = b.bio_sex, .age = std::min<unsigned>(b.age, 120)},
587 add_(L
"name", {L
"country", L
"birth"}, from.name, [from](
const generation_context& ctx) {
588 const auto& cca2 = ctx.get<
const dasmig::country&>(L
"country").cca2;
589 const auto& b = ctx.get<
const dasmig::birth&>(L
"birth");
590 std::string lower = cca2;
591 std::ranges::transform(lower, lower.begin(), [](
char c) {
592 return static_cast<char>(c >=
'A' && c <=
'Z' ? c -
'A' +
'a' : c);
596 const auto name_culture = ng::to_culture(core::from_utf8(lower));
597 if (name_culture == culture::any)
598 throw std::invalid_argument(
"persona-generator: name-generator has no names for country "
600 const auto n = from.name->get_full_name(
601 {.gender = b.bio_sex == sex::female ? gender::f : gender::m, .culture = name_culture},
603 return named_{core::to_utf8(
static_cast<std::wstring
>(n)),
604 person_name{.given = core::to_utf8(n.given()),
605 .patronymic = core::to_utf8(n.patronymic()),
606 .family = core::to_utf8(n.family())}};
609 add_(L
"traits", {}, from.trait, [from](
const generation_context& ctx) {
610 return from.trait->get_personality(
611 {.count =
static_cast<std::size_t
>(ctx.input<std::int64_t>(
"traits"))},
615 add_(L
"interest", {L
"country", L
"birth"}, from.interest, [from](
const generation_context& ctx) {
616 const auto& b = ctx.get<
const dasmig::birth&>(L
"birth");
618 const auto& cca2 = ctx.get<
const dasmig::country&>(L
"country").cca2;
620 r.age =
static_cast<std::uint8_t
>(std::min<unsigned>(b.age, 120));
621 r.person_sex = b.bio_sex;
622 return from.interest->get_interest(r, ctx.random()());
625 add_(L
"occupation", {L
"country", L
"birth"}, from.occupation, [from](
const generation_context& ctx) {
626 const auto& b = ctx.get<
const dasmig::birth&>(L
"birth");
627 occupation_request r;
628 const auto& cca2 = ctx.get<
const dasmig::country&>(L
"country").cca2;
630 r.age =
static_cast<std::uint8_t
>(std::min<unsigned>(b.age, 120));
631 r.person_sex = b.bio_sex;
632 return from.occupation->get_occupation(r, ctx.random()());