@tracepass/dpp-validate 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/vendor/battery-applicability.d.ts +140 -0
- package/dist/vendor/battery-applicability.d.ts.map +1 -0
- package/dist/vendor/battery-applicability.js +513 -0
- package/dist/vendor/battery-applicability.js.map +1 -0
- package/dist/vendor/condition-profile.d.ts +29 -0
- package/dist/vendor/condition-profile.d.ts.map +1 -0
- package/dist/vendor/condition-profile.js +47 -0
- package/dist/vendor/condition-profile.js.map +1 -0
- package/dist/vendor/publish-gate.d.ts +54 -8
- package/dist/vendor/publish-gate.d.ts.map +1 -1
- package/dist/vendor/publish-gate.js +112 -31
- package/dist/vendor/publish-gate.js.map +1 -1
- package/dist/verdict.d.ts +3 -2
- package/dist/verdict.d.ts.map +1 -1
- package/dist/verdict.js +42 -7
- package/dist/verdict.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Battery field-applicability predicate — the SINGLE source of truth for
|
|
3
|
+
* "does this Annex XIII field apply to *this* battery?", consumed by both:
|
|
4
|
+
*
|
|
5
|
+
* - the compliance verdict engine (rules.ts) — flags a filled field that
|
|
6
|
+
* doesn't apply, or warns when applicability can't be determined; and
|
|
7
|
+
* - the passport editor UI (PassportEditor.tsx) — soft-collapses
|
|
8
|
+
* not-applicable fields so a reviewer isn't asked for data the
|
|
9
|
+
* regulation doesn't require for their battery type.
|
|
10
|
+
*
|
|
11
|
+
* Source: DG GROW battery-passport webinar 27 May 2026, slide 36
|
|
12
|
+
* (per-category applicability matrix) + the cross-reference/scope-gate
|
|
13
|
+
* slides; Reg. (EU) 2023/1542 Art. 14(1) + Annex VII Parts A and B.
|
|
14
|
+
* Analysis: knowledgebase/regulations/dpp-battery-webinar-2026-05.md.
|
|
15
|
+
* Spec home: knowledgebase/regulations/dpp-conditional-rules.md.
|
|
16
|
+
*
|
|
17
|
+
* Design contract — the SAFETY rule that makes A safe:
|
|
18
|
+
* "unknown" (a gate's trigger field is absent) ⇒ treat as APPLIES for the
|
|
19
|
+
* UI (show the field) and as `unverifiable_conditional` for the verdict
|
|
20
|
+
* (warn, never assert). We NEVER hide or suppress a field on uncertainty —
|
|
21
|
+
* a mis-classified battery must never lose a legally-required field.
|
|
22
|
+
*
|
|
23
|
+
* Triggers:
|
|
24
|
+
* - batteryCategory — a real template field in battery.json (enum: LMT |
|
|
25
|
+
* EV | industrial_gt_2kwh | industrial_lte_2kwh | SLI
|
|
26
|
+
* | portable). Read from `passport.fields`.
|
|
27
|
+
* - hasBMS, rechargeable, externalStorageOnly, isStationaryBess — tri-state
|
|
28
|
+
* booleans on `passport.batteryProfile` (a separate
|
|
29
|
+
* block, NOT template fields — keeps the template's
|
|
30
|
+
* field count intact). Absent ⇒ the dependent gate
|
|
31
|
+
* resolves to "unknown" (show + warn), not a guess.
|
|
32
|
+
*
|
|
33
|
+
* Pure + IO-free → unit-tested in tests/compliance/battery-applicability.test.ts.
|
|
34
|
+
*/
|
|
35
|
+
import type { Passport } from "@tracepass/dpp-types";
|
|
36
|
+
import { IN_SCOPE_BATTERY_CATEGORIES } from "./battery-scope.js";
|
|
37
|
+
export type Applicability = "applies" | "not_applicable" | "unknown";
|
|
38
|
+
/** Battery categories that are *in scope* for a battery passport at all.
|
|
39
|
+
* Re-exported from the shared definition so this module's consumers keep
|
|
40
|
+
* importing it from here; the passport-level scope gate is rules.ts BAT-1. */
|
|
41
|
+
export { IN_SCOPE_BATTERY_CATEGORIES };
|
|
42
|
+
export interface FieldGate {
|
|
43
|
+
/** Template field keys this gate governs. */
|
|
44
|
+
keys: string[];
|
|
45
|
+
/** Regulation article / annex point reference, for the verdict citation. */
|
|
46
|
+
article: string;
|
|
47
|
+
/** One-line reason, surfaced in the verdict + editor tooltip. */
|
|
48
|
+
reason: string;
|
|
49
|
+
/** Extra remediation, for gates that turn on the stationary-storage flag. */
|
|
50
|
+
fixHint?: string;
|
|
51
|
+
/**
|
|
52
|
+
* ISO-8601 date from which the underlying provision is mandatory.
|
|
53
|
+
* Before this date `categoryFieldApplicability` returns "unknown" for fields
|
|
54
|
+
* governed by this gate, so they produce a warning but never a hard block.
|
|
55
|
+
* After this date the gate's `decide()` result is used as-is.
|
|
56
|
+
*/
|
|
57
|
+
mandatoryFrom?: string;
|
|
58
|
+
/** Decide applicability from the resolved trigger values. */
|
|
59
|
+
decide(t: Triggers): Applicability;
|
|
60
|
+
}
|
|
61
|
+
interface Triggers {
|
|
62
|
+
/** Sub-category value read from the passport (batteryCategory / detergentUserType / …). */
|
|
63
|
+
category: string | undefined;
|
|
64
|
+
hasBMS: boolean | undefined;
|
|
65
|
+
rechargeable: boolean | undefined;
|
|
66
|
+
externalStorageOnly: boolean | undefined;
|
|
67
|
+
isStationaryBess: boolean | undefined;
|
|
68
|
+
microorganismsAdded?: boolean | undefined;
|
|
69
|
+
sdsProvided?: boolean | undefined;
|
|
70
|
+
containsAllergenicFragrances?: boolean | undefined;
|
|
71
|
+
alsoUnderOtherActs?: boolean | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Field-derived tri-state: resolves whether a named passport field has an
|
|
74
|
+
* approved, non-empty value.
|
|
75
|
+
* true — field present + approved
|
|
76
|
+
* undefined — field present but pending_review (unknown → no hard block)
|
|
77
|
+
* false — field absent or empty
|
|
78
|
+
* Optional for backward compat; absent ≡ false (field not present).
|
|
79
|
+
*/
|
|
80
|
+
fieldPresent?(key: string): boolean | undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The conditional gates, one per applicability rule from the deck. Order
|
|
84
|
+
* doesn't matter — each governs a disjoint set of field keys.
|
|
85
|
+
*/
|
|
86
|
+
export declare const BATTERY_FIELD_GATES: FieldGate[];
|
|
87
|
+
/** Every field key under any gate (the conditional set). */
|
|
88
|
+
export declare const GATED_FIELD_KEYS: ReadonlySet<string>;
|
|
89
|
+
/**
|
|
90
|
+
* Applicability for a single battery field key. Ungated keys are always
|
|
91
|
+
* "applies". This is the granular accessor the editor uses per-field.
|
|
92
|
+
*/
|
|
93
|
+
export declare function fieldApplicability(passport: Passport, key: string): Applicability;
|
|
94
|
+
/**
|
|
95
|
+
* Full map for the gated fields only (callers default everything else to
|
|
96
|
+
* "applies"). The verdict engine iterates this to emit findings; the
|
|
97
|
+
* editor reads it once per render and looks up per field.
|
|
98
|
+
*
|
|
99
|
+
* Returns an empty map for non-battery passports — `category` here is the
|
|
100
|
+
* template category, NOT batteryCategory; callers pass it so this is a
|
|
101
|
+
* no-op outside battery and the predicate stays battery-scoped.
|
|
102
|
+
*/
|
|
103
|
+
/** @deprecated Use `categoryFieldApplicability` instead. Kept for backward compat. */
|
|
104
|
+
export declare function batteryFieldApplicability(passport: Passport, category: string): Record<string, Applicability>;
|
|
105
|
+
/** Look up the gate metadata (article + reason) for a gated field key. */
|
|
106
|
+
export declare function gateForKey(key: string): FieldGate | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* Date strings from which each category's passport obligations become mandatory.
|
|
109
|
+
* Before these dates, any gate that carries `mandatoryFrom` returns "unknown"
|
|
110
|
+
* (warning, no hard block). After, the gate's `decide()` result is used as-is.
|
|
111
|
+
*/
|
|
112
|
+
export declare const DETERGENTS_PASSPORT_MANDATORY_FROM = "2029-09-23";
|
|
113
|
+
export declare const TOYS_PASSPORT_MANDATORY_FROM = "2030-08-01";
|
|
114
|
+
/**
|
|
115
|
+
* CATEGORY_FIELD_GATES — generalised gate registry, keyed by template category.
|
|
116
|
+
*
|
|
117
|
+
* Phase 1: battery only. Phase 2: detergents and toys added.
|
|
118
|
+
*
|
|
119
|
+
* Callers that need to enumerate gates for a category use this registry.
|
|
120
|
+
* The battery-specific `batteryFieldApplicability` is now a thin wrapper.
|
|
121
|
+
*/
|
|
122
|
+
export declare const CATEGORY_FIELD_GATES: Record<string, FieldGate[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Compute the applicability map for all gated fields of any category.
|
|
125
|
+
* Returns {} for categories with no gates (safe to iterate).
|
|
126
|
+
*
|
|
127
|
+
* Replaces the battery-only early return in `batteryFieldApplicability` with a
|
|
128
|
+
* generic path: every category that has gates in CATEGORY_FIELD_GATES is handled,
|
|
129
|
+
* everything else is a no-op. `batteryFieldApplicability` is kept as a
|
|
130
|
+
* backward-compat alias.
|
|
131
|
+
*
|
|
132
|
+
* @param now Optional reference date for `mandatoryFrom` enforcement. Defaults
|
|
133
|
+
* to the current wall clock. Callers should pass a fixed date in tests.
|
|
134
|
+
* When a gate's `mandatoryFrom` is in the future relative to `now`,
|
|
135
|
+
* ALL keys governed by that gate resolve to "unknown" (warning only,
|
|
136
|
+
* never a hard publish block) — keeping the provision from creating
|
|
137
|
+
* obligations before it legally applies.
|
|
138
|
+
*/
|
|
139
|
+
export declare function categoryFieldApplicability(passport: Passport, category: string, now?: Date): Record<string, Applicability>;
|
|
140
|
+
//# sourceMappingURL=battery-applicability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"battery-applicability.d.ts","sourceRoot":"","sources":["../../src/vendor/battery-applicability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAGjE,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAAC;AAErE;;+EAE+E;AAC/E,OAAO,EAAE,2BAA2B,EAAE,CAAC;AAUvC,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,MAAM,CAAC,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;CACpC;AAED,UAAU,QAAQ;IAChB,2FAA2F;IAC3F,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,YAAY,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,mBAAmB,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,gBAAgB,EAAE,OAAO,GAAG,SAAS,CAAC;IAGtC,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAElC,4BAA4B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnD,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC;;;;;;;OAOG;IACH,YAAY,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CACjD;AAyFD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,EA2L1C,CAAC;AAQF,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAA+B,CAAC;AAEjF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAIjF;AAED;;;;;;;;GAQG;AACH,sFAAsF;AACtF,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAE/B;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAE7D;AAED;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,eAAe,CAAC;AAC/D,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAqFzD;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAI5D,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,IAAI,GACT,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAiB/B"}
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Battery field-applicability predicate — the SINGLE source of truth for
|
|
3
|
+
* "does this Annex XIII field apply to *this* battery?", consumed by both:
|
|
4
|
+
*
|
|
5
|
+
* - the compliance verdict engine (rules.ts) — flags a filled field that
|
|
6
|
+
* doesn't apply, or warns when applicability can't be determined; and
|
|
7
|
+
* - the passport editor UI (PassportEditor.tsx) — soft-collapses
|
|
8
|
+
* not-applicable fields so a reviewer isn't asked for data the
|
|
9
|
+
* regulation doesn't require for their battery type.
|
|
10
|
+
*
|
|
11
|
+
* Source: DG GROW battery-passport webinar 27 May 2026, slide 36
|
|
12
|
+
* (per-category applicability matrix) + the cross-reference/scope-gate
|
|
13
|
+
* slides; Reg. (EU) 2023/1542 Art. 14(1) + Annex VII Parts A and B.
|
|
14
|
+
* Analysis: knowledgebase/regulations/dpp-battery-webinar-2026-05.md.
|
|
15
|
+
* Spec home: knowledgebase/regulations/dpp-conditional-rules.md.
|
|
16
|
+
*
|
|
17
|
+
* Design contract — the SAFETY rule that makes A safe:
|
|
18
|
+
* "unknown" (a gate's trigger field is absent) ⇒ treat as APPLIES for the
|
|
19
|
+
* UI (show the field) and as `unverifiable_conditional` for the verdict
|
|
20
|
+
* (warn, never assert). We NEVER hide or suppress a field on uncertainty —
|
|
21
|
+
* a mis-classified battery must never lose a legally-required field.
|
|
22
|
+
*
|
|
23
|
+
* Triggers:
|
|
24
|
+
* - batteryCategory — a real template field in battery.json (enum: LMT |
|
|
25
|
+
* EV | industrial_gt_2kwh | industrial_lte_2kwh | SLI
|
|
26
|
+
* | portable). Read from `passport.fields`.
|
|
27
|
+
* - hasBMS, rechargeable, externalStorageOnly, isStationaryBess — tri-state
|
|
28
|
+
* booleans on `passport.batteryProfile` (a separate
|
|
29
|
+
* block, NOT template fields — keeps the template's
|
|
30
|
+
* field count intact). Absent ⇒ the dependent gate
|
|
31
|
+
* resolves to "unknown" (show + warn), not a guess.
|
|
32
|
+
*
|
|
33
|
+
* Pure + IO-free → unit-tested in tests/compliance/battery-applicability.test.ts.
|
|
34
|
+
*/
|
|
35
|
+
import { resolveConditionProfile } from "./condition-profile.js";
|
|
36
|
+
import { IN_SCOPE_BATTERY_CATEGORIES } from "./battery-scope.js";
|
|
37
|
+
import { SUBCATEGORY_FIELD } from "./subcategory.js";
|
|
38
|
+
/** Battery categories that are *in scope* for a battery passport at all.
|
|
39
|
+
* Re-exported from the shared definition so this module's consumers keep
|
|
40
|
+
* importing it from here; the passport-level scope gate is rules.ts BAT-1. */
|
|
41
|
+
export { IN_SCOPE_BATTERY_CATEGORIES };
|
|
42
|
+
/** Field keys whose applicability is conditional, with a short reason +
|
|
43
|
+
* the deck article they map to. Everything NOT listed here is always
|
|
44
|
+
* "applies". Kept as data so the rules engine + editor can enumerate the
|
|
45
|
+
* conditional set and so a reader can audit each gate against the deck. */
|
|
46
|
+
/** Remediation for the gates that depend on `isStationaryBess`. */
|
|
47
|
+
const SBESS_FIX_HINT = "For an industrial (>2 kWh) battery, confirm whether it is a stationary battery energy storage system: Art. 14 state-of-health and Annex VII expected-lifetime data apply only to stationary storage systems, LMT and (for state of health) EV batteries.";
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a profile flag to a confirmed boolean. Only an `approved` flag
|
|
50
|
+
* counts — a `pending_review` (AI-suggested, unconfirmed) flag resolves to
|
|
51
|
+
* `undefined` ("unknown") so it can NEVER hard-hide a field. This is the
|
|
52
|
+
* safety contract: AI may suggest, but gating tightens only on confirmation.
|
|
53
|
+
*
|
|
54
|
+
* Accepts any object with `value: boolean` and `status: string` — works for
|
|
55
|
+
* both the legacy `BatteryProfileFlag` and the new `ConditionFlag`.
|
|
56
|
+
*/
|
|
57
|
+
function confirmed(flag) {
|
|
58
|
+
if (!flag)
|
|
59
|
+
return undefined;
|
|
60
|
+
return flag.status === "approved" ? flag.value : undefined;
|
|
61
|
+
}
|
|
62
|
+
function readTriggers(passport, templateCategory = "battery") {
|
|
63
|
+
// Sub-category field key (batteryCategory / detergentUserType / …) from the
|
|
64
|
+
// SUBCATEGORY_FIELD map. Falls back to undefined for categories with no map entry (e.g. toys).
|
|
65
|
+
const subcatFieldKey = SUBCATEGORY_FIELD[templateCategory];
|
|
66
|
+
const catField = subcatFieldKey ? passport.fields[subcatFieldKey] : undefined;
|
|
67
|
+
// Use the canonical accessor so conditionProfile (new) and batteryProfile
|
|
68
|
+
// (legacy) both work through the same path.
|
|
69
|
+
const profile = resolveConditionProfile(passport);
|
|
70
|
+
return {
|
|
71
|
+
category: catField && catField.value != null && catField.value !== ""
|
|
72
|
+
? String(catField.value)
|
|
73
|
+
: undefined,
|
|
74
|
+
// Battery flags
|
|
75
|
+
hasBMS: confirmed(profile["hasBMS"]),
|
|
76
|
+
rechargeable: confirmed(profile["rechargeable"]),
|
|
77
|
+
externalStorageOnly: confirmed(profile["externalStorageOnly"]),
|
|
78
|
+
isStationaryBess: confirmed(profile["isStationaryBess"]),
|
|
79
|
+
// Detergents flags (phase 2)
|
|
80
|
+
microorganismsAdded: confirmed(profile["microorganismsAdded"]),
|
|
81
|
+
sdsProvided: confirmed(profile["sdsProvided"]),
|
|
82
|
+
// Toys flags (phase 2)
|
|
83
|
+
containsAllergenicFragrances: confirmed(profile["containsAllergenicFragrances"]),
|
|
84
|
+
alsoUnderOtherActs: confirmed(profile["alsoUnderOtherActs"]),
|
|
85
|
+
// Field-derived tri-state: approved non-empty → true; pending_review → undefined; absent → false
|
|
86
|
+
fieldPresent: (key) => {
|
|
87
|
+
const f = passport.fields[key];
|
|
88
|
+
if (!f || f.value == null || f.value === "")
|
|
89
|
+
return false;
|
|
90
|
+
return f.status === "approved" ? true : undefined;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Is this battery an Art. 14(1) subject (state-of-health / expected-lifetime
|
|
96
|
+
* data applies)? Art. 14(1) covers EV, LMT, and stationary battery energy
|
|
97
|
+
* storage systems — NOT other industrial batteries.
|
|
98
|
+
*
|
|
99
|
+
* Returns:
|
|
100
|
+
* true — EV or LMT (deterministic from category alone)
|
|
101
|
+
* true/false — industrial_gt_2kwh resolved through isStationaryBess
|
|
102
|
+
* undefined — category absent (cannot determine) or
|
|
103
|
+
* industrial_gt_2kwh with isStationaryBess unconfirmed
|
|
104
|
+
* false — any other category (portable, SLI, industrial_lte_2kwh)
|
|
105
|
+
*/
|
|
106
|
+
function art14Subject(t) {
|
|
107
|
+
if (t.category === undefined)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (t.category === "EV" || t.category === "LMT")
|
|
110
|
+
return true;
|
|
111
|
+
if (t.category === "industrial_gt_2kwh")
|
|
112
|
+
return t.isStationaryBess;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Annex VII Part A + B applicability: applies for LMT; for industrial_gt_2kwh
|
|
117
|
+
* only when confirmed as a stationary BESS; not_applicable for EV and all
|
|
118
|
+
* other categories; unknown when category is absent or isStationaryBess unset.
|
|
119
|
+
*
|
|
120
|
+
* Used for: the Annex VII Part A cluster (remaining capacity, pack resistance,
|
|
121
|
+
* etc.) and the Annex VII Part B cluster (expected-lifetime: capacity/energy
|
|
122
|
+
* throughput, date of service entry).
|
|
123
|
+
*/
|
|
124
|
+
function sbessOrLmt(t) {
|
|
125
|
+
if (t.category === undefined)
|
|
126
|
+
return "unknown";
|
|
127
|
+
if (t.category === "LMT")
|
|
128
|
+
return "applies";
|
|
129
|
+
if (t.category === "industrial_gt_2kwh") {
|
|
130
|
+
if (t.isStationaryBess === true)
|
|
131
|
+
return "applies";
|
|
132
|
+
if (t.isStationaryBess === false)
|
|
133
|
+
return "not_applicable";
|
|
134
|
+
return "unknown";
|
|
135
|
+
}
|
|
136
|
+
// EV uses SOCE instead; portable / SLI / industrial_lte_2kwh are out of scope.
|
|
137
|
+
return "not_applicable";
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The conditional gates, one per applicability rule from the deck. Order
|
|
141
|
+
* doesn't matter — each governs a disjoint set of field keys.
|
|
142
|
+
*/
|
|
143
|
+
export const BATTERY_FIELD_GATES = [
|
|
144
|
+
{
|
|
145
|
+
// Deck 1(k): "capacity threshold for exhaustion (only for electric
|
|
146
|
+
// vehicle batteries)". Resolvable today — trigger is batteryCategory.
|
|
147
|
+
keys: ["capacityThresholdForExhaustion"],
|
|
148
|
+
article: "Annex XIII 1(k)",
|
|
149
|
+
reason: "Capacity threshold for exhaustion applies only to electric-vehicle (EV) batteries.",
|
|
150
|
+
decide: (t) => {
|
|
151
|
+
if (t.category === undefined)
|
|
152
|
+
return "unknown";
|
|
153
|
+
return t.category === "EV" ? "applies" : "not_applicable";
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
// Annex XIII 4(b) / Art. 14(1): state of health applies to stationary
|
|
158
|
+
// battery energy storage systems (SBESS), LMT, and EV batteries — and
|
|
159
|
+
// only when they have a battery management system. A plain industrial
|
|
160
|
+
// battery that is NOT a stationary ESS does NOT owe state-of-health data.
|
|
161
|
+
// art14Subject encodes the Art. 14(1) subject set; hasBMS is the second
|
|
162
|
+
// condition. Either being false → not_applicable; either absent → unknown.
|
|
163
|
+
keys: ["stateOfHealth"],
|
|
164
|
+
article: "Annex XIII 4(b) / Art. 14",
|
|
165
|
+
fixHint: SBESS_FIX_HINT,
|
|
166
|
+
reason: "State of health (Art. 14) applies only to stationary battery energy storage systems, LMT, and EV batteries that have a battery management system (BMS). A plain industrial (>2 kWh) battery that is not a stationary storage system does not owe this data.",
|
|
167
|
+
decide: (t) => {
|
|
168
|
+
const a14 = art14Subject(t);
|
|
169
|
+
// Either axis definitively false → field does not apply.
|
|
170
|
+
if (a14 === false || t.hasBMS === false)
|
|
171
|
+
return "not_applicable";
|
|
172
|
+
// Either axis unknown (category absent, or SBESS unconfirmed) → cannot determine.
|
|
173
|
+
if (a14 === undefined || t.hasBMS === undefined)
|
|
174
|
+
return "unknown";
|
|
175
|
+
// Both confirmed true → applies.
|
|
176
|
+
return "applies";
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
// Deck (c): carbon footprint cross-references Art. 7, which is
|
|
181
|
+
// rechargeable-only ("If the battery is non-rechargeable, (c) remains
|
|
182
|
+
// empty"). rechargeable absent ⇒ unknown.
|
|
183
|
+
keys: [
|
|
184
|
+
"carbonFootprintTotal",
|
|
185
|
+
"carbonFootprintPerformanceClass",
|
|
186
|
+
"carbonFootprintLabel",
|
|
187
|
+
"carbonFootprintStudyUrl",
|
|
188
|
+
"cfRawMaterialAcquisition",
|
|
189
|
+
"cfMainProductProduction",
|
|
190
|
+
"cfDistribution",
|
|
191
|
+
"cfEndOfLifeRecycling",
|
|
192
|
+
],
|
|
193
|
+
article: "Annex XIII 1(c) / Art. 7",
|
|
194
|
+
reason: "Carbon-footprint declaration applies only to rechargeable batteries.",
|
|
195
|
+
decide: (t) => {
|
|
196
|
+
if (t.rechargeable === undefined)
|
|
197
|
+
return "unknown";
|
|
198
|
+
return t.rechargeable ? "applies" : "not_applicable";
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
// Deck (e): recycled content cross-references Art. 8 ("If the battery
|
|
203
|
+
// has external storage only, (e) remains empty"). externalStorageOnly
|
|
204
|
+
// absent ⇒ unknown.
|
|
205
|
+
keys: [
|
|
206
|
+
"recycledContentCobalt",
|
|
207
|
+
"recycledContentLead",
|
|
208
|
+
"recycledContentLithium",
|
|
209
|
+
"recycledContentNickel",
|
|
210
|
+
"recycledContentDocumentation",
|
|
211
|
+
// Pre/post-consumer split-shares (Annex XIII 1(e)) carry the same gate
|
|
212
|
+
// as the combined figures above — external-storage-only batteries are exempt.
|
|
213
|
+
"preConsumerRecycledNickelShare",
|
|
214
|
+
"preConsumerRecycledCobaltShare",
|
|
215
|
+
"preConsumerRecycledLithiumShare",
|
|
216
|
+
"postConsumerRecycledNickelShare",
|
|
217
|
+
"postConsumerRecycledCobaltShare",
|
|
218
|
+
"postConsumerRecycledLithiumShare",
|
|
219
|
+
],
|
|
220
|
+
article: "Annex XIII 1(e) / Art. 8",
|
|
221
|
+
reason: "Recycled-content information does not apply to industrial batteries with exclusively external storage.",
|
|
222
|
+
decide: (t) => {
|
|
223
|
+
// Art. 8(1) exempts only "industrial batteries … except those with
|
|
224
|
+
// exclusively external storage"; EV and LMT batteries owe it regardless.
|
|
225
|
+
if (t.category !== undefined && t.category !== "industrial_gt_2kwh")
|
|
226
|
+
return "applies";
|
|
227
|
+
if (t.externalStorageOnly === undefined)
|
|
228
|
+
return "unknown";
|
|
229
|
+
return t.externalStorageOnly ? "not_applicable" : "applies";
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
// Annex XIII 4(b) / Art. 14: state of certified energy (SOCE) applies
|
|
234
|
+
// only to electric-vehicle (EV) batteries. Resolvable from batteryCategory.
|
|
235
|
+
keys: ["stateOfCertifiedEnergy"],
|
|
236
|
+
article: "Annex XIII 4(b) / Art. 14",
|
|
237
|
+
reason: "State of certified energy (SOCE) applies only to electric-vehicle (EV) batteries.",
|
|
238
|
+
decide: (t) => {
|
|
239
|
+
if (t.category === undefined)
|
|
240
|
+
return "unknown";
|
|
241
|
+
return t.category === "EV" ? "applies" : "not_applicable";
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
// Annex XIII 4(b) / Art. 14 / Annex VII Part A: remaining capacity /
|
|
246
|
+
// power capability, remaining round-trip efficiency (DP 64), self-discharge
|
|
247
|
+
// evolution, pack ohmic resistance, and initial self-discharge rate apply
|
|
248
|
+
// only to SBESS and LMT batteries — NOT EV (which has SOCE instead), and
|
|
249
|
+
// NOT plain industrial batteries that are not stationary storage systems.
|
|
250
|
+
// Art. 14(1) subjects: SBESS + LMT + EV. Annex VII Part A applies to
|
|
251
|
+
// SBESS + LMT. sbessOrLmt() resolves this correctly.
|
|
252
|
+
// NOTE: roundTripEfficiencyFade (DP 58) is "if applicable" in ALL THREE
|
|
253
|
+
// guidance columns and therefore is NOT in this gate — it applies to EV too.
|
|
254
|
+
keys: [
|
|
255
|
+
"remainingCapacity",
|
|
256
|
+
"remainingPowerCapability",
|
|
257
|
+
"remainingRoundTripEfficiency",
|
|
258
|
+
"evolutionOfSelfDischargeRate",
|
|
259
|
+
"currentInternalResistancePack",
|
|
260
|
+
// initialSelfDischargeRate cites Annex VII Part A(4) — same applicability
|
|
261
|
+
// as the rest of the Part A cluster.
|
|
262
|
+
"initialSelfDischargeRate",
|
|
263
|
+
],
|
|
264
|
+
article: "Annex XIII 4(b) / Art. 14 / Annex VII Part A",
|
|
265
|
+
fixHint: SBESS_FIX_HINT,
|
|
266
|
+
reason: "Remaining capacity, remaining power capability, remaining round-trip efficiency, self-discharge evolution, pack ohmic resistance, and initial self-discharge rate (Annex VII Part A) apply only to LMT batteries and stationary battery energy storage systems, not EV or non-stationary industrial batteries.",
|
|
267
|
+
decide: sbessOrLmt,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
// Annex XIII 4(b) / Art. 14 / Annex VII Part B: expected-lifetime
|
|
271
|
+
// parameters — capacity throughput, energy throughput, and date of first
|
|
272
|
+
// service entry — apply only to SBESS and LMT batteries (Annex VII Part B).
|
|
273
|
+
// EV carries SOCE + the capacity-threshold gate instead; a plain industrial
|
|
274
|
+
// battery that is not a stationary storage system does not owe this data.
|
|
275
|
+
// The template already marks these notApplicable for EV via requiredBy;
|
|
276
|
+
// the gate enforces that at the applicability-warning layer too.
|
|
277
|
+
keys: [
|
|
278
|
+
"capacityThroughput",
|
|
279
|
+
"energyThroughput",
|
|
280
|
+
"dateOfServiceEntry",
|
|
281
|
+
],
|
|
282
|
+
article: "Annex XIII 4(b) / Art. 14 / Annex VII Part B",
|
|
283
|
+
fixHint: SBESS_FIX_HINT,
|
|
284
|
+
reason: "Expected-lifetime parameters (Annex VII Part B) apply only to LMT batteries and stationary battery energy storage systems.",
|
|
285
|
+
decide: sbessOrLmt,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
// Phase 1 — rechargeable gate: 12 performance fields that derive from the
|
|
289
|
+
// Art. 10(1) performance document, which only exists for rechargeable batteries.
|
|
290
|
+
//
|
|
291
|
+
// Category awareness: EV and LMT batteries are inherently rechargeable by
|
|
292
|
+
// definition — no flag check is needed. The gate always returns "applies" for
|
|
293
|
+
// those categories, matching the "required" entries in the template's requiredBy
|
|
294
|
+
// map. For industrial_gt_2kwh the flag is decisive: an approved rechargeable=true
|
|
295
|
+
// promotes the conditional template entry to a hard publish block; rechargeable=false
|
|
296
|
+
// means the Art. 10(1) duty does not arise.
|
|
297
|
+
//
|
|
298
|
+
// Note: dynamicEnergyRoundTripEfficiency (DP 57) is "if applicable" for ALL three
|
|
299
|
+
// categories and is NOT in this gate — it carries no requiredBy map at all.
|
|
300
|
+
//
|
|
301
|
+
// Legal basis:
|
|
302
|
+
// Annex XIII 1(j) — expected lifetime, cycle data, performance-fade metrics
|
|
303
|
+
// Annex XIII 1(n) — initial / 50%-cycle-life round-trip efficiency
|
|
304
|
+
// Annex XIII 4(a)(i/ii/iv) — dynamic rated capacity, power, internal resistance
|
|
305
|
+
// Art. 10(1) — performance document for rechargeable industrial >2 kWh,
|
|
306
|
+
// EV, and LMT batteries
|
|
307
|
+
keys: [
|
|
308
|
+
// Static performance (Annex XIII 1(j)/(n) / Art. 10(1))
|
|
309
|
+
"initialEnergyRoundTripEfficiency",
|
|
310
|
+
"roundTripEfficiencyAtHalfCycleLife",
|
|
311
|
+
// Expected lifetime (Annex XIII 4(c) / Art. 10(1))
|
|
312
|
+
"expectedLifetimeCycles",
|
|
313
|
+
"expectedLifetimeYears",
|
|
314
|
+
"expectedLifetimeReferenceConditions",
|
|
315
|
+
// Performance test parameters (Art. 10(1))
|
|
316
|
+
"cRateRelevantCycleLifeTest",
|
|
317
|
+
// State-of-health performance metrics (Art. 10(1) / Annex XIII 4(b) partial)
|
|
318
|
+
"internalResistanceIncrease",
|
|
319
|
+
"capacityFade",
|
|
320
|
+
"powerFade",
|
|
321
|
+
// Dynamic performance data (Annex XIII 4(a)(i/ii/iv)) — updated on status change
|
|
322
|
+
// Note: 4(a)(iii) = dynamicEnergyRoundTripEfficiency is "if applicable" → not gated
|
|
323
|
+
"dynamicRatedCapacity",
|
|
324
|
+
"dynamicPowerCapability",
|
|
325
|
+
"dynamicInternalResistance",
|
|
326
|
+
],
|
|
327
|
+
article: "Annex XIII 1(j)/(n), 4(a)(i/ii/iv), Art. 10(1)",
|
|
328
|
+
reason: "These performance fields come from the Art. 10(1) performance document (rechargeable industrial >2 kWh, EV, LMT). EV and LMT are inherently rechargeable; the flag governs industrial_gt_2kwh only.",
|
|
329
|
+
decide: (t) => {
|
|
330
|
+
// EV and LMT are rechargeable by definition — the flag is irrelevant for them.
|
|
331
|
+
if (t.category === "EV" || t.category === "LMT")
|
|
332
|
+
return "applies";
|
|
333
|
+
// industrial_gt_2kwh: the rechargeable flag is decisive.
|
|
334
|
+
if (t.rechargeable === undefined)
|
|
335
|
+
return "unknown";
|
|
336
|
+
return t.rechargeable ? "applies" : "not_applicable";
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
];
|
|
340
|
+
/** Pre-computed map from field key → the gate governing it. */
|
|
341
|
+
const KEY_TO_GATE = new Map();
|
|
342
|
+
for (const gate of BATTERY_FIELD_GATES) {
|
|
343
|
+
for (const k of gate.keys)
|
|
344
|
+
KEY_TO_GATE.set(k, gate);
|
|
345
|
+
}
|
|
346
|
+
/** Every field key under any gate (the conditional set). */
|
|
347
|
+
export const GATED_FIELD_KEYS = new Set(KEY_TO_GATE.keys());
|
|
348
|
+
/**
|
|
349
|
+
* Applicability for a single battery field key. Ungated keys are always
|
|
350
|
+
* "applies". This is the granular accessor the editor uses per-field.
|
|
351
|
+
*/
|
|
352
|
+
export function fieldApplicability(passport, key) {
|
|
353
|
+
const gate = KEY_TO_GATE.get(key);
|
|
354
|
+
if (!gate)
|
|
355
|
+
return "applies";
|
|
356
|
+
return gate.decide(readTriggers(passport, "battery"));
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Full map for the gated fields only (callers default everything else to
|
|
360
|
+
* "applies"). The verdict engine iterates this to emit findings; the
|
|
361
|
+
* editor reads it once per render and looks up per field.
|
|
362
|
+
*
|
|
363
|
+
* Returns an empty map for non-battery passports — `category` here is the
|
|
364
|
+
* template category, NOT batteryCategory; callers pass it so this is a
|
|
365
|
+
* no-op outside battery and the predicate stays battery-scoped.
|
|
366
|
+
*/
|
|
367
|
+
/** @deprecated Use `categoryFieldApplicability` instead. Kept for backward compat. */
|
|
368
|
+
export function batteryFieldApplicability(passport, category) {
|
|
369
|
+
return categoryFieldApplicability(passport, category);
|
|
370
|
+
}
|
|
371
|
+
/** Look up the gate metadata (article + reason) for a gated field key. */
|
|
372
|
+
export function gateForKey(key) {
|
|
373
|
+
return KEY_TO_GATE.get(key);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Date strings from which each category's passport obligations become mandatory.
|
|
377
|
+
* Before these dates, any gate that carries `mandatoryFrom` returns "unknown"
|
|
378
|
+
* (warning, no hard block). After, the gate's `decide()` result is used as-is.
|
|
379
|
+
*/
|
|
380
|
+
export const DETERGENTS_PASSPORT_MANDATORY_FROM = "2029-09-23";
|
|
381
|
+
export const TOYS_PASSPORT_MANDATORY_FROM = "2030-08-01";
|
|
382
|
+
// ─── Detergents gates (Reg (EU) 2026/405) ────────────────────────────────────
|
|
383
|
+
const DETERGENTS_FIELD_GATES = [
|
|
384
|
+
{
|
|
385
|
+
// Annex VI Part A(i): microorganisms list is required ONLY where micro-organisms
|
|
386
|
+
// are intentionally added to the product. If no micro-organisms are added the
|
|
387
|
+
// field does not apply. `microorganismsAdded` flag (owner-confirmed) resolves this.
|
|
388
|
+
keys: ["microorganisms"],
|
|
389
|
+
article: "Annex VI Part A(i) / Reg (EU) 2026/405",
|
|
390
|
+
reason: "The list of micro-organisms is required only where micro-organisms are intentionally added to the detergent formulation.",
|
|
391
|
+
mandatoryFrom: DETERGENTS_PASSPORT_MANDATORY_FROM,
|
|
392
|
+
decide: (t) => {
|
|
393
|
+
if (t.microorganismsAdded === undefined)
|
|
394
|
+
return "unknown";
|
|
395
|
+
return t.microorganismsAdded ? "applies" : "not_applicable";
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
// Annex VI Part A(h): the full ingredients list for industrial/institutional
|
|
400
|
+
// detergents is required UNLESS the manufacturer has provided a Safety Data Sheet
|
|
401
|
+
// (SDS) for the product. When an SDS is provided the duty is discharged separately.
|
|
402
|
+
keys: ["ingredients"],
|
|
403
|
+
article: "Annex VI Part A(h) / Reg (EU) 2026/405",
|
|
404
|
+
reason: "The full ingredients list for an industrial/institutional detergent is required in the passport unless a Safety Data Sheet covering that information has been provided separately.",
|
|
405
|
+
mandatoryFrom: DETERGENTS_PASSPORT_MANDATORY_FROM,
|
|
406
|
+
decide: (t) => {
|
|
407
|
+
if (t.sdsProvided === undefined)
|
|
408
|
+
return "unknown";
|
|
409
|
+
// SDS provided → the duty is discharged via SDS, not the passport.
|
|
410
|
+
return t.sdsProvided ? "not_applicable" : "applies";
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
];
|
|
414
|
+
// ─── Toys gates (Reg (EU) 2025/2509) ─────────────────────────────────────────
|
|
415
|
+
const TOYS_FIELD_GATES = [
|
|
416
|
+
{
|
|
417
|
+
// Annex VI Part I(l): a list of fragrances to which the toy may expose users
|
|
418
|
+
// is required only when those fragrances are listed under Annex II of
|
|
419
|
+
// Regulation (EC) No 1223/2009 (cosmetics allergens). If no allergen-listed
|
|
420
|
+
// fragrances are present the duty does not arise.
|
|
421
|
+
keys: ["allergenicFragrances"],
|
|
422
|
+
article: "Annex VI Part I(l) / Reg (EU) 2025/2509",
|
|
423
|
+
reason: "A list of allergen-listed fragrances is required in the toy passport only when such fragrances are present in the toy.",
|
|
424
|
+
mandatoryFrom: TOYS_PASSPORT_MANDATORY_FROM,
|
|
425
|
+
decide: (t) => {
|
|
426
|
+
if (t.containsAllergenicFragrances === undefined)
|
|
427
|
+
return "unknown";
|
|
428
|
+
return t.containsAllergenicFragrances ? "applies" : "not_applicable";
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
// Annex VI Part I(h): a statement about which other Union acts apply to the
|
|
433
|
+
// toy is required only where the toy also falls under one of the acts listed
|
|
434
|
+
// in Annex XI to the Regulation, or under Directive 2011/65/EU (RoHS).
|
|
435
|
+
keys: ["replacesDeclarationOfConformity"],
|
|
436
|
+
article: "Annex VI Part I(h) / Reg (EU) 2025/2509",
|
|
437
|
+
reason: "Information on compliance with other applicable Union acts (Annex XI / RoHS) is required only when the toy falls under one of those acts.",
|
|
438
|
+
mandatoryFrom: TOYS_PASSPORT_MANDATORY_FROM,
|
|
439
|
+
decide: (t) => {
|
|
440
|
+
if (t.alsoUnderOtherActs === undefined)
|
|
441
|
+
return "unknown";
|
|
442
|
+
return t.alsoUnderOtherActs ? "applies" : "not_applicable";
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
// Annex VI Part I(j): the notified-body certificate reference is required only
|
|
447
|
+
// when a notified body was involved (i.e. the `notifiedBody` field is filled).
|
|
448
|
+
// This is field-derived (no separate flag): an approved non-empty `notifiedBody`
|
|
449
|
+
// → certificate reference applies; absent/empty → not_applicable.
|
|
450
|
+
keys: ["notifiedBodyCertificateReference"],
|
|
451
|
+
article: "Annex VI Part I(j) / Reg (EU) 2025/2509",
|
|
452
|
+
reason: "The notified-body certificate reference is required only when a notified body was involved in the conformity assessment (indicated by the notifiedBody field being filled).",
|
|
453
|
+
mandatoryFrom: TOYS_PASSPORT_MANDATORY_FROM,
|
|
454
|
+
decide: (t) => {
|
|
455
|
+
// fieldPresent is optional in Triggers (backward compat); treat absent as false.
|
|
456
|
+
const present = t.fieldPresent ? t.fieldPresent("notifiedBody") : false;
|
|
457
|
+
if (present === undefined)
|
|
458
|
+
return "unknown"; // pending_review → warn, no block
|
|
459
|
+
return present ? "applies" : "not_applicable";
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
];
|
|
463
|
+
/**
|
|
464
|
+
* CATEGORY_FIELD_GATES — generalised gate registry, keyed by template category.
|
|
465
|
+
*
|
|
466
|
+
* Phase 1: battery only. Phase 2: detergents and toys added.
|
|
467
|
+
*
|
|
468
|
+
* Callers that need to enumerate gates for a category use this registry.
|
|
469
|
+
* The battery-specific `batteryFieldApplicability` is now a thin wrapper.
|
|
470
|
+
*/
|
|
471
|
+
export const CATEGORY_FIELD_GATES = {
|
|
472
|
+
battery: BATTERY_FIELD_GATES,
|
|
473
|
+
detergents: DETERGENTS_FIELD_GATES,
|
|
474
|
+
toys: TOYS_FIELD_GATES,
|
|
475
|
+
};
|
|
476
|
+
/**
|
|
477
|
+
* Compute the applicability map for all gated fields of any category.
|
|
478
|
+
* Returns {} for categories with no gates (safe to iterate).
|
|
479
|
+
*
|
|
480
|
+
* Replaces the battery-only early return in `batteryFieldApplicability` with a
|
|
481
|
+
* generic path: every category that has gates in CATEGORY_FIELD_GATES is handled,
|
|
482
|
+
* everything else is a no-op. `batteryFieldApplicability` is kept as a
|
|
483
|
+
* backward-compat alias.
|
|
484
|
+
*
|
|
485
|
+
* @param now Optional reference date for `mandatoryFrom` enforcement. Defaults
|
|
486
|
+
* to the current wall clock. Callers should pass a fixed date in tests.
|
|
487
|
+
* When a gate's `mandatoryFrom` is in the future relative to `now`,
|
|
488
|
+
* ALL keys governed by that gate resolve to "unknown" (warning only,
|
|
489
|
+
* never a hard publish block) — keeping the provision from creating
|
|
490
|
+
* obligations before it legally applies.
|
|
491
|
+
*/
|
|
492
|
+
export function categoryFieldApplicability(passport, category, now) {
|
|
493
|
+
const gates = CATEGORY_FIELD_GATES[category];
|
|
494
|
+
if (!gates || gates.length === 0)
|
|
495
|
+
return {};
|
|
496
|
+
const triggers = readTriggers(passport, category);
|
|
497
|
+
const effectiveNow = now ?? new Date();
|
|
498
|
+
const out = {};
|
|
499
|
+
for (const gate of gates) {
|
|
500
|
+
// Date-gate: if the provision has not yet entered into force, return "unknown"
|
|
501
|
+
// for every field in this gate — never a hard block before the law applies.
|
|
502
|
+
if (gate.mandatoryFrom && effectiveNow < new Date(gate.mandatoryFrom)) {
|
|
503
|
+
for (const k of gate.keys)
|
|
504
|
+
out[k] = "unknown";
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const verdict = gate.decide(triggers);
|
|
508
|
+
for (const k of gate.keys)
|
|
509
|
+
out[k] = verdict;
|
|
510
|
+
}
|
|
511
|
+
return out;
|
|
512
|
+
}
|
|
513
|
+
//# sourceMappingURL=battery-applicability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"battery-applicability.js","sourceRoot":"","sources":["../../src/vendor/battery-applicability.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generalised condition-profile accessor.
|
|
3
|
+
*
|
|
4
|
+
* `conditionProfile` (the new field) wins when present; falls back to the
|
|
5
|
+
* legacy `batteryProfile` block, adapting it to the `ConditionFlag` shape
|
|
6
|
+
* (audit defaults to []). This mirrors the `gs1` → `identifier` precedence
|
|
7
|
+
* pattern: old data stays readable without a migration, and new writes land
|
|
8
|
+
* in the canonical location.
|
|
9
|
+
*
|
|
10
|
+
* **Use `resolveConditionProfile` everywhere** instead of reading
|
|
11
|
+
* `passport.batteryProfile` directly — this is the single source of truth
|
|
12
|
+
* for "what condition flags has a reviewer set?".
|
|
13
|
+
*
|
|
14
|
+
* Pure + IO-free → unit-tested in tests/compliance/condition-profile.test.ts.
|
|
15
|
+
*/
|
|
16
|
+
import type { Passport, ConditionProfile } from "@tracepass/dpp-types";
|
|
17
|
+
/**
|
|
18
|
+
* Return the effective condition profile for a passport, regardless of which
|
|
19
|
+
* storage field holds the data.
|
|
20
|
+
*
|
|
21
|
+
* - If `passport.conditionProfile` is present (even as `{}`), return it as-is.
|
|
22
|
+
* An empty conditionProfile means "all flags cleared" — it must NOT fall back
|
|
23
|
+
* to batteryProfile, or clearing the last flag would resurrect legacy data.
|
|
24
|
+
* - Otherwise, adapt `passport.batteryProfile` (the legacy shape, which lacks
|
|
25
|
+
* per-flag audit arrays) to the `ConditionProfile` shape with `audit: []`.
|
|
26
|
+
* - If neither is set, return an empty object.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveConditionProfile(passport: Pick<Passport, "conditionProfile" | "batteryProfile">): ConditionProfile;
|
|
29
|
+
//# sourceMappingURL=condition-profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"condition-profile.d.ts","sourceRoot":"","sources":["../../src/vendor/condition-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEvE;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,kBAAkB,GAAG,gBAAgB,CAAC,GAAG,gBAAgB,CAkBzH"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generalised condition-profile accessor.
|
|
3
|
+
*
|
|
4
|
+
* `conditionProfile` (the new field) wins when present; falls back to the
|
|
5
|
+
* legacy `batteryProfile` block, adapting it to the `ConditionFlag` shape
|
|
6
|
+
* (audit defaults to []). This mirrors the `gs1` → `identifier` precedence
|
|
7
|
+
* pattern: old data stays readable without a migration, and new writes land
|
|
8
|
+
* in the canonical location.
|
|
9
|
+
*
|
|
10
|
+
* **Use `resolveConditionProfile` everywhere** instead of reading
|
|
11
|
+
* `passport.batteryProfile` directly — this is the single source of truth
|
|
12
|
+
* for "what condition flags has a reviewer set?".
|
|
13
|
+
*
|
|
14
|
+
* Pure + IO-free → unit-tested in tests/compliance/condition-profile.test.ts.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Return the effective condition profile for a passport, regardless of which
|
|
18
|
+
* storage field holds the data.
|
|
19
|
+
*
|
|
20
|
+
* - If `passport.conditionProfile` is present (even as `{}`), return it as-is.
|
|
21
|
+
* An empty conditionProfile means "all flags cleared" — it must NOT fall back
|
|
22
|
+
* to batteryProfile, or clearing the last flag would resurrect legacy data.
|
|
23
|
+
* - Otherwise, adapt `passport.batteryProfile` (the legacy shape, which lacks
|
|
24
|
+
* per-flag audit arrays) to the `ConditionProfile` shape with `audit: []`.
|
|
25
|
+
* - If neither is set, return an empty object.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveConditionProfile(passport) {
|
|
28
|
+
if (passport.conditionProfile !== undefined) {
|
|
29
|
+
return passport.conditionProfile;
|
|
30
|
+
}
|
|
31
|
+
const bp = passport.batteryProfile;
|
|
32
|
+
if (!bp)
|
|
33
|
+
return {};
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [key, flag] of Object.entries(bp)) {
|
|
36
|
+
if (flag != null) {
|
|
37
|
+
out[key] = {
|
|
38
|
+
value: flag.value,
|
|
39
|
+
status: flag.status,
|
|
40
|
+
source: flag.source,
|
|
41
|
+
audit: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=condition-profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"condition-profile.js","sourceRoot":"","sources":["../../src/vendor/condition-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,QAA+D;IACrG,IAAI,QAAQ,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QAC5C,OAAO,QAAQ,CAAC,gBAAgB,CAAC;IACnC,CAAC;IACD,MAAM,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC;IACnC,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,CAAC;IACnB,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,CAA2C,EAAE,CAAC;QACvF,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjB,GAAG,CAAC,GAAG,CAAC,GAAG;gBACT,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,KAAK,EAAE,EAAE;aACV,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -19,24 +19,35 @@
|
|
|
19
19
|
* and commit `mirror-manifest.json` with the code.
|
|
20
20
|
*/
|
|
21
21
|
import type { Passport, Template, TemplateField } from "@tracepass/dpp-types";
|
|
22
|
+
/** Applicability value returned by the condition-gate engine. */
|
|
23
|
+
export type GateApplicability = "applies" | "not_applicable" | "unknown";
|
|
22
24
|
export interface PublishCheck {
|
|
23
25
|
ready: boolean;
|
|
24
26
|
/** Field keys that have no value set. */
|
|
25
27
|
missingFields: string[];
|
|
26
28
|
/** Field keys with a value but status != approved. */
|
|
27
29
|
unapprovedFields: string[];
|
|
30
|
+
/**
|
|
31
|
+
* Field keys with `requiredBy[category] === "conditional"` whose gate
|
|
32
|
+
* resolved `"applies"` (from an APPROVED flag) and the field is empty.
|
|
33
|
+
* This is a HARD publish block — stricter than a soft gap, because the
|
|
34
|
+
* owner has confirmed the condition holds.
|
|
35
|
+
*/
|
|
36
|
+
conditionalMissingFields: string[];
|
|
28
37
|
/** Short human reason suitable for a tooltip or toast — undefined when ready. */
|
|
29
38
|
reason?: string;
|
|
30
39
|
/**
|
|
31
40
|
* Distinguishes WHY the passport isn't ready:
|
|
32
|
-
* - "hard"
|
|
33
|
-
*
|
|
34
|
-
* - "gap"
|
|
35
|
-
*
|
|
36
|
-
*
|
|
41
|
+
* - "hard" → structural blocker that can't be bypassed
|
|
42
|
+
* (already-published, wrong status, template missing).
|
|
43
|
+
* - "gap" → required fields are missing or unapproved. The UI
|
|
44
|
+
* can offer to publish anyway with an explicit "I
|
|
45
|
+
* understand the risk" acknowledgement.
|
|
46
|
+
* - "conditional" → a condition-flag gate confirmed a duty applies and
|
|
47
|
+
* the field is empty. Hard block, same as "hard".
|
|
37
48
|
* - `undefined` when ready === true.
|
|
38
49
|
*/
|
|
39
|
-
blockerType?: "hard" | "gap";
|
|
50
|
+
blockerType?: "hard" | "gap" | "conditional";
|
|
40
51
|
}
|
|
41
52
|
/**
|
|
42
53
|
* Resolves whether a template field is effectively required for a specific
|
|
@@ -100,6 +111,31 @@ export declare function effectiveRequiredFor(templateCategory: string, tf: Pick<
|
|
|
100
111
|
* absence of information is never grounds for asserting non-applicability.
|
|
101
112
|
*/
|
|
102
113
|
export declare function notApplicableForCategory(tf: Pick<TemplateField, "validation">, category: string | undefined): boolean;
|
|
114
|
+
/** The field-level findings returned by `evaluateFieldRequirements`. */
|
|
115
|
+
export interface FieldRequirements {
|
|
116
|
+
missingFields: string[];
|
|
117
|
+
unapprovedFields: string[];
|
|
118
|
+
conditionalMissingFields: string[];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Evaluate field-level requirements for a passport against its template,
|
|
122
|
+
* status-independently. Called by both `checkPublishReady` (which adds
|
|
123
|
+
* status early-returns) and `evaluateCompliance` (which must run even on
|
|
124
|
+
* published passports so live DPPs are re-evaluated after a template or
|
|
125
|
+
* flag change).
|
|
126
|
+
*
|
|
127
|
+
* Returns three buckets:
|
|
128
|
+
* - `missingFields` — statically required, no value set
|
|
129
|
+
* - `unapprovedFields` — statically required, value set but not approved
|
|
130
|
+
* - `conditionalMissingFields` — gate-confirmed conditional duty, absent
|
|
131
|
+
* OR present-but-unapproved (hard block)
|
|
132
|
+
*
|
|
133
|
+
* `applicabilityMap` is pre-computed by the caller via
|
|
134
|
+
* `categoryFieldApplicability(passport, template.category)`. Pass `{}` for
|
|
135
|
+
* non-battery categories — the gate branch is then a no-op. Keeping it
|
|
136
|
+
* injected preserves the IO-free/pure contract of this function.
|
|
137
|
+
*/
|
|
138
|
+
export declare function evaluateFieldRequirements(passport: Passport, template: Template, applicabilityMap?: Record<string, GateApplicability>): FieldRequirements;
|
|
103
139
|
/**
|
|
104
140
|
* Returns whether `passport` satisfies every publish precondition given
|
|
105
141
|
* its `template`. A passport is publish-ready iff:
|
|
@@ -107,9 +143,19 @@ export declare function notApplicableForCategory(tf: Pick<TemplateField, "valida
|
|
|
107
143
|
* states return ready:false with a specific reason),
|
|
108
144
|
* - every template field whose effective required flag (resolved via
|
|
109
145
|
* `effectiveRequired` — which consults `requiredBy` when present) is
|
|
110
|
-
* true has a non-empty value AND status === "approved"
|
|
146
|
+
* true has a non-empty value AND status === "approved",
|
|
147
|
+
* - every `requiredBy[category] === "conditional"` field whose gate
|
|
148
|
+
* resolves `"applies"` (from an APPROVED condition flag) also has a
|
|
149
|
+
* non-empty value AND status === "approved" (hard block, same severity
|
|
150
|
+
* as a structural blocker).
|
|
111
151
|
*
|
|
112
152
|
* `undefined` template means we can't evaluate — treat as not-ready.
|
|
153
|
+
*
|
|
154
|
+
* @param applicabilityMap Pre-computed gate-applicability map (field key →
|
|
155
|
+
* "applies" | "not_applicable" | "unknown"). Computed by the caller using
|
|
156
|
+
* `categoryFieldApplicability(passport, template.category)`. Pass `{}` or
|
|
157
|
+
* omit for non-battery passports (no change in behaviour). Keeping it
|
|
158
|
+
* injected preserves the IO-free/pure contract of this function.
|
|
113
159
|
*/
|
|
114
|
-
export declare function checkPublishReady(passport: Passport, template: Template | undefined): PublishCheck;
|
|
160
|
+
export declare function checkPublishReady(passport: Passport, template: Template | undefined, applicabilityMap?: Record<string, GateApplicability>): PublishCheck;
|
|
115
161
|
//# sourceMappingURL=publish-gate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish-gate.d.ts","sourceRoot":"","sources":["../../src/vendor/publish-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI9E,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,yCAAyC;IACzC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB
|
|
1
|
+
{"version":3,"file":"publish-gate.d.ts","sourceRoot":"","sources":["../../src/vendor/publish-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI9E,iEAAiE;AACjE,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAAC;AAEzE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,yCAAyC;IACzC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;;OAKG;IACH,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,aAAa,CAAC;CAC9C;AAQD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,EACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAUT;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,EAAE,EAAE,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,EACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAKT;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,gBAAgB,EAAE,MAAM,EACxB,EAAE,EAAE,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,EACrC,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAIT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,EACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAIT;AAED,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,wBAAwB,EAAE,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAM,GACvD,iBAAiB,CAmEnB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAM,GACvD,YAAY,CAoEd"}
|
|
@@ -110,6 +110,87 @@ export function notApplicableForCategory(tf, category) {
|
|
|
110
110
|
return false;
|
|
111
111
|
return requiredBy[category] === "notApplicable";
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Evaluate field-level requirements for a passport against its template,
|
|
115
|
+
* status-independently. Called by both `checkPublishReady` (which adds
|
|
116
|
+
* status early-returns) and `evaluateCompliance` (which must run even on
|
|
117
|
+
* published passports so live DPPs are re-evaluated after a template or
|
|
118
|
+
* flag change).
|
|
119
|
+
*
|
|
120
|
+
* Returns three buckets:
|
|
121
|
+
* - `missingFields` — statically required, no value set
|
|
122
|
+
* - `unapprovedFields` — statically required, value set but not approved
|
|
123
|
+
* - `conditionalMissingFields` — gate-confirmed conditional duty, absent
|
|
124
|
+
* OR present-but-unapproved (hard block)
|
|
125
|
+
*
|
|
126
|
+
* `applicabilityMap` is pre-computed by the caller via
|
|
127
|
+
* `categoryFieldApplicability(passport, template.category)`. Pass `{}` for
|
|
128
|
+
* non-battery categories — the gate branch is then a no-op. Keeping it
|
|
129
|
+
* injected preserves the IO-free/pure contract of this function.
|
|
130
|
+
*/
|
|
131
|
+
export function evaluateFieldRequirements(passport, template, applicabilityMap = {}) {
|
|
132
|
+
// The passport's sub-category, read from the field the template category
|
|
133
|
+
// names (battery: batteryCategory, fmcg: productSubcategory, detergents:
|
|
134
|
+
// detergentUserType; core SUBCATEGORY_FIELD), so effectiveRequired can
|
|
135
|
+
// resolve `requiredBy`. Unset → undefined → the plain `required` flag.
|
|
136
|
+
const category = passportSubcategory(template.category, passport.fields);
|
|
137
|
+
const missingFields = [];
|
|
138
|
+
const unapprovedFields = [];
|
|
139
|
+
const conditionalMissingFields = [];
|
|
140
|
+
for (const tf of template.fields) {
|
|
141
|
+
// `anticipated` fields rest on a delegated act that has not been adopted,
|
|
142
|
+
// so no instrument in force compels the data and their absence cannot block
|
|
143
|
+
// publication. Skip them before the required check.
|
|
144
|
+
if (tf.validation.anticipated)
|
|
145
|
+
continue;
|
|
146
|
+
const f = passport.fields[tf.key];
|
|
147
|
+
// ── Gate-aware conditional check ────────────────────────────────────────
|
|
148
|
+
// A field with requiredBy[category] === "conditional" is normally not
|
|
149
|
+
// required. When a gate's APPROVED flag confirms the condition applies,
|
|
150
|
+
// the field becomes required and its absence is a HARD publish block.
|
|
151
|
+
// An unapproved value (pending_review) is also a hard block — consistent
|
|
152
|
+
// with the owner's decision that conditional duties are non-bypassable.
|
|
153
|
+
// "unknown" (absent or pending_review FLAG) → skip here; the rules engine
|
|
154
|
+
// emits an unverifiable_conditional warning via BAT-APP but never blocks.
|
|
155
|
+
const rb = tf.validation.requiredBy;
|
|
156
|
+
if (applicabilityMap[tf.key] === "applies" &&
|
|
157
|
+
(
|
|
158
|
+
// Path 1: field has a per-subcategory requiredBy map and the current
|
|
159
|
+
// subcategory is "conditional" — the gate's "applies" verdict promotes
|
|
160
|
+
// the conditional entry to a hard block (battery, detergents pattern).
|
|
161
|
+
(rb && category !== undefined && rb[category] === "conditional") ||
|
|
162
|
+
// Path 2: no requiredBy map at all and field is not statically required.
|
|
163
|
+
// Used for categories whose gated fields carry no subcategory (toys) —
|
|
164
|
+
// the gate alone decides whether the duty arises; when it says "applies"
|
|
165
|
+
// the field becomes mandatory regardless of the subcategory value.
|
|
166
|
+
(!tf.validation.required && !rb))) {
|
|
167
|
+
if (isFieldAbsent(f)) {
|
|
168
|
+
conditionalMissingFields.push(tf.key);
|
|
169
|
+
}
|
|
170
|
+
else if (f.status !== "approved") {
|
|
171
|
+
// Field is present but not yet confirmed — same hard block as absent,
|
|
172
|
+
// because an unreviewed value satisfies neither the owner nor the law.
|
|
173
|
+
conditionalMissingFields.push(tf.key);
|
|
174
|
+
}
|
|
175
|
+
// Don't fall through to the static required check — the gate owns this
|
|
176
|
+
// field's requirement for this passport.
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
// ── Static required check ────────────────────────────────────────────────
|
|
180
|
+
if (!effectiveRequiredFor(template.category, tf, category))
|
|
181
|
+
continue;
|
|
182
|
+
// Shared helper: `[]` stays a valid answer ("none apply"), while a
|
|
183
|
+
// whitespace-only string now counts as missing — the inline `=== ""` it
|
|
184
|
+
// replaces let " " satisfy a required field and publish a blank.
|
|
185
|
+
if (isFieldAbsent(f)) {
|
|
186
|
+
missingFields.push(tf.key);
|
|
187
|
+
}
|
|
188
|
+
else if (f.status !== "approved") {
|
|
189
|
+
unapprovedFields.push(tf.key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return { missingFields, unapprovedFields, conditionalMissingFields };
|
|
193
|
+
}
|
|
113
194
|
/**
|
|
114
195
|
* Returns whether `passport` satisfies every publish precondition given
|
|
115
196
|
* its `template`. A passport is publish-ready iff:
|
|
@@ -117,16 +198,27 @@ export function notApplicableForCategory(tf, category) {
|
|
|
117
198
|
* states return ready:false with a specific reason),
|
|
118
199
|
* - every template field whose effective required flag (resolved via
|
|
119
200
|
* `effectiveRequired` — which consults `requiredBy` when present) is
|
|
120
|
-
* true has a non-empty value AND status === "approved"
|
|
201
|
+
* true has a non-empty value AND status === "approved",
|
|
202
|
+
* - every `requiredBy[category] === "conditional"` field whose gate
|
|
203
|
+
* resolves `"applies"` (from an APPROVED condition flag) also has a
|
|
204
|
+
* non-empty value AND status === "approved" (hard block, same severity
|
|
205
|
+
* as a structural blocker).
|
|
121
206
|
*
|
|
122
207
|
* `undefined` template means we can't evaluate — treat as not-ready.
|
|
208
|
+
*
|
|
209
|
+
* @param applicabilityMap Pre-computed gate-applicability map (field key →
|
|
210
|
+
* "applies" | "not_applicable" | "unknown"). Computed by the caller using
|
|
211
|
+
* `categoryFieldApplicability(passport, template.category)`. Pass `{}` or
|
|
212
|
+
* omit for non-battery passports (no change in behaviour). Keeping it
|
|
213
|
+
* injected preserves the IO-free/pure contract of this function.
|
|
123
214
|
*/
|
|
124
|
-
export function checkPublishReady(passport, template) {
|
|
215
|
+
export function checkPublishReady(passport, template, applicabilityMap = {}) {
|
|
125
216
|
if (passport.status === "published") {
|
|
126
217
|
return {
|
|
127
218
|
ready: false,
|
|
128
219
|
missingFields: [],
|
|
129
220
|
unapprovedFields: [],
|
|
221
|
+
conditionalMissingFields: [],
|
|
130
222
|
reason: "Already published",
|
|
131
223
|
blockerType: "hard",
|
|
132
224
|
};
|
|
@@ -136,6 +228,7 @@ export function checkPublishReady(passport, template) {
|
|
|
136
228
|
ready: false,
|
|
137
229
|
missingFields: [],
|
|
138
230
|
unapprovedFields: [],
|
|
231
|
+
conditionalMissingFields: [],
|
|
139
232
|
reason: `Cannot publish from status ${passport.status}`,
|
|
140
233
|
blockerType: "hard",
|
|
141
234
|
};
|
|
@@ -145,50 +238,38 @@ export function checkPublishReady(passport, template) {
|
|
|
145
238
|
ready: false,
|
|
146
239
|
missingFields: [],
|
|
147
240
|
unapprovedFields: [],
|
|
241
|
+
conditionalMissingFields: [],
|
|
148
242
|
reason: "Template missing",
|
|
149
243
|
blockerType: "hard",
|
|
150
244
|
};
|
|
151
245
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// publication. Skip them before the required check.
|
|
163
|
-
if (tf.validation.anticipated)
|
|
164
|
-
continue;
|
|
165
|
-
if (!effectiveRequiredFor(template.category, tf, category))
|
|
166
|
-
continue;
|
|
167
|
-
const f = passport.fields[tf.key];
|
|
168
|
-
// Shared helper: `[]` stays a valid answer ("none apply"), while a
|
|
169
|
-
// whitespace-only string now counts as missing — the inline `=== ""` it
|
|
170
|
-
// replaces let " " satisfy a required field and publish a blank.
|
|
171
|
-
if (isFieldAbsent(f)) {
|
|
172
|
-
missingFields.push(tf.key);
|
|
173
|
-
}
|
|
174
|
-
else if (f.status !== "approved") {
|
|
175
|
-
unapprovedFields.push(tf.key);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (missingFields.length === 0 && unapprovedFields.length === 0) {
|
|
179
|
-
return { ready: true, missingFields: [], unapprovedFields: [] };
|
|
246
|
+
const { missingFields, unapprovedFields, conditionalMissingFields } = evaluateFieldRequirements(passport, template, applicabilityMap);
|
|
247
|
+
if (missingFields.length === 0 &&
|
|
248
|
+
unapprovedFields.length === 0 &&
|
|
249
|
+
conditionalMissingFields.length === 0) {
|
|
250
|
+
return {
|
|
251
|
+
ready: true,
|
|
252
|
+
missingFields: [],
|
|
253
|
+
unapprovedFields: [],
|
|
254
|
+
conditionalMissingFields: [],
|
|
255
|
+
};
|
|
180
256
|
}
|
|
181
257
|
const parts = [];
|
|
258
|
+
if (conditionalMissingFields.length > 0)
|
|
259
|
+
parts.push(`${conditionalMissingFields.length} conditional-required missing`);
|
|
182
260
|
if (missingFields.length > 0)
|
|
183
261
|
parts.push(`${missingFields.length} missing`);
|
|
184
262
|
if (unapprovedFields.length > 0)
|
|
185
263
|
parts.push(`${unapprovedFields.length} not approved`);
|
|
264
|
+
// Hard block when any conditional-required field is missing; soft gap otherwise.
|
|
265
|
+
const blockerType = conditionalMissingFields.length > 0 ? "conditional" : "gap";
|
|
186
266
|
return {
|
|
187
267
|
ready: false,
|
|
188
268
|
missingFields,
|
|
189
269
|
unapprovedFields,
|
|
270
|
+
conditionalMissingFields,
|
|
190
271
|
reason: `Required fields: ${parts.join(", ")}`,
|
|
191
|
-
blockerType
|
|
272
|
+
blockerType,
|
|
192
273
|
};
|
|
193
274
|
}
|
|
194
275
|
//# sourceMappingURL=publish-gate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish-gate.js","sourceRoot":"","sources":["../../src/vendor/publish-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"publish-gate.js","sourceRoot":"","sources":["../../src/vendor/publish-gate.ts"],"names":[],"mappings":"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"}
|
package/dist/verdict.d.ts
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* Pure and IO-free: no database, no network, no clock.
|
|
10
10
|
*
|
|
11
11
|
* Composition:
|
|
12
|
-
* static tier — required fields
|
|
13
|
-
*
|
|
12
|
+
* static tier — evaluateFieldRequirements (required fields,
|
|
13
|
+
* status-independent) + required economic operators
|
|
14
|
+
* (required-roles) + format validation
|
|
14
15
|
* conditional tier — CONDITIONAL_RULES[category] + CROSS_CUTTING_RULES
|
|
15
16
|
* coverage — "evaluated" if the category has conditional rules,
|
|
16
17
|
* else "static-only"
|
package/dist/verdict.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verdict.d.ts","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"verdict.d.ts","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAiB,MAAM,sBAAsB,CAAC;AAM9E,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAiEpB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,QAAQ,EAAE,MAAM;AAChB,kFAAkF;AAClF,GAAG,GAAE,IAAiB,GACrB,gBAAgB,CA6IlB"}
|
package/dist/verdict.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* Pure and IO-free: no database, no network, no clock.
|
|
10
10
|
*
|
|
11
11
|
* Composition:
|
|
12
|
-
* static tier — required fields
|
|
13
|
-
*
|
|
12
|
+
* static tier — evaluateFieldRequirements (required fields,
|
|
13
|
+
* status-independent) + required economic operators
|
|
14
|
+
* (required-roles) + format validation
|
|
14
15
|
* conditional tier — CONDITIONAL_RULES[category] + CROSS_CUTTING_RULES
|
|
15
16
|
* coverage — "evaluated" if the category has conditional rules,
|
|
16
17
|
* else "static-only"
|
|
@@ -18,9 +19,10 @@
|
|
|
18
19
|
* Each finding cites the article of the instrument that mandates it, taken
|
|
19
20
|
* from the template field's own `regulationRef`.
|
|
20
21
|
*/
|
|
21
|
-
import {
|
|
22
|
+
import { evaluateFieldRequirements } from "./vendor/publish-gate.js";
|
|
22
23
|
import { getPartyRoles } from "./vendor/required-roles.js";
|
|
23
24
|
import { derivePassportCounts } from "./vendor/counts.js";
|
|
25
|
+
import { categoryFieldApplicability } from "./vendor/battery-applicability.js";
|
|
24
26
|
import { CONDITIONAL_RULES, CROSS_CUTTING_RULES } from "./rules.js";
|
|
25
27
|
/** Validate a filled value against a template field's format rules.
|
|
26
28
|
* Returns a finding (warning) on mismatch, or null when clean. Empty
|
|
@@ -113,11 +115,17 @@ now = new Date()) {
|
|
|
113
115
|
const critical = [];
|
|
114
116
|
const warnings = [];
|
|
115
117
|
const checkedRules = [];
|
|
116
|
-
// ── Static tier 1: required fields
|
|
118
|
+
// ── Static tier 1: required fields ─────────────────────────────────
|
|
117
119
|
checkedRules.push("static:required-fields");
|
|
118
|
-
|
|
120
|
+
// Compute the gate-applicability map for conditional duties whose APPROVED
|
|
121
|
+
// flag confirms the condition holds. evaluateFieldRequirements is called
|
|
122
|
+
// directly (not via checkPublishReady) so published passports are not
|
|
123
|
+
// short-circuited — live DPPs must be re-evaluated immediately when a
|
|
124
|
+
// field or flag changes.
|
|
125
|
+
const applicabilityMap = categoryFieldApplicability(passport, category);
|
|
126
|
+
const { missingFields, unapprovedFields, conditionalMissingFields } = evaluateFieldRequirements(passport, template, applicabilityMap);
|
|
119
127
|
const fieldRef = (key) => template.fields.find((f) => f.key === key)?.regulationRef;
|
|
120
|
-
for (const key of
|
|
128
|
+
for (const key of missingFields) {
|
|
121
129
|
const ref = fieldRef(key);
|
|
122
130
|
critical.push({
|
|
123
131
|
type: "missing_field",
|
|
@@ -129,7 +137,7 @@ now = new Date()) {
|
|
|
129
137
|
fix: `Provide a value for ${key}.`,
|
|
130
138
|
});
|
|
131
139
|
}
|
|
132
|
-
for (const key of
|
|
140
|
+
for (const key of unapprovedFields) {
|
|
133
141
|
critical.push({
|
|
134
142
|
type: "unapproved_field",
|
|
135
143
|
severity: "critical",
|
|
@@ -138,6 +146,33 @@ now = new Date()) {
|
|
|
138
146
|
fix: `Review and approve ${key}.`,
|
|
139
147
|
});
|
|
140
148
|
}
|
|
149
|
+
// Gate-confirmed conditional duties: the APPROVED flag makes the duty concrete
|
|
150
|
+
// and the field must be present + approved. Same hard-block severity as a
|
|
151
|
+
// statically-required field — the owner confirmed the condition holds.
|
|
152
|
+
// "gate:conditional" is pushed when the category has gates (non-empty
|
|
153
|
+
// applicabilityMap) so the caller knows whether gates were evaluated.
|
|
154
|
+
if (Object.keys(applicabilityMap).length > 0) {
|
|
155
|
+
checkedRules.push("gate:conditional");
|
|
156
|
+
}
|
|
157
|
+
for (const key of conditionalMissingFields) {
|
|
158
|
+
const ref = fieldRef(key);
|
|
159
|
+
const f = passport.fields[key];
|
|
160
|
+
const isAbsent = f == null || f.value === null || f.value === undefined || f.value === "";
|
|
161
|
+
critical.push({
|
|
162
|
+
type: "missing_field",
|
|
163
|
+
severity: "critical",
|
|
164
|
+
ruleId: "gate:conditional",
|
|
165
|
+
target: key,
|
|
166
|
+
regulation: ref ? template.regulation?.number : undefined,
|
|
167
|
+
article: ref?.article ?? undefined,
|
|
168
|
+
why: isAbsent
|
|
169
|
+
? `Conditional field "${key}" is required because an approved condition flag confirms the duty applies, but the field has no value.`
|
|
170
|
+
: `Conditional field "${key}" is required because an approved condition flag confirms the duty applies, but the value is not yet approved.`,
|
|
171
|
+
fix: isAbsent
|
|
172
|
+
? `Provide and approve a value for ${key}.`
|
|
173
|
+
: `Approve the value for ${key}.`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
141
176
|
// ── Static tier 2: required economic-operator parties ──────────────
|
|
142
177
|
checkedRules.push("static:required-parties");
|
|
143
178
|
const roles = getPartyRoles(category);
|
package/dist/verdict.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tracepass/dpp-validate",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Evaluate an EU Digital Product Passport against its category field-spec
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "Evaluate an EU Digital Product Passport against its category field-spec \u2014 a three-tier compliance verdict with regulation-cited findings. Pure functions, no third-party runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"digital-product-passport",
|
|
7
7
|
"dpp",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"clean": "rm -rf dist"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@tracepass/dpp-types": "^0.
|
|
48
|
+
"@tracepass/dpp-types": "^0.14.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"typescript": "^5.6.3"
|