package litigationplanner // proceeding_mapping bridges the two proceeding-type vocabularies in the // codebase: the **litigation** conceptual category (INF / REV / APP / // CCR / AMD / APM / ZPO_CIVIL) used by the historical project-binding // + Pipeline-A rules, and the **fristenrechner** code category // (upc.inf.cfi / de.inf.lg / epa.opp.opd / …) used by the Determinator // cascade + rule engine. Post-Phase-3-Slice-5 (t-paliad-186) projects // bind to fristenrechner codes directly, but the litigation→fristenrechner // mapping is still needed for the ~40 Pipeline-A rules that remain on // litigation proceedings and for any other surface that thinks in // litigation terms. // // The mapping table here is the single source of truth — see // docs/design-determinator-row-cascade-2026-05-13.md §4.2 for the // design rationale + ambiguity notes, and // docs/design-proceeding-code-taxonomy-2026-05-18.md for the // lowercase dot-separated naming convention applied by mig 096 // (t-paliad-206). **Never silent FK promotion**: every ambiguous case // returns ok=false so callers can degrade gracefully ("no narrowing") // instead of guessing. // Stable code constants — the strings landed by mig 096. Use these // throughout the codebase so a future rename only needs to touch this // file. The id-anchored FKs (deadline_rules.proceeding_type_id, // projects.proceeding_type_id) are unaffected by the rename. const ( CodeUPCInfringement = "upc.inf.cfi" CodeUPCRevocation = "upc.rev.cfi" CodeUPCCounterclaim = "upc.ccr.cfi" CodeUPCPreliminary = "upc.pi.cfi" CodeUPCDamages = "upc.dmgs.cfi" CodeUPCDiscovery = "upc.disc.cfi" CodeUPCAppealMerits = "upc.apl.merits" CodeUPCAppealOrder = "upc.apl.order" CodeUPCAppealCost = "upc.apl.cost" CodeDEInfringementLG = "de.inf.lg" CodeDEInfringementOLG = "de.inf.olg" CodeDEInfringementBGH = "de.inf.bgh" CodeDENullityBPatG = "de.null.bpatg" CodeDENullityBGH = "de.null.bgh" CodeEPAGrant = "epa.grant.exa" CodeEPAOpposition = "epa.opp.opd" CodeEPAOppositionAppeal = "epa.opp.boa" CodeDPMAOpposition = "dpma.opp.dpma" CodeDPMAAppealBPatG = "dpma.appeal.bpatg" CodeDPMAAppealBGH = "dpma.appeal.bgh" ) // MapLitigationToFristenrechner returns the fristenrechner code + // condition flags implied by a (litigationCode, jurisdiction) pair. // // Inputs are case-sensitive — pass the canonical upper-snake form // (e.g. "INF", "UPC"). Unrecognised codes or genuinely ambiguous // combinations (APP+DE, ZPO_CIVIL+DE) return ok=false with a zero // fristenrechner code; callers should treat that as "no narrowing" // and leave the cascade wide-open rather than auto-pick. // // Condition flags are returned as a slice so callers can apply them // alongside the fristenrechner code (CCR+UPC → upc.inf.cfi + with_ccr, // AMD+UPC → upc.inf.cfi + with_amend). An empty slice means no flag // context applies. func MapLitigationToFristenrechner(litigationCode, jurisdiction string) (fristenrechnerCode string, conditionFlags []string, ok bool) { switch litigationCode { case "INF": switch jurisdiction { case "UPC": return CodeUPCInfringement, nil, true case "DE": return CodeDEInfringementLG, nil, true } case "REV": switch jurisdiction { case "UPC": return CodeUPCRevocation, nil, true case "DE": return CodeDENullityBPatG, nil, true } case "CCR": // Counterclaim revocation — UPC fold-in is structural (the // counterclaim lives inside an upc.inf.cfi proceeding with the // with_ccr flag). DE Nichtigkeit is conceptually the same // adversarial-validity test, no separate flag. switch jurisdiction { case "UPC": return CodeUPCInfringement, []string{"with_ccr"}, true case "DE": return CodeDENullityBPatG, nil, true } case "AMD": // Amendment-application bundled into upc.inf.cfi via with_amend. // No DE / EPA / DPMA analogue today. if jurisdiction == "UPC" { return CodeUPCInfringement, []string{"with_amend"}, true } case "APP": // Appeal is ambiguous in DE (OLG vs BGH) and the project // model doesn't carry the instance hint we'd need to // disambiguate. UPC is unambiguous — upc.apl.merits covers // the merits appeal track for inf/rev/ccr/damages. if jurisdiction == "UPC" { return CodeUPCAppealMerits, nil, true } case "APM": // Preliminary injunction / urgency procedure — UPC-only // concept in the fristenrechner taxonomy. if jurisdiction == "UPC" { return CodeUPCPreliminary, nil, true } case "OPP": // Opposition — primarily EPA. DPMA has dpma.opp.dpma but it // doesn't surface from the litigation vocabulary today. if jurisdiction == "EPA" { return CodeEPAOpposition, nil, true } } return "", nil, false } // ResolveCounterclaimRouting handles the determinator's // upc.ccr.cfi illustrative-peer route: the code exists in the dropdown // for taxonomic completeness, but no rules are attached to it. When the // cascade resolves to upc.ccr.cfi we route the rule lookup back to // upc.inf.cfi with a default with_ccr=true flag — see // docs/design-proceeding-code-taxonomy-2026-05-18.md §0.3 sub-decision S1. // // `code` is the proceeding code the cascade resolved to. If it's // upc.ccr.cfi, the function returns (CodeUPCInfringement, // []string{"with_ccr"}, true). For any other code the function returns // (code, nil, false) and callers proceed with the code unchanged. The // boolean signals "routing was applied"; the caller can surface the hint // "Regeln liegen auf upc.inf.cfi (with_ccr=true); wir leiten Sie dorthin // weiter." in the UI. func ResolveCounterclaimRouting(code string) (effectiveCode string, defaultFlags []string, routed bool) { if route, ok := SubTrackRoutings[code]; ok { return route.ParentCode, route.DefaultFlags, true } return code, nil, false }