Atomic extraction of the deadline-rule compute engine + types from
internal/services into a new pkg/litigationplanner package that paliad
+ youpc.org can both import. No behaviour change — every existing test
passes against the post-move shape.
Package contents (~1850 LoC):
- doc.go package docstring + reuse manifesto
- types.go Rule, ProceedingType, NullableJSON, AdjustmentReason,
HolidayDTO, CalcOptions, CalcRuleParams, Timeline,
TimelineEntry, RuleCalculation*, FristenrechnerType,
ProjectHint, sentinel errors
- catalog.go Catalog interface (proceeding + rule lookups)
- holidays.go HolidayCalendar interface
- courts.go CourtRegistry interface + DefaultsForJurisdiction +
country/regime constants
- expr.go EvalConditionExpr + HasConditionExpr +
ExtractFlagsFromExpr (jsonb gate evaluator)
- durations.go ApplyDuration + AddWorkingDays (pure compute)
- subtrack.go SubTrackRouting + LookupSubTrackRouting registry
- legal_source.go FormatLegalSourceDisplay + BuildLegalSourceURL
- proceeding_mapping.go MapLitigationToFristenrechner + code constants
(CodeUPCInfringement, CodeDEInfringementLG, ...)
- engine.go Calculate + CalculateRule + the trigger-event
branch + applyRuleOverrides (the big move)
paliad side (~1900 LoC net deletion):
- internal/services/fristenrechner.go shrinks from 1505 → ~290 lines
(thin paliad Catalog adapter + type aliases for back-compat).
- internal/models/models.go: DeadlineRule, ProceedingType, NullableJSON
become type aliases to litigationplanner.* — every sqlx scan and
every projection_service caller compiles unchanged.
- internal/services/holidays.go: AdjustmentReason + HolidayDTO become
aliases to lp.* (canonical definitions now in the package).
- internal/services/proceeding_mapping.go: rewritten as thin re-exports
of lp constants + helpers.
- internal/services/deadline_search_service.go: FormatLegalSourceDisplay
+ BuildLegalSourceURL replaced with delegating wrappers to lp.
Catalog interface satisfaction:
- DeadlineRuleService → paliadCatalog adapter (wraps the existing
service, replicates the original SELECT shapes).
- HolidayService → satisfies lp.HolidayCalendar directly (compile-
time assertion at end of fristenrechner.go).
- CourtService → satisfies lp.CourtRegistry directly.
Wire shape is byte-identical. JSON tags on Rule / ProceedingType /
Timeline / TimelineEntry / RuleCalculation match the historical
UIResponse / UIDeadline shape; the frontend reads the same bytes.
Slice B (Catalog interface + paliad loader cleanup) is folded into
this commit since Slice A already needs the interfaces to call
Calculate across the boundary. Slice C (embedded UPC snapshot +
generator) is the next coder shift; the Berufung unification m
called out lands in Slice B/C per head's brief.
Refs: docs/design-litigation-planner-2026-05-26.md
140 lines
5.8 KiB
Go
140 lines
5.8 KiB
Go
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
|
|
}
|