Enhance marriage pregnancy management in SQL and UserCharacterWorker: Added a new query to clear stale marriage pregnancy records after 30 days. Updated existing queries to include shared children count for more accurate pregnancy probability calculations. Refactored logic in maybe_run_hourly_pregnancies to incorporate the new query and improved documentation for clarity on marriage pregnancy mechanics.

This commit is contained in:
Torsten Schulz (local)
2026-03-30 10:16:50 +02:00
parent ac059f688d
commit ac7bd1d7ed
2 changed files with 61 additions and 8 deletions

View File

@@ -1666,6 +1666,8 @@ pub const QUERY_AUTOBATISM: &str = r#"
// Worker würfelt stündlich: P_stunde = 1 - (1 - P_jahr)^(1/8760), damit 24×/Tag zusammen
// dieselbe kumulative Jahresaufteilung wie früher 1×/Tag mit P_tag = 1 - (1 - P_jahr)^(1/365).
// Grenzen in Tagen: 1 Jahr ≈ 365 Tage (mother_age_days).
// Erstes gemeinsames Kind (0 Zeilen in child_relation für dieses Paar): prob_year mindestens 1.0
// im Alter 18~44 (657016000 Tage), damit kinderlose Ehen nicht über viele Jahre ohne Nachwuchs bleiben.
// WICHTIG: Vater/Mutter und Alter immer über gender ableiten — nicht character1/2 fest als Mutter!
//
// Ablauf Ehe: (1) Konzeption setzt `marriage_pregnancy_due_at` (+5 Tage), (2) Geburt wenn fällig.
@@ -1705,6 +1707,14 @@ pub const QUERY_CLEAR_MARRIAGE_PREGNANCY_DUE: &str = r#"
WHERE id = $1::int;
"#;
/// Hängengebliebene Ehe-Schwangerschaft (Geburt fehlgeschlagen / inkonsistent): Flag nach 30 Tagen löschen.
pub const QUERY_CLEAR_STALE_MARRIAGE_PREGNANCY_DUE: &str = r#"
UPDATE falukant_data.relationship
SET marriage_pregnancy_due_at = NULL
WHERE marriage_pregnancy_due_at IS NOT NULL
AND marriage_pregnancy_due_at < NOW() - INTERVAL '30 days';
"#;
/// Stündlicher Konzeptionswurf (Ehe): bei Treffer wird `marriage_pregnancy_due_at` auf +5 Tage gesetzt.
pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
WITH paired AS (
@@ -1717,7 +1727,13 @@ pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
CASE WHEN c1.gender = 'male' THEN c1.region_id ELSE c2.region_id END AS region_id,
CASE WHEN c1.gender = 'male' THEN fu1.id ELSE fu2.id END AS father_uid,
CASE WHEN c1.gender = 'female' THEN fu1.id ELSE fu2.id END AS mother_uid,
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days,
(
SELECT COUNT(*)::int
FROM falukant_data.child_relation cr
WHERE cr.father_character_id = (CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END)
AND cr.mother_character_id = (CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END)
) AS shared_children_count
FROM falukant_data.relationship r
JOIN falukant_type.relationship r2
ON r2.id = r.relationship_type_id AND r2.tr = 'married'
@@ -1743,6 +1759,7 @@ pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
p.father_uid,
p.mother_uid,
p.mother_age_days,
p.shared_children_count,
CASE
WHEN p.mother_age_days < 4380 THEN 0.005
WHEN p.mother_age_days < 4745 THEN 0.30
@@ -1774,12 +1791,24 @@ pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
WHEN p.mother_age_days < 17520 THEN 0.015
WHEN p.mother_age_days < 18250 THEN 0.005
ELSE 0.001
END AS prob_year
END AS prob_year_raw
FROM paired p
),
mother_age_final AS (
SELECT
ma.*,
CASE
WHEN ma.shared_children_count = 0
AND ma.mother_age_days >= 6570
AND ma.mother_age_days < 16000
THEN GREATEST(ma.prob_year_raw, 1.0)
ELSE ma.prob_year_raw
END AS prob_year
FROM mother_age ma
),
conceivable AS (
SELECT ma.relationship_id
FROM mother_age ma
FROM mother_age_final ma
WHERE ma.mother_age_days >= 4380
AND ma.mother_age_days < 18993
AND random() < (1 - POWER(1 - ma.prob_year, 1.0/8760.0))
@@ -1811,7 +1840,13 @@ pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
CASE WHEN c1.gender = 'male' THEN c1.region_id ELSE c2.region_id END AS region_id,
CASE WHEN c1.gender = 'male' THEN fu1.id ELSE fu2.id END AS father_uid,
CASE WHEN c1.gender = 'female' THEN fu1.id ELSE fu2.id END AS mother_uid,
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days,
(
SELECT COUNT(*)::int
FROM falukant_data.child_relation cr
WHERE cr.father_character_id = (CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END)
AND cr.mother_character_id = (CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END)
) AS shared_children_count
FROM falukant_data.relationship r
JOIN falukant_type.relationship r2
ON r2.id = r.relationship_type_id AND r2.tr = 'married'
@@ -1835,6 +1870,7 @@ pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
p.father_uid,
p.mother_uid,
p.mother_age_days,
p.shared_children_count,
CASE
WHEN p.mother_age_days < 4380 THEN 0.005
WHEN p.mother_age_days < 4745 THEN 0.30
@@ -1866,8 +1902,20 @@ pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
WHEN p.mother_age_days < 17520 THEN 0.015
WHEN p.mother_age_days < 18250 THEN 0.005
ELSE 0.001
END AS prob_year
END AS prob_year_raw
FROM paired p
),
mother_age_final AS (
SELECT
ma.*,
CASE
WHEN ma.shared_children_count = 0
AND ma.mother_age_days >= 6570
AND ma.mother_age_days < 16000
THEN GREATEST(ma.prob_year_raw, 1.0)
ELSE ma.prob_year_raw
END AS prob_year
FROM mother_age ma
)
SELECT
father_cid,
@@ -1879,7 +1927,7 @@ pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
mother_uid,
mother_age_days,
(1 - POWER(1 - prob_year, 1.0/8760.0)) * 100 AS prob_pct
FROM mother_age
FROM mother_age_final
WHERE mother_age_days >= 4380
AND mother_age_days < 18993
AND random() < (1 - POWER(1 - prob_year, 1.0/8760.0));

View File

@@ -33,7 +33,8 @@ use crate::worker::sql::{
QUERY_UPDATE_USER_MONEY,
QUERY_GET_FALUKANT_USER_ID,
QUERY_AUTOBATISM,
QUERY_CLEAR_MARRIAGE_PREGNANCY_DUE, QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES,
QUERY_CLEAR_MARRIAGE_PREGNANCY_DUE, QUERY_CLEAR_STALE_MARRIAGE_PREGNANCY_DUE,
QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES,
QUERY_GET_MARRIAGE_BIRTH_DELIVERIES, QUERY_MARRIAGE_PREGNANCY_COLUMN_READY,
QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE,
QUERY_INSERT_CHILD,
@@ -181,6 +182,7 @@ impl UserCharacterWorker {
/// Ehe-Schwangerschaft: höchstens einmal pro Stunde (Daemon-Schleife ~1 s).
/// Konzeption setzt `marriage_pregnancy_due_at` (+5 Tage); Geburt über `QUERY_GET_MARRIAGE_BIRTH_DELIVERIES`.
/// Stündliche Zerlegung der Jahres-Wahrscheinlichkeit (`1/8760`), siehe `QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE`.
/// Erstes gemeinsames Kind: Jahreswahrscheinlichkeit mindestens 1.0 (18~44 J.), siehe `sql.rs`.
fn maybe_run_hourly_pregnancies(&mut self) {
let now = Instant::now();
let should_run = match self.last_pregnancy_run {
@@ -569,6 +571,9 @@ impl UserCharacterWorker {
self.process_single_marriage_delivery(&mut conn, &row)?;
}
conn.prepare("clear_stale_preg", QUERY_CLEAR_STALE_MARRIAGE_PREGNANCY_DUE)?;
conn.execute("clear_stale_preg", &[])?;
conn.prepare("try_conception", QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE)?;
conn.execute("try_conception", &[])?;
} else {
@@ -590,7 +595,7 @@ impl UserCharacterWorker {
let relationship_id = parse_i32(row, "relationship_id", -1);
let father_cid = parse_i32(row, "father_cid", -1);
let mother_cid = parse_i32(row, "mother_cid", -1);
if relationship_id < 0 || father_cid < 0 || mother_cid < 0 {
if father_cid < 0 || mother_cid < 0 {
return Ok(());
}