Implement political office history logging: Added SQL logic to archive political office records upon expiration and deletion, ensuring historical tracking of office terms. Updated relevant queries to insert records into falukant_log.political_office_history for better compliance with data retention policies.
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 1m33s

This commit is contained in:
Torsten Schulz (local)
2026-04-17 17:30:40 +02:00
parent 75a1e5b306
commit 0892e2db8b
3 changed files with 105 additions and 24 deletions

View File

@@ -0,0 +1,28 @@
-- Abgeschlossene politische Ämter (Amtsende, Neubesetzung, Entfernung) für Auswertung „Karrierehöchstwert“ / UI.
-- Wird vom YpDaemon vor Löschen aus falukant_data.political_office befüllt.
CREATE TABLE IF NOT EXISTS falukant_log.political_office_history (
id BIGSERIAL PRIMARY KEY,
character_id INTEGER NOT NULL,
office_type_id INTEGER NOT NULL,
region_id INTEGER,
start_date TIMESTAMPTZ NOT NULL,
end_date TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Tabelle kann bereits vom Backend existieren (ohne region_id o. Ä.): CREATE TABLE IF NOT EXISTS ergänzt keine Spalten.
ALTER TABLE falukant_log.political_office_history
ADD COLUMN IF NOT EXISTS region_id INTEGER;
CREATE INDEX IF NOT EXISTS idx_pol_office_hist_character
ON falukant_log.political_office_history (character_id);
CREATE INDEX IF NOT EXISTS idx_pol_office_hist_office_type
ON falukant_log.political_office_history (office_type_id);
CREATE INDEX IF NOT EXISTS idx_pol_office_hist_region
ON falukant_log.political_office_history (region_id);
COMMENT ON TABLE falukant_log.political_office_history IS
'Politische Amtszeiten nach Ende; start_date/end_date aus Amtszeile bzw. NOW() bei vorzeitigem Ende (YpDaemon).';

View File

@@ -41,3 +41,7 @@ Spalte **`falukant_data.falukant_user.certificate_productions_count_since`**: Ze
## `015_falukant_log_production_completion_count.sql`
Spalte **`falukant_log.production.completion_count`**: zählt **abgeschlossene Produktionen** pro aggregierter Log-Zeile (bei gleichem Tag/Produkt/Region wird die Menge per UPSERT summiert; ohne `completion_count` bliebe `COUNT(*)` über die Zeilen fälschlich niedrig). Zertifikatsabfrage nutzt **`SUM(completion_count)`** (Migration **`015`** vor Deploy des aktualisierten Produce-Workers ausführen).
## `016_falukant_log_political_office_history.sql`
Tabelle **`falukant_log.political_office_history`**: Archiv abgeschlossener politischer Amtszeiten (`character_id`, `office_type_id`, `region_id`, `start_date`, `end_date`). Der Daemon schreibt **vor** jedem relevanten `DELETE` auf **`falukant_data.political_office`** (Amtsende/Neuwahl-Pfad, Übersitz-Trim, Charaktertod). **`falukant_data.process_elections()`** (PostgreSQL) liegt außerhalb des Rust-Repos — falls dort Zeilen gelöscht werden, analog **`INSERT` in diese Historie** in der DB-Funktion ergänzen.

View File

@@ -1040,14 +1040,27 @@ pub const QUERY_SELECT_NEEDED_ELECTIONS: &str = r#"
target_date AS (
SELECT NOW()::date AS election_date
),
offices_ending_today AS (
SELECT po.id,
po.character_id,
po.office_type_id,
po.region_id,
po.created_at AS start_date,
(po.created_at + (pot.term_length * INTERVAL '1 day')) AS end_date
FROM falukant_data.political_office po
JOIN falukant_type.political_office_type pot ON po.office_type_id = pot.id
CROSS JOIN target_date td
WHERE (po.created_at + (pot.term_length * INTERVAL '1 day'))::date = td.election_date
),
archived_expiring AS (
INSERT INTO falukant_log.political_office_history (character_id, office_type_id, region_id, start_date, end_date)
SELECT character_id, office_type_id, region_id, start_date, end_date
FROM offices_ending_today
),
expired_today AS (
DELETE FROM falukant_data.political_office AS po
USING falukant_type.political_office_type AS pot
WHERE po.office_type_id = pot.id
AND (po.created_at + (pot.term_length * INTERVAL '1 day'))::date
= (SELECT election_date FROM target_date)
RETURNING
pot.id AS office_type_id,
DELETE FROM falukant_data.political_office po
WHERE po.id IN (SELECT id FROM offices_ending_today)
RETURNING po.office_type_id AS office_type_id,
po.region_id AS region_id
),
gaps_per_region AS (
@@ -1151,13 +1164,26 @@ pub const QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES: &str = r#"
pub const QUERY_PROCESS_EXPIRED_AND_FILL: &str = r#"
WITH
doomed AS (
SELECT po.id,
po.character_id,
po.office_type_id,
po.region_id,
po.created_at AS start_date,
(po.created_at + (pot.term_length * INTERVAL '1 day')) AS term_end
FROM falukant_data.political_office po
JOIN falukant_type.political_office_type pot ON po.office_type_id = pot.id
WHERE (po.created_at + (pot.term_length * INTERVAL '1 day')) <= NOW()
),
archived_expired AS (
INSERT INTO falukant_log.political_office_history (character_id, office_type_id, region_id, start_date, end_date)
SELECT character_id, office_type_id, region_id, start_date, term_end
FROM doomed
),
expired_offices AS (
DELETE FROM falukant_data.political_office AS po
USING falukant_type.political_office_type AS pot
WHERE po.office_type_id = pot.id
AND (po.created_at + (pot.term_length * INTERVAL '1 day')) <= NOW()
RETURNING
pot.id AS office_type_id,
DELETE FROM falukant_data.political_office po
WHERE po.id IN (SELECT id FROM doomed)
RETURNING po.office_type_id AS office_type_id,
po.region_id AS region_id
),
distinct_types AS (
@@ -1617,8 +1643,10 @@ pub const QUERY_TRIM_EXCESS_OFFICES_GLOBAL: &str = r#"
ranked AS (
SELECT
po.id,
po.character_id,
po.office_type_id,
po.region_id,
po.created_at,
s.seats_total,
ROW_NUMBER() OVER (
PARTITION BY po.office_type_id, po.region_id
@@ -1630,9 +1658,14 @@ pub const QUERY_TRIM_EXCESS_OFFICES_GLOBAL: &str = r#"
AND s.region_id = po.region_id
),
to_delete AS (
SELECT id
SELECT id, character_id, office_type_id, region_id, created_at
FROM ranked
WHERE rn > seats_total
),
archived_trim AS (
INSERT INTO falukant_log.political_office_history (character_id, office_type_id, region_id, start_date, end_date)
SELECT character_id, office_type_id, region_id, created_at, NOW()
FROM to_delete
)
DELETE FROM falukant_data.political_office
WHERE id IN (SELECT id FROM to_delete);
@@ -2485,7 +2518,12 @@ pub const QUERY_DELETE_POLITICAL_OFFICE: &str = r#"
WITH removed AS (
DELETE FROM falukant_data.political_office
WHERE character_id = $1
RETURNING office_type_id, region_id
RETURNING character_id, office_type_id, region_id, created_at
),
archived_removed AS (
INSERT INTO falukant_log.political_office_history (character_id, office_type_id, region_id, start_date, end_date)
SELECT character_id, office_type_id, region_id, created_at, NOW()
FROM removed
),
affected AS (
SELECT DISTINCT office_type_id, region_id
@@ -2506,8 +2544,10 @@ pub const QUERY_DELETE_POLITICAL_OFFICE: &str = r#"
ranked AS (
SELECT
po.id,
po.character_id,
po.office_type_id,
po.region_id,
po.created_at,
s.seats_total,
ROW_NUMBER() OVER (
PARTITION BY po.office_type_id, po.region_id
@@ -2519,9 +2559,14 @@ pub const QUERY_DELETE_POLITICAL_OFFICE: &str = r#"
AND s.region_id = po.region_id
),
to_delete AS (
SELECT id
SELECT id, character_id, office_type_id, region_id, created_at
FROM ranked
WHERE rn > seats_total
),
archived_trim AS (
INSERT INTO falukant_log.political_office_history (character_id, office_type_id, region_id, start_date, end_date)
SELECT character_id, office_type_id, region_id, created_at, NOW()
FROM to_delete
)
DELETE FROM falukant_data.political_office
WHERE id IN (SELECT id FROM to_delete);
@@ -3903,6 +3948,7 @@ pub const QUERY_HAS_MOTHER_BIRTH_TODAY: &str = r#"
/// Ein Spielercharakter pro Falukant-User (bei mehreren lebenden: **höchste** `character.id`,
/// typischerweise zuletzt aktiver Slot — konsistent mit UI, das oft den Hauptcharakter nutzt).
/// `completed_production_count`: Summe `completion_count` in `falukant_log.production` seit `certificate_productions_count_since` (Migration `014`; Spalte `completion_count` Migration `015`); **NULL** = alle passenden Log-Zeilen (Bestand vor erstem Aufstieg nach Migration).
/// `max_church_hierarchy`: `GREATEST(character.highest_church_hierarchy_ever, MAX aktuelles kirchliches Amt)` (Migration `007` / Kirchen-Daemon).
pub const QUERY_GET_PRODUCTION_CERTIFICATE_INPUT_ROWS: &str = r#"
SELECT DISTINCT ON (fu.id)
fu.id AS falukant_user_id,
@@ -3929,12 +3975,15 @@ pub const QUERY_GET_PRODUCTION_CERTIFICATE_INPUT_ROWS: &str = r#"
) >= fu.certificate_productions_count_since
)
), 0) AS completed_production_count,
GREATEST(
COALESCE(c.highest_church_hierarchy_ever, 0)::int,
COALESCE((
SELECT MAX(cot.hierarchy_level)::int
FROM falukant_data.church_office co
JOIN falukant_type.church_office_type cot ON cot.id = co.office_type_id
WHERE co.character_id = c.id
), 0) AS max_church_hierarchy,
), 0)
) AS max_church_hierarchy,
COALESCE((
SELECT STRING_AGG(DISTINCT pot.name, '|')
FROM falukant_data.political_office po