Files
paliad/internal/db/migrations/051_proceeding_display_order.up.sql
m 63eb5bde6f feat(t-paliad-134): pill ordering + name standardisation + chip dedup
Five m's-bookmark fixes on top of the B1 surface change:

1. Sort proceeding pills inside concept cards by real-world frequency.
   New paliad.proceeding_types.display_order column (m's spec values:
   UPC_INF=10, DE_INF=20, UPC_REV=30, ..., UPC_PI=920, ...). Default
   999 for unmapped legacy codes. Search service surfaces it through
   the deadline_search matview (rebuilt to add the column) and uses
   it as primary key in pillSortKey, replacing the jurisdiction-rank.

2. Name standardisation: -klage → -verfahren on the proceeding-types
   that describe a multi-step process. Specifically:
     UPC_REV  Nichtigkeitsklage              → Nichtigkeitsverfahren
     UPC_APP  Berufung                       → Berufungsverfahren
     DE_INF   Verletzungsklage (LG)          → Verletzungsverfahren (LG)
     DE_INF_OLG, DE_NULL_BGH, DPMA_OPP, DPMA_BPATG_BESCHWERDE,
     UPC_COST_APPEAL, UPC_APP_ORDERS, DPMA_BGH_RB, DE_INF_BGH —
     same -verfahren standardisation.

3. legal_source for rev.defence × UPC_REV: was NULL, leaking the
   internal local_code 'rev.defence' to the UI. Set to UPC.RoP.49.1
   (Defence to Application for Revocation, R.49.1).

4. Frontend renderPill no longer falls back to rule_local_code when
   legal_source is missing — the source span just collapses, so no
   internal slug ever shows up as a "citation".

5. Quick-pick chips refactored to a slug-based array (QUICK_CHIPS) in
   fristenrechner.tsx, single source of truth for both fork-shortcut
   and B2-search-bar rows. Each chip carries data-chip-name-de /
   data-chip-name-en; relabelChips() rewrites visible text per active
   language. Dropped the duplicate "Statement of Defence" chip (same
   concept as "Klageerwiderung"). Each chip now maps to one concept
   slug — Klageerwiderung→statement-of-defence, Berufung→notice-of-
   appeal, Einspruch→opposition, Replik→reply-to-defence,
   Beschwerde→nichtzulassungsbeschwerde, Schadensbemessung→
   application-for-determination-of-damages, Wiedereinsetzung→
   wiedereinsetzung.

Migration 051 uses RAISE WARNING (not EXCEPTION) on coverage gates
per the 049 outage lesson — partial-migration recovery beats whole-
transaction failure. Matview rebuild stays inside the transaction;
RefreshSearchView() on next boot is a cheap no-op.
2026-05-05 11:53:13 +02:00

224 lines
10 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- t-paliad-134 add-on (m 2026-05-05): pill ordering by real-world
-- frequency + name standardisation + legal-source fix for rev.defence.
--
-- Three changes on paliad.proceeding_types and paliad.deadline_rules:
--
-- 1. New column display_order: secondary sort key for proceeding-pill
-- ordering inside concept cards. sort_order is per-jurisdiction
-- grouping (used in the wizard); display_order reflects pill
-- likelihood (most common first). m's spec values, lower = more
-- common; gap-numbered so future additions slot in.
--
-- 2. name standardisation — m wants -verfahren (process) consistently.
-- 'Nichtigkeitsklage' → 'Nichtigkeitsverfahren' is the explicit
-- example; Berufung/Beschwerde/Einspruch get the same treatment
-- where the row describes the multi-step process rather than a
-- single submission.
--
-- 3. legal_source for the rev.defence rule (UPC_REV statement-of-
-- defence): was NULL, which leaked the internal local_code 'rev.
-- defence' to the UI as the citation. Should be 'UPC.RoP.49.1'.
-- rule_code mirrors that as 'RoP.49.1' for trigram search hits.
--
-- Validation gates use RAISE WARNING (not EXCEPTION) per the 049 outage
-- lesson — partial-migration recovery beats whole-transaction failure.
BEGIN;
-- ---------------------------------------------------------------------
-- 1. display_order column + backfill
-- ---------------------------------------------------------------------
ALTER TABLE paliad.proceeding_types
ADD COLUMN IF NOT EXISTS display_order int NOT NULL DEFAULT 999;
COMMENT ON COLUMN paliad.proceeding_types.display_order IS
'Pill-ordering rank by real-world frequency (lower = shown first). '
'Independent of sort_order, which groups by jurisdiction for the '
'Verfahrensablauf wizard. Default 999 = appears at the end.';
UPDATE paliad.proceeding_types SET display_order = CASE code
-- Common, listed first
WHEN 'UPC_INF' THEN 10
WHEN 'DE_INF' THEN 20
WHEN 'UPC_REV' THEN 30
WHEN 'DE_NULL' THEN 40
WHEN 'EPA_OPP' THEN 50
WHEN 'EPA_APP' THEN 60
WHEN 'DPMA_OPP' THEN 70
WHEN 'UPC_APP' THEN 80
WHEN 'DE_INF_OLG' THEN 90
WHEN 'DE_INF_BGH' THEN 100
WHEN 'DE_NULL_BGH' THEN 110
-- Less common, pushed back
WHEN 'EP_GRANT' THEN 200
WHEN 'DPMA_BPATG_BESCHWERDE' THEN 210
WHEN 'DPMA_BGH_RB' THEN 220
WHEN 'UPC_APP_ORDERS' THEN 230
-- Rare, last block
WHEN 'UPC_DAMAGES' THEN 900
WHEN 'UPC_DISCOVERY' THEN 910
WHEN 'UPC_PI' THEN 920
WHEN 'UPC_COST_APPEAL' THEN 930
-- Legacy v0 codes (INF/REV/CCR/APM/APP/AMD/ZPO_CIVIL): keep at 999.
ELSE display_order
END;
-- Soft check: warn (don't fail) if any active proceeding still has the
-- default. m wants visibility, not transactional gate-keeping.
DO $$
DECLARE
n_default int;
BEGIN
SELECT count(*)
INTO n_default
FROM paliad.proceeding_types
WHERE is_active = true
AND display_order = 999
AND code IN ('UPC_INF','UPC_REV','UPC_PI','UPC_APP','UPC_DAMAGES',
'UPC_DISCOVERY','UPC_COST_APPEAL','UPC_APP_ORDERS',
'DE_INF','DE_NULL','DE_INF_OLG','DE_INF_BGH','DE_NULL_BGH',
'EPA_OPP','EPA_APP','EP_GRANT',
'DPMA_OPP','DPMA_BPATG_BESCHWERDE','DPMA_BGH_RB');
IF n_default > 0 THEN
RAISE WARNING 't-paliad-134: % active proceeding_types in the v3 set still have default display_order=999. Frequency-sort will push them to the end.', n_default;
END IF;
END$$;
-- ---------------------------------------------------------------------
-- 2. -klage / -verfahren standardisation in name (DE)
-- ---------------------------------------------------------------------
-- Each row keyed by code so renames are idempotent across re-runs.
UPDATE paliad.proceeding_types SET name = 'Nichtigkeitsverfahren' WHERE code = 'UPC_REV';
UPDATE paliad.proceeding_types SET name = 'Berufungsverfahren' WHERE code = 'UPC_APP';
UPDATE paliad.proceeding_types SET name = 'Verletzungsverfahren (LG)' WHERE code = 'DE_INF';
UPDATE paliad.proceeding_types SET name = 'Berufungsverfahren OLG (Verletzung)' WHERE code = 'DE_INF_OLG';
UPDATE paliad.proceeding_types SET name = 'Berufungsverfahren BGH (Nichtigkeit)' WHERE code = 'DE_NULL_BGH';
UPDATE paliad.proceeding_types SET name = 'Beschwerdeverfahren BPatG (DPMA)' WHERE code = 'DPMA_BPATG_BESCHWERDE';
UPDATE paliad.proceeding_types SET name = 'Einspruchsverfahren DPMA' WHERE code = 'DPMA_OPP';
UPDATE paliad.proceeding_types SET name = 'Berufungsverfahren Kosten' WHERE code = 'UPC_COST_APPEAL';
UPDATE paliad.proceeding_types SET name = 'Berufungsverfahren Anordnungen' WHERE code = 'UPC_APP_ORDERS';
UPDATE paliad.proceeding_types SET name = 'Rechtsbeschwerdeverfahren BGH' WHERE code = 'DPMA_BGH_RB';
UPDATE paliad.proceeding_types SET name = 'Revisions-/NZB-Verfahren BGH (Verletzung)' WHERE code = 'DE_INF_BGH';
-- ---------------------------------------------------------------------
-- 3. legal_source for the rev.defence rule (UPC_REV)
-- ---------------------------------------------------------------------
UPDATE paliad.deadline_rules dr
SET legal_source = 'UPC.RoP.49.1',
rule_code = 'RoP.49.1'
FROM paliad.proceeding_types p
WHERE dr.proceeding_type_id = p.id
AND p.code = 'UPC_REV'
AND dr.code = 'rev.defence'
AND dr.legal_source IS NULL;
DO $$
DECLARE
n_unfixed int;
BEGIN
SELECT count(*)
INTO n_unfixed
FROM paliad.deadline_rules dr
JOIN paliad.proceeding_types p ON p.id = dr.proceeding_type_id
WHERE p.code = 'UPC_REV' AND dr.code = 'rev.defence' AND dr.legal_source IS NULL;
IF n_unfixed > 0 THEN
RAISE WARNING 't-paliad-134: rev.defence × UPC_REV still has NULL legal_source after backfill (% rows).', n_unfixed;
END IF;
END$$;
-- ---------------------------------------------------------------------
-- 4. Matview rebuild — surface proceeding_display_order so the search
-- service can ORDER BY it directly. Drop + recreate keeps the
-- column list explicit; existing query patterns and all indexes
-- are reproduced verbatim with the one new column added.
-- ---------------------------------------------------------------------
DROP MATERIALIZED VIEW IF EXISTS paliad.deadline_search;
CREATE MATERIALIZED VIEW paliad.deadline_search AS
SELECT
'rule'::text AS kind,
'r:' || dr.id::text AS row_key,
dc.id AS concept_id,
dc.slug AS concept_slug,
dc.name_de AS concept_name_de,
dc.name_en AS concept_name_en,
dc.description AS concept_description,
dc.aliases AS concept_aliases,
dc.party AS concept_party,
dc.category AS concept_category,
dc.sort_order AS concept_sort_order,
dr.id AS rule_id,
NULL::bigint AS trigger_event_id,
pt.code AS proceeding_code,
pt.name AS proceeding_name_de,
pt.name_en AS proceeding_name_en,
pt.jurisdiction AS jurisdiction,
pt.display_order AS proceeding_display_order,
dr.code AS rule_local_code,
dr.name AS rule_name_de,
dr.name_en AS rule_name_en,
dr.legal_source AS legal_source,
dr.rule_code AS rule_code,
dr.duration_value,
dr.duration_unit,
dr.timing,
COALESCE(dr.primary_party, dc.party) AS effective_party
FROM paliad.deadline_rules dr
JOIN paliad.proceeding_types pt ON pt.id = dr.proceeding_type_id
JOIN paliad.deadline_concepts dc ON dc.id = dr.concept_id
WHERE dr.is_active
AND pt.is_active
AND pt.category = 'fristenrechner'
UNION ALL
SELECT
'trigger'::text,
't:' || te.id::text,
dc.id,
dc.slug,
dc.name_de,
dc.name_en,
dc.description,
dc.aliases,
dc.party,
dc.category,
dc.sort_order,
NULL::uuid,
te.id,
NULL::text,
NULL::text,
NULL::text,
'cross-cutting'::text,
9999::int AS proceeding_display_order,
te.code,
te.name_de,
te.name,
NULL::text,
NULL::text,
NULL::int,
NULL::text,
NULL::text,
dc.party
FROM paliad.trigger_events te
JOIN paliad.deadline_concepts dc ON dc.slug = te.concept_id
WHERE te.is_active;
CREATE UNIQUE INDEX deadline_search_row_key ON paliad.deadline_search (row_key);
CREATE INDEX deadline_search_concept_id ON paliad.deadline_search (concept_id);
CREATE INDEX deadline_search_proc_code ON paliad.deadline_search (proceeding_code);
CREATE INDEX deadline_search_legal_source ON paliad.deadline_search (legal_source);
CREATE INDEX deadline_search_effective_party ON paliad.deadline_search (effective_party);
CREATE INDEX deadline_search_legal_source_trgm ON paliad.deadline_search USING gin (legal_source gin_trgm_ops);
CREATE INDEX deadline_search_concept_de_trgm ON paliad.deadline_search USING gin (concept_name_de gin_trgm_ops);
CREATE INDEX deadline_search_concept_en_trgm ON paliad.deadline_search USING gin (concept_name_en gin_trgm_ops);
CREATE INDEX deadline_search_rule_de_trgm ON paliad.deadline_search USING gin (rule_name_de gin_trgm_ops);
CREATE INDEX deadline_search_rule_en_trgm ON paliad.deadline_search USING gin (rule_name_en gin_trgm_ops);
CREATE INDEX deadline_search_rule_code_trgm ON paliad.deadline_search USING gin (rule_code gin_trgm_ops);
-- The fresh matview is populated by the implicit initial scan above. The
-- next RefreshSearchView() (called from cmd/server/main.go after this
-- migration runner) is a no-op refresh and stays cheap.
COMMIT;