diff --git a/internal/db/migrations/163_caption_wording_followup.down.sql b/internal/db/migrations/163_caption_wording_followup.down.sql new file mode 100644 index 0000000..2951072 --- /dev/null +++ b/internal/db/migrations/163_caption_wording_followup.down.sql @@ -0,0 +1,40 @@ +-- Revert 163_caption_wording_followup (t-paliad-361). Restores the A-S2 +-- (post-mig-161 / mig-137) state for all three changes. + +-- ---------------------------------------------------------------- +-- Change 1 down — UPC appeal EN responding party back to 'Appellee'. +-- ---------------------------------------------------------------- + +UPDATE paliad.proceeding_types + SET role_reactive_label_en = 'Appellee' + WHERE code = 'upc.apl.unified' + AND role_reactive_label_en = 'Respondent'; + +-- ---------------------------------------------------------------- +-- Change 2 down — drop the Streitpatent line from the upc-formal caption seed, +-- restoring the verbatim post-mig-161 parametric seed. +-- ---------------------------------------------------------------- + +UPDATE paliad.submission_bases AS b +SET section_spec = jsonb_set(b.section_spec, '{defaults}', ( + SELECT jsonb_agg( + CASE WHEN elem->>'section_key' = 'caption' + THEN elem || jsonb_build_object( + 'seed_md_de', E'{{caption.heading_de}}\n\n**{{parties.claimant.0.name}}**\nvertreten durch {{parties.claimant.0.representative}}\n\n— {{caption.claimant_designation_de}} —\n\n{{caption.versus_de}}\n\n**{{parties.defendant.0.name}}**\nvertreten durch {{parties.defendant.0.representative}}\n\n— {{caption.defendant_designation_de}} —\n\nwegen {{caption.subject_de}}\n\nAktenzeichen: {{project.case_number}}\n{{project.court}}', + 'seed_md_en', E'{{caption.heading_en}}\n\n**{{parties.claimant.0.name}}**\nrepresented by {{parties.claimant.0.representative}}\n\n— {{caption.claimant_designation_en}} —\n\n{{caption.versus_en}}\n\n**{{parties.defendant.0.name}}**\nrepresented by {{parties.defendant.0.representative}}\n\n— {{caption.defendant_designation_en}} —\n\nre {{caption.subject_en}}\n\nCase number: {{project.case_number}}\n{{project.court}}') + ELSE elem END + ORDER BY ord) + FROM jsonb_array_elements(b.section_spec->'defaults') WITH ORDINALITY AS d(elem, ord))) +WHERE b.slug = 'upc-formal' AND b.section_spec ? 'defaults'; + +-- ---------------------------------------------------------------- +-- Change 3 down — clear the backfilled role labels (back to NULL, the +-- pre-163 state for these four proceedings). +-- ---------------------------------------------------------------- + +UPDATE paliad.proceeding_types + SET role_proactive_label_de = NULL, + role_reactive_label_de = NULL, + role_proactive_label_en = NULL, + role_reactive_label_en = NULL + WHERE code IN ('de.inf.olg', 'de.inf.bgh', 'de.null.bpatg', 'de.null.bgh'); diff --git a/internal/db/migrations/163_caption_wording_followup.up.sql b/internal/db/migrations/163_caption_wording_followup.up.sql new file mode 100644 index 0000000..612c0e4 --- /dev/null +++ b/internal/db/migrations/163_caption_wording_followup.up.sql @@ -0,0 +1,108 @@ +-- 163_caption_wording_followup — t-paliad-361, follow-up to t-paliad-358 A-S2. +-- +-- m ruled on the 7 lexy-wording flags from A-S2 via AskUserQuestion +-- (2026-06-01 14:30). Most flags CONFIRMED the live wording; three changes +-- land here. All three are caption (Rubrum) wording and share this one +-- reversible migration. +-- +-- Change 1 — UPC appeal responding party (EN): 'Appellee' → 'Respondent'. +-- m chose Respondent over Appellee. The only place 'Appellee' is stored is +-- the mig-137 role-label override on upc.apl.unified (id=160, retired by +-- mig 155 but kept as the canonical UPC-appeal role-label row). The caption +-- resolver's instance-derived EN fallback already says 'Respondent' +-- (submission_vars.go), so this fixes the wording at the data source rather +-- than downstream. DE side (Berufungsbeklagter) is left untouched per m. +-- +-- Change 2 — restore the standalone 'Streitpatent' / 'Patent in suit' line in +-- the upc-formal Composer caption seed. A-S2 (mig 161) dropped it when it +-- unified the caption onto the {{caption.*}} keys. m wants the patent-in-suit +-- line back, but KEEPS the parametric 'In der Sache' heading (he did not +-- revert that). Only the upc-formal base's caption seed is touched. +-- +-- Change 3 — backfill role-label overrides for the four DE appeal/nullity +-- proceedings that carry none (de.inf.olg, de.inf.bgh, de.null.bpatg, +-- de.null.bgh). Without an override these fall to the instance-derived path, +-- which is only correct when project.instance_level is set. The backfill +-- makes the designations right regardless of instance_level. Wording is +-- lexy-confirmed (statute-grounded: §§ 511, 542, 544 ZPO; §§ 81, 110 PatG), +-- bracketed-inclusive gender style to match the A-S2-confirmed convention. +-- +-- ADDITIVE / data-only. No schema changes. Reversible (see .down.sql). + +-- ---------------------------------------------------------------- +-- Change 1 — UPC appeal EN responding party: 'Appellee' → 'Respondent'. +-- ---------------------------------------------------------------- + +UPDATE paliad.proceeding_types + SET role_reactive_label_en = 'Respondent' + WHERE code = 'upc.apl.unified' + AND role_reactive_label_en = 'Appellee'; + +-- ---------------------------------------------------------------- +-- Change 2 — restore the Streitpatent line in the upc-formal caption seed. +-- Position-independent: rewrites only the section_key='caption' element of +-- section_spec->'defaults', preserving order (WITH ORDINALITY) and every +-- other field on the element (elem || patch). Keeps the parametric heading; +-- re-adds 'Streitpatent: {{project.patent_number_upc}}' (DE) / +-- 'Patent in suit: {{...}}' (EN) grouped with the case number, ahead of the +-- {{project.court}} line. +-- ---------------------------------------------------------------- + +UPDATE paliad.submission_bases AS b +SET section_spec = jsonb_set( + b.section_spec, + '{defaults}', + ( + SELECT jsonb_agg( + CASE WHEN elem->>'section_key' = 'caption' + THEN elem || jsonb_build_object( + 'seed_md_de', E'{{caption.heading_de}}\n\n**{{parties.claimant.0.name}}**\nvertreten durch {{parties.claimant.0.representative}}\n\n— {{caption.claimant_designation_de}} —\n\n{{caption.versus_de}}\n\n**{{parties.defendant.0.name}}**\nvertreten durch {{parties.defendant.0.representative}}\n\n— {{caption.defendant_designation_de}} —\n\nwegen {{caption.subject_de}}\n\nAktenzeichen: {{project.case_number}}\nStreitpatent: {{project.patent_number_upc}}\n{{project.court}}', + 'seed_md_en', E'{{caption.heading_en}}\n\n**{{parties.claimant.0.name}}**\nrepresented by {{parties.claimant.0.representative}}\n\n— {{caption.claimant_designation_en}} —\n\n{{caption.versus_en}}\n\n**{{parties.defendant.0.name}}**\nrepresented by {{parties.defendant.0.representative}}\n\n— {{caption.defendant_designation_en}} —\n\nre {{caption.subject_en}}\n\nCase number: {{project.case_number}}\nPatent in suit: {{project.patent_number_upc}}\n{{project.court}}') + ELSE elem + END + ORDER BY ord + ) + FROM jsonb_array_elements(b.section_spec->'defaults') WITH ORDINALITY AS d(elem, ord) + ) +) +WHERE b.slug = 'upc-formal' + AND b.section_spec ? 'defaults'; + +-- ---------------------------------------------------------------- +-- Change 3 — backfill lexy-confirmed role labels for the four DE +-- appeal/nullity proceedings (mig-137 mechanism). Bracketed-inclusive +-- gender style; EN equivalents. +-- +-- de.inf.olg Berufungskläger(in) / Berufungsbeklagte(r) // Appellant / Respondent (§ 511 ZPO Berufung) +-- de.inf.bgh Revisionskläger(in) / Revisionsbeklagte(r) // Appellant / Respondent (§§ 542/544 ZPO; Revision as default over NZB) +-- de.null.bpatg Nichtigkeitskläger(in) / Beklagte(r) (Patentinhaber(in)) // Nullity claimant / Defendant (patent proprietor) (§ 81 PatG) +-- de.null.bgh Berufungskläger(in) / Berufungsbeklagte(r) // Appellant / Respondent (§ 110 PatG, post-2009 Berufung) +-- ---------------------------------------------------------------- + +UPDATE paliad.proceeding_types + SET role_proactive_label_de = 'Berufungskläger(in)', + role_reactive_label_de = 'Berufungsbeklagte(r)', + role_proactive_label_en = 'Appellant', + role_reactive_label_en = 'Respondent' + WHERE code = 'de.inf.olg'; + +UPDATE paliad.proceeding_types + SET role_proactive_label_de = 'Revisionskläger(in)', + role_reactive_label_de = 'Revisionsbeklagte(r)', + role_proactive_label_en = 'Appellant', + role_reactive_label_en = 'Respondent' + WHERE code = 'de.inf.bgh'; + +UPDATE paliad.proceeding_types + SET role_proactive_label_de = 'Nichtigkeitskläger(in)', + role_reactive_label_de = 'Beklagte(r) (Patentinhaber(in))', + role_proactive_label_en = 'Nullity claimant', + role_reactive_label_en = 'Defendant (patent proprietor)' + WHERE code = 'de.null.bpatg'; + +UPDATE paliad.proceeding_types + SET role_proactive_label_de = 'Berufungskläger(in)', + role_reactive_label_de = 'Berufungsbeklagte(r)', + role_proactive_label_en = 'Appellant', + role_reactive_label_en = 'Respondent' + WHERE code = 'de.null.bgh'; diff --git a/internal/services/submission_vars_caption_test.go b/internal/services/submission_vars_caption_test.go index 9178d08..3ecac07 100644 --- a/internal/services/submission_vars_caption_test.go +++ b/internal/services/submission_vars_caption_test.go @@ -1,9 +1,18 @@ package services -// Pins the parametric caption resolver (t-paliad-358 A-S2): heading / subject -// derive from jurisdiction + the proceeding code's nature segment; designations -// reuse the proceeding role-label overrides, fall back to instance-derived -// appeal/cassation wording, then to the civil default. +// Pins the parametric caption resolver (t-paliad-358 A-S2 + t-paliad-361 +// wording follow-up): heading / subject derive from jurisdiction + the +// proceeding code's nature segment; designations reuse the proceeding +// role-label overrides, fall back to instance-derived appeal/cassation +// wording, then to the civil default. +// +// t-paliad-361 additions: +// - UPC appeal EN responding party is now "Respondent" (not "Appellee"). +// - The four DE appeal/nullity proceedings (de.inf.olg, de.inf.bgh, +// de.null.bpatg, de.null.bgh) carry lexy-confirmed role-label overrides +// (mig 163), so their designations are correct even when +// project.instance_level is unset — pinned by the cases below that pass an +// empty Project but still expect the appeal/nullity wording. import ( "testing" @@ -17,6 +26,18 @@ func ptType(code, jurisdiction string) *models.ProceedingType { return &models.ProceedingType{Code: code, Jurisdiction: sp(jurisdiction)} } +// ptRoles builds a proceeding type carrying the mig-137/mig-163 role-label +// overrides (the four-column bracketed-inclusive designations). +func ptRoles(code, jurisdiction, proDE, reDE, proEN, reEN string) *models.ProceedingType { + return &models.ProceedingType{ + Code: code, Jurisdiction: sp(jurisdiction), + RoleProactiveLabelDE: sp(proDE), + RoleReactiveLabelDE: sp(reDE), + RoleProactiveLabelEN: sp(proEN), + RoleReactiveLabelEN: sp(reEN), + } +} + func TestResolveCaption(t *testing.T) { cases := []struct { name string @@ -26,6 +47,8 @@ func TestResolveCaption(t *testing.T) { wantClaimDE string wantDefDE string wantSubjDE string + wantClaimEN string + wantDefEN string }{ { name: "DE LG infringement → Rechtsstreit / Kläger-Beklagte / Patentverletzung", @@ -35,15 +58,19 @@ func TestResolveCaption(t *testing.T) { wantClaimDE: "Klägerin", wantDefDE: "Beklagte", wantSubjDE: "Patentverletzung", + wantClaimEN: "Claimant", + wantDefEN: "Defendant", }, { - name: "DE BPatG nullity → Patentnichtigkeitssache", + name: "DE BPatG nullity (no role-label data) → civil default fallback", project: &models.Project{}, pt: ptType("de.null.bpatg", "DE"), wantHeadDE: "In der Patentnichtigkeitssache", wantClaimDE: "Klägerin", wantDefDE: "Beklagte", wantSubjDE: "Nichtigkeit des Streitpatents", + wantClaimEN: "Claimant", + wantDefEN: "Defendant", }, { name: "UPC infringement → In der Sache / civil default", @@ -53,54 +80,120 @@ func TestResolveCaption(t *testing.T) { wantClaimDE: "Klägerin", wantDefDE: "Beklagte", wantSubjDE: "Patentverletzung", + wantClaimEN: "Claimant", + wantDefEN: "Defendant", }, { name: "UPC revocation → role-label override (Antragsteller Nichtigkeit)", project: &models.Project{}, - pt: &models.ProceedingType{ - Code: "upc.rev.cfi", Jurisdiction: sp("UPC"), - RoleProactiveLabelDE: sp("Antragsteller (Nichtigkeit)"), - RoleReactiveLabelDE: sp("Antragsgegner (Nichtigkeit)"), - }, + pt: ptRoles("upc.rev.cfi", "UPC", + "Antragsteller (Nichtigkeit)", "Antragsgegner (Nichtigkeit)", + "Revocation claimant", "Revocation defendant"), wantHeadDE: "In der Sache", wantClaimDE: "Antragsteller (Nichtigkeit)", wantDefDE: "Antragsgegner (Nichtigkeit)", wantSubjDE: "Nichtigkeit des Streitpatents", + wantClaimEN: "Revocation claimant", + wantDefEN: "Revocation defendant", }, { - name: "UPC appeal → role-label override wins over instance", + // t-paliad-361 Change 1: the role-label override wins over the + // instance-derived path, and its EN reactive label is now + // "Respondent" (was "Appellee", mig 163). + name: "UPC appeal → role-label override wins, EN reactive is Respondent", project: &models.Project{InstanceLevel: sp("appeal")}, - pt: &models.ProceedingType{ - Code: "upc.apl.unified", Jurisdiction: sp("UPC"), - RoleProactiveLabelDE: sp("Berufungskläger"), - RoleReactiveLabelDE: sp("Berufungsbeklagter"), - }, + pt: ptRoles("upc.apl.unified", "UPC", + "Berufungskläger", "Berufungsbeklagter", + "Appellant", "Respondent"), wantHeadDE: "In der Sache", wantClaimDE: "Berufungskläger", wantDefDE: "Berufungsbeklagter", wantSubjDE: "Patentstreitsache", + wantClaimEN: "Appellant", + wantDefEN: "Respondent", }, { - name: "DE OLG appeal via instance_level (no role-label data)", + name: "DE OLG appeal via instance_level (no role-label data) → instance-derived", project: &models.Project{InstanceLevel: sp("appeal")}, pt: ptType("de.inf.olg", "DE"), wantHeadDE: "In dem Rechtsstreit", wantClaimDE: "Berufungskläger(in)", wantDefDE: "Berufungsbeklagte(r)", wantSubjDE: "Patentverletzung", + wantClaimEN: "Appellant", + wantDefEN: "Respondent", }, { name: "EPA opposition → Einsprechende(r) / Patentinhaber(in)", project: &models.Project{}, - pt: &models.ProceedingType{ - Code: "epa.opp.opd", Jurisdiction: sp("EPA"), - RoleProactiveLabelDE: sp("Einsprechende(r)"), - RoleReactiveLabelDE: sp("Patentinhaber(in)"), - }, + pt: ptRoles("epa.opp.opd", "EPA", + "Einsprechende(r)", "Patentinhaber(in)", + "Opponent", "Patentee"), wantHeadDE: "Im Einspruchsverfahren", wantClaimDE: "Einsprechende(r)", wantDefDE: "Patentinhaber(in)", wantSubjDE: "Einspruch gegen das Streitpatent", + wantClaimEN: "Opponent", + wantDefEN: "Patentee", + }, + + // --------------------------------------------------------------- + // t-paliad-361 Change 3: the four DE appeal/nullity proceedings now + // carry lexy-confirmed role-label overrides (mig 163). Each case + // passes an EMPTY Project (instance_level unset) to prove the override + // yields the correct designation without relying on the instance path. + // --------------------------------------------------------------- + { + name: "de.inf.olg backfill → Berufungskläger(in)/Berufungsbeklagte(r), instance unset", + project: &models.Project{}, + pt: ptRoles("de.inf.olg", "DE", + "Berufungskläger(in)", "Berufungsbeklagte(r)", + "Appellant", "Respondent"), + wantHeadDE: "In dem Rechtsstreit", + wantClaimDE: "Berufungskläger(in)", + wantDefDE: "Berufungsbeklagte(r)", + wantSubjDE: "Patentverletzung", + wantClaimEN: "Appellant", + wantDefEN: "Respondent", + }, + { + name: "de.inf.bgh backfill → Revisionskläger(in)/Revisionsbeklagte(r), instance unset", + project: &models.Project{}, + pt: ptRoles("de.inf.bgh", "DE", + "Revisionskläger(in)", "Revisionsbeklagte(r)", + "Appellant", "Respondent"), + wantHeadDE: "In dem Rechtsstreit", + wantClaimDE: "Revisionskläger(in)", + wantDefDE: "Revisionsbeklagte(r)", + wantSubjDE: "Patentverletzung", + wantClaimEN: "Appellant", + wantDefEN: "Respondent", + }, + { + name: "de.null.bpatg backfill → Nichtigkeitskläger(in)/Beklagte(r) (Patentinhaber(in))", + project: &models.Project{}, + pt: ptRoles("de.null.bpatg", "DE", + "Nichtigkeitskläger(in)", "Beklagte(r) (Patentinhaber(in))", + "Nullity claimant", "Defendant (patent proprietor)"), + wantHeadDE: "In der Patentnichtigkeitssache", + wantClaimDE: "Nichtigkeitskläger(in)", + wantDefDE: "Beklagte(r) (Patentinhaber(in))", + wantSubjDE: "Nichtigkeit des Streitpatents", + wantClaimEN: "Nullity claimant", + wantDefEN: "Defendant (patent proprietor)", + }, + { + name: "de.null.bgh backfill → Berufungskläger(in)/Berufungsbeklagte(r) (§110 PatG Berufung)", + project: &models.Project{}, + pt: ptRoles("de.null.bgh", "DE", + "Berufungskläger(in)", "Berufungsbeklagte(r)", + "Appellant", "Respondent"), + wantHeadDE: "In der Patentnichtigkeitssache", + wantClaimDE: "Berufungskläger(in)", + wantDefDE: "Berufungsbeklagte(r)", + wantSubjDE: "Nichtigkeit des Streitpatents", + wantClaimEN: "Appellant", + wantDefEN: "Respondent", }, } @@ -119,6 +212,12 @@ func TestResolveCaption(t *testing.T) { if got.subjectDE != c.wantSubjDE { t.Errorf("subjectDE = %q, want %q", got.subjectDE, c.wantSubjDE) } + if got.claimantEN != c.wantClaimEN { + t.Errorf("claimantEN = %q, want %q", got.claimantEN, c.wantClaimEN) + } + if got.defendantEN != c.wantDefEN { + t.Errorf("defendantEN = %q, want %q", got.defendantEN, c.wantDefEN) + } }) } }