feat(politics): update election scheduling logic to include all unoccupied positions and improve query efficiency

This commit is contained in:
Torsten Schulz (local)
2026-08-24 09:02:55 +02:00
parent 846ba5b3c4
commit d0f4e33fc0

View File

@@ -80,72 +80,98 @@ private:
)";
// ------------------------------------------------------------
// STEP 1: Erzeuge nur diejenigen Wahlen, bei denen noch keine Election
// für denselben Termin (NOW()+2 Tage) existiert.
// STEP 1: Erzeuge Wahlen für *alle* unbesetzten Soll-Stellen.
// Dabei zählen auch bereits angelegte Wahlen als Reservierung,
// damit der tägliche Lauf keine doppelten Ausschreibungen erzeugt.
// ------------------------------------------------------------
static constexpr const char* QUERY_SELECT_NEEDED_ELECTIONS = R"(
WITH
-- 1) Definiere das heutige Datum einmal als Referenz
-- 1) Definiere den tatsächlichen Wahltermin einmal als Referenz.
-- Die Bewerbungsphase dauert drei Tage (vgl. process_elections()).
target_date AS (
SELECT NOW()::date AS election_date
SELECT NOW() + INTERVAL '3 days' AS election_date
),
-- 2) Lösche nur diejenigen Ämter, deren Ablaufdatum heute erreicht ist,
-- und merke deren (office_type_id, region_id)
expired_today AS (
-- 2) Abgelaufene Ämter entfernen. Ein <= statt = heilt auch Lücken,
-- falls der Worker an einem Tag nicht gelaufen ist.
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'))::date = (SELECT election_date FROM target_date)
AND po.created_at + (pot.term_length * INTERVAL '1 day') <= NOW()
RETURNING
pot.id AS office_type_id,
po.region_id AS region_id
),
-- 3) Gruppiere nach Typ+Region und zähle, wie viele Sitze heute frei geworden sind
gaps_per_region AS (
-- 3) Alle Soll-Stellen pro politischem Amt und echter Spielregion.
-- Bisher wurden nur gerade abgelaufene Ämter betrachtet. Deshalb
-- erhielten nie besetzte (z. B. neu hinzugefügte) Amtstypen nie eine
-- Ausschreibung.
required_seats AS (
SELECT
office_type_id,
region_id,
COUNT(*) AS gaps
FROM expired_today
pot.id AS office_type_id,
r.id AS region_id,
pot.seats_per_region AS seats_total
FROM falukant_type.political_office_type AS pot
JOIN falukant_data.region AS r
ON TRUE
JOIN falukant_type.region AS rt
ON rt.id = r.region_type_id
AND rt.label_tr = pot.region_type
),
-- 4) Aktuell besetzte und durch laufende Wahlen bereits reservierte
-- Sitze zählen.
occupied_seats AS (
SELECT po.office_type_id, po.region_id, COUNT(*) AS seats_occupied
FROM falukant_data.political_office AS po
JOIN falukant_type.political_office_type AS pot
ON pot.id = po.office_type_id
-- Datenändernde CTEs teilen sich denselben Statement-Snapshot.
-- Deshalb abgelaufene Zeilen hier ausdrücklich nicht mitzählen.
WHERE po.created_at + (pot.term_length * INTERVAL '1 day') > NOW()
GROUP BY po.office_type_id, po.region_id
),
reserved_seats AS (
SELECT office_type_id, region_id, SUM(posts_to_fill) AS seats_reserved
FROM falukant_data.election
GROUP BY office_type_id, region_id
),
-- 4) Filtere nur diejenigen Typ+RegionKombinationen, für die noch **keine** Election
-- mit genau demselben Datum angelegt wurde
to_schedule AS (
gaps_per_region AS (
SELECT
g.office_type_id,
g.region_id,
g.gaps,
rs.office_type_id,
rs.region_id,
rs.seats_total
- COALESCE(os.seats_occupied, 0)
- COALESCE(es.seats_reserved, 0) AS gaps,
td.election_date
FROM
gaps_per_region AS g
CROSS JOIN
target_date AS td
WHERE NOT EXISTS (
SELECT 1
FROM falukant_data.election AS e
WHERE e.office_type_id = g.office_type_id
AND e.region_id = g.region_id
AND e."date"::date = td.election_date
)
FROM required_seats AS rs
LEFT JOIN occupied_seats AS os
ON os.office_type_id = rs.office_type_id
AND os.region_id = rs.region_id
LEFT JOIN reserved_seats AS es
ON es.office_type_id = rs.office_type_id
AND es.region_id = rs.region_id
CROSS JOIN target_date AS td
WHERE rs.seats_total
- COALESCE(os.seats_occupied, 0)
- COALESCE(es.seats_reserved, 0) > 0
),
-- 5) Lege für jede so gefilterte Kombination genau eine Election an
-- 5) Lege für jede vakante Kombination genau eine Election an.
new_elections AS (
INSERT INTO falukant_data.election
(office_type_id, "date", posts_to_fill, created_at, updated_at, region_id)
SELECT
ts.office_type_id,
ts.election_date AS "date",
ts.election_date AS "date",
ts.gaps AS posts_to_fill,
NOW() AS created_at,
NOW() AS updated_at,
ts.region_id
FROM
to_schedule AS ts
gaps_per_region AS ts
RETURNING
id AS election_id,
region_id,