@pdtf/schemas 3.6.0-4 → 3.6.0-41

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.
Files changed (64) hide show
  1. package/.claude/plans/not-seeing-associated-costs.md +99 -0
  2. package/.claude/settings.local.json +22 -3
  3. package/README.md +52 -33
  4. package/docs/sef25-extensions-ui-spec.md +355 -0
  5. package/index.js +130 -66
  6. package/package.json +4 -2
  7. package/src/examples/v3/exampleTransaction.json +3 -3
  8. package/src/schemas/v3/combined.json +7651 -1672
  9. package/src/schemas/v3/compactSkeleton.txt +365 -14
  10. package/src/schemas/v3/overlays/baspi4.json +163 -18
  11. package/src/schemas/v3/overlays/baspi5.json +168 -20
  12. package/src/schemas/v3/overlays/extensions/as.json +4 -2
  13. package/src/schemas/v3/overlays/extensions/dk.json +32 -0
  14. package/src/schemas/v3/overlays/extensions/dr.json +9 -9
  15. package/src/schemas/v3/overlays/extensions/er.json +4 -2
  16. package/src/schemas/v3/overlays/extensions/fd.json +6 -3
  17. package/src/schemas/v3/overlays/extensions/hi.json +6 -3
  18. package/src/schemas/v3/overlays/extensions/hs.json +4 -2
  19. package/src/schemas/v3/overlays/extensions/ic.json +59 -0
  20. package/src/schemas/v3/overlays/extensions/jk.json +28 -8
  21. package/src/schemas/v3/overlays/extensions/la.json +4 -2
  22. package/src/schemas/v3/overlays/extensions/lc.json +75 -0
  23. package/src/schemas/v3/overlays/extensions/ma.json +6 -3
  24. package/src/schemas/v3/overlays/extensions/mc.json +4 -2
  25. package/src/schemas/v3/overlays/extensions/mi.json +69 -0
  26. package/src/schemas/v3/overlays/extensions/nd.json +43 -0
  27. package/src/schemas/v3/overlays/extensions/oa.json +4 -2
  28. package/src/schemas/v3/overlays/extensions/oc.json +4 -2
  29. package/src/schemas/v3/overlays/extensions/pc.json +37 -0
  30. package/src/schemas/v3/overlays/extensions/ph.json +194 -0
  31. package/src/schemas/v3/overlays/extensions/rw.json +68 -0
  32. package/src/schemas/v3/overlays/extensions/sb.json +4 -2
  33. package/src/schemas/v3/overlays/extensions/sc.json +129 -0
  34. package/src/schemas/v3/overlays/extensions/sd.json +63 -0
  35. package/src/schemas/v3/overlays/extensions/sf.json +4 -2
  36. package/src/schemas/v3/overlays/extensions/sl.json +4 -2
  37. package/src/schemas/v3/overlays/extensions/tf.json +6 -3
  38. package/src/schemas/v3/overlays/extensions/tr.json +73 -0
  39. package/src/schemas/v3/overlays/extensions/wg.json +350 -0
  40. package/src/schemas/v3/overlays/fme1.json +16 -14
  41. package/src/schemas/v3/overlays/lpe1.json +60 -30
  42. package/src/schemas/v3/overlays/nts.json +68 -1
  43. package/src/schemas/v3/overlays/nts2.json +68 -4
  44. package/src/schemas/v3/overlays/ntsl.json +68 -1
  45. package/src/schemas/v3/overlays/ntsl2.json +68 -4
  46. package/src/schemas/v3/overlays/piq.json +38 -1
  47. package/src/schemas/v3/overlays/rds.json +118 -6
  48. package/src/schemas/v3/overlays/sef25.json +1067 -0
  49. package/src/schemas/v3/overlays/ta6.json +46 -16
  50. package/src/schemas/v3/overlays/ta6ed6.json +6498 -0
  51. package/src/schemas/v3/overlays/ta7.json +70 -6
  52. package/src/schemas/v3/overlays/ta7ed5.json +1539 -0
  53. package/src/schemas/v3/pdtf-transaction.json +4361 -676
  54. package/src/schemas/v3/skeleton.json +505 -36
  55. package/src/tests/v3/buyerCircumstances.test.js +543 -0
  56. package/src/tests/v3/extensionOverlays.test.js +911 -0
  57. package/src/tests/v3/offers.test.js +37 -40
  58. package/src/tests/v3/patternProperties.test.js +149 -30
  59. package/src/tests/v3/ta6ed6.test.js +2317 -0
  60. package/src/tests/v3/ta7ed5.test.js +567 -0
  61. package/src/tests/v3/transactionSchema.test.js +31 -0
  62. package/src/utils/extractExtensionOverlays.js +216 -31
  63. package/src/utils/extractOverlay.js +88 -23
  64. package/src/utils/pathSkeleton.js +1 -1
@@ -0,0 +1,99 @@
1
+ # Plan: associatedCost amount field not appearing for water/drainage
2
+
3
+ ## Context
4
+
5
+ **Reported symptom**: even with the relevant SEF25 overlay extension loaded, the `amount` input does not appear under the water and drainage *associated costs* sections when a payment frequency like "Per month" or "Per year" is selected.
6
+
7
+ **Are the fields correctly ref'd?** Yes — the `sef25Ref` / `ntsRef` codes line up between base and overlay:
8
+
9
+ | Field path | base `combined.json` | `sef25.json` | `extensions/sc.json` |
10
+ | --- | --- | --- | --- |
11
+ | water `associatedCost` | `sef25Ref U1.1` | `U1.1` | `ntsRef U1.1` |
12
+ | water `frequency` | `sef25Ref U1.1.2` | `U1.1.2` | `ntsRef U1.1.2` |
13
+ | water `amount` (in oneOf branch 1) | `sef25Ref U1.1.1` | `U1.1.1` | `ntsRef U1.1.1` |
14
+ | drainage `associatedCost` | `sef25Ref U1.2` | `U1.2` | `ntsRef U1.2` |
15
+ | drainage `frequency` | `sef25Ref U1.2.2` | `U1.2.2` | `ntsRef U1.2.2` |
16
+ | drainage `amount` | `sef25Ref U1.2.1` | `U1.2.1` | `ntsRef U1.2.1` |
17
+
18
+ There are no `$ref` indirections involved — these are inline definitions, and they all match.
19
+
20
+ ## Root cause — missing `discriminator` keyword
21
+
22
+ The three `associatedCost` blocks (leased solar `S1.1`, mains water `U1.1`, mains foul drainage `U1.2`) follow the project's standard *discriminated oneOf* pattern: a child enum field selects which `oneOf` branch is active. Branch 0 (`Not applicable`) leaves `amount` out; branch 1 (`Per month`/`Per year`) requires `amount`.
23
+
24
+ In commit [`188809f`](../../../) ("use frequency as discriminator for associatedCost") this pattern was correctly modelled with both `oneOf` **and** `"discriminator": { "propertyName": "frequency" }`. The block was later flattened, then re‑introduced in commit [`6317499`](../../../) ("re-introduce frequency discriminator on associatedCost (lenient)"). Despite the title, that commit added the `oneOf` branches but **did not put the `discriminator` keyword back**. The current state of all three blocks is therefore `oneOf` without a discriminator hint.
25
+
26
+ Every other oneOf in `combined.json` carries an explicit `discriminator` (`grep -c '"discriminator"'` on the file confirms this is the universal convention here). Both AJV (configured with `discriminator: true` in [index.js:8](../../index.js#L8)) and the schema‑driven UI generator that consumes the merged schema rely on it to know which oneOf branch is active for a given form value. Without it, picking `Per month` does not switch the active branch, so the `amount` property never becomes visible.
27
+
28
+ The overlay files (`sef25.json`, `extensions/sc.json`) themselves do not need a discriminator added — the overlay extractor at [src/utils/extractOverlay.js:143-167](../../src/utils/extractOverlay.js#L143) already propagates `discriminator` from base into the generated overlays, so re‑running it after the base fix will pick the discriminator up automatically.
29
+
30
+ ## Fix
31
+
32
+ Three identical edits to **`src/schemas/v3/combined.json`** — add the missing `discriminator` to each `associatedCost` block. After the existing `"properties": { "frequency": { ... } }` line and before `"oneOf": [ ... ]`, insert:
33
+
34
+ ```json
35
+ "discriminator": { "propertyName": "frequency" },
36
+ ```
37
+
38
+ ### Locations
39
+
40
+ 1. Leased solar panel `associatedCost` (`sef25Ref: "S1.1"`) — [src/schemas/v3/combined.json:21605](../../src/schemas/v3/combined.json#L21605) (insert before line ~21618 `oneOf`)
41
+ 2. Water `mainsWater` → `oneOf[0]` (No / private supply) → `associatedCost` (`U1.1`) — [src/schemas/v3/combined.json:22425](../../src/schemas/v3/combined.json#L22425) (insert before line 22438 `oneOf`)
42
+ 3. Drainage `mainsFoulDrainage` → `oneOf[2]` (off-mains) → `associatedCost` (`U1.2`) — [src/schemas/v3/combined.json:23547](../../src/schemas/v3/combined.json#L23547) (insert before line 23560 `oneOf`)
43
+
44
+ Note the leased-solar block has the same bug. The user only mentioned water and drainage, but since the structure is identical and the fix is one line each, fixing all three keeps the codebase consistent and pre‑empts the same report for solar panels later. (Confirm with user — see Open Questions.)
45
+
46
+ ### Regenerate
47
+
48
+ ```bash
49
+ npm run extract-overlays # regenerates pdtf-transaction.json + sef25.json etc.
50
+ npm run extract-extension-overlays # regenerates sc.json and other extension overlays
51
+ ```
52
+
53
+ `extractOverlay.js` already detects `element.discriminator` and writes both the discriminator and the per‑branch enum into the generated overlays, so the regenerated `pdtf-transaction.json`, `overlays/sef25.json`, and `overlays/extensions/sc.json` will all carry the discriminator without manual edits.
54
+
55
+ ### Version bump
56
+
57
+ Bump `package.json` from `3.6.0-40` to `3.6.0-41`. This is a non‑breaking schema change (existing payloads still validate; only the UI hint is restored).
58
+
59
+ ## Verification
60
+
61
+ 1. **Validate the schema fix locally:**
62
+ ```bash
63
+ npm test
64
+ ```
65
+ The existing test suite must still pass. The change is purely additive (adding a discriminator keyword on top of an existing oneOf), so no current test is expected to break.
66
+
67
+ 2. **Spot‑check with a small AJV/UI smoke test (optional but recommended):**
68
+ ```js
69
+ const { getTransactionSchema } = require("./index");
70
+ const schema = getTransactionSchema(
71
+ "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
72
+ ["nts2025", "sc"] // or ["nts2025", "sef25"-style stack you actually use
73
+ );
74
+ // Walk to schema.properties.propertyPack.properties.waterAndDrainage.properties.water.properties.mainsWater.oneOf[0].properties.associatedCost
75
+ // and confirm BOTH of these are present:
76
+ // .discriminator === { propertyName: "frequency" }
77
+ // .oneOf[1].properties.amount.type === "number"
78
+ ```
79
+
80
+ 3. **End-to-end check in the consuming UI:** load the form with the SEF25 / `sc` extension stack, pick `Per month` under water and under drainage, and confirm the `amount (£)` input appears. Repeat for `Per year` (should appear) and `Not applicable` (should hide).
81
+
82
+ 4. **Confirm overlays were regenerated cleanly:**
83
+ ```bash
84
+ git diff src/schemas/v3/pdtf-transaction.json src/schemas/v3/overlays/sef25.json src/schemas/v3/overlays/extensions/sc.json
85
+ ```
86
+ The diff should show `discriminator: { propertyName: "frequency" }` added in each of those three generated files alongside the existing oneOf for water/drainage (and solar, if included).
87
+
88
+ ## Critical files
89
+
90
+ - `src/schemas/v3/combined.json` — three edits, the source of truth
91
+ - `src/schemas/v3/pdtf-transaction.json` — regenerated, do not hand-edit
92
+ - `src/schemas/v3/overlays/sef25.json` — regenerated
93
+ - `src/schemas/v3/overlays/extensions/sc.json` — regenerated
94
+ - `src/utils/extractOverlay.js` — read-only reference; already handles discriminator propagation
95
+ - `package.json` — version bump
96
+
97
+ ## Open questions
98
+
99
+ - Include the leased solar panel `S1.1` block in the same fix? (Identical bug, one extra one-line edit, keeps the codebase consistent — recommended yes.)
@@ -15,7 +15,6 @@
15
15
  "Bash(git commit:*)",
16
16
  "Bash(git push:*)",
17
17
  "Bash(git fetch:*)",
18
- "Bash(git checkout:*)",
19
18
  "Bash(gh pr view:*)",
20
19
  "WebFetch(domain:github.com)",
21
20
  "Bash(tree:*)",
@@ -35,8 +34,28 @@
35
34
  "Bash(base64:*)",
36
35
  "Bash(gh issue view:*)",
37
36
  "Read(//Users/ed/.claude/**)",
38
- "Bash(npm init:*)"
37
+ "Bash(npm init:*)",
38
+ "Bash(do echo \"=== printWidth $w ===\")",
39
+ "Bash(pdftoppm:*)",
40
+ "Bash(pdftotext:*)",
41
+ "Bash(/tmp/inspect2.js:*)",
42
+ "Bash(/tmp/inspect3.js:*)",
43
+ "Bash(/tmp/inspect4.js:*)",
44
+ "Bash(/tmp/inspect5.js:*)",
45
+ "Bash(/tmp/inspect6.js:*)",
46
+ "Bash(/tmp/final_inspect.js:*)",
47
+ "Bash(file:*)",
48
+ "Bash(gh pr create:*)",
49
+ "Bash(claude --version)",
50
+ "Bash(gh pr list --head ta6ed6 --json number,title,url)",
51
+ "Bash(gh pr:*)",
52
+ "Bash(sort -t'.' -k1,1n -k2,2n -k3,3n -k4,4n)",
53
+ "Bash(wc -l /Users/ed/Code/schemas/src/schemas/v3/combined.json /Users/ed/Code/schemas/src/schemas/v3/overlays/*.json)"
39
54
  ],
40
- "deny": []
55
+ "deny": [],
56
+ "additionalDirectories": [
57
+ "/tmp",
58
+ "/private/tmp"
59
+ ]
41
60
  }
42
61
  }
package/README.md CHANGED
@@ -7,7 +7,7 @@ The Property Data Trust Framework (PDTF) Schemas provide standardized JSON Schem
7
7
 
8
8
  ## Project Goals & Status
9
9
 
10
- **Current Version:** 3.5.0 (3.5.1-1 available as a pre-release on branch/package-tag `next`)
10
+ **Current Version:** 3.5.0 (3.6.0-41 available as a pre-release on branch/package-tag `next`)
11
11
 
12
12
  This schema framework aims to support the [Home Buying and Selling Group](https://homebuyingandsellinggroup.co.uk) 'Property Pack' initiative, encompassing all requirements starting with the Buyers and Sellers Property Information set ([BASPI v4.0](https://homebuyingandsellinggroup.co.uk/baspi/)).
13
13
 
@@ -62,7 +62,7 @@ if (!isValid) {
62
62
 
63
63
  - **🏗️ Modular Schema System** - Base schemas with flexible overlay composition
64
64
  - **📋 Multiple Form Support** - BASPI, NTS, Law Society TA forms, and more
65
- - **🧩 Extension Overlays** - Granular NTS2 features as individual modules
65
+ - **🧩 Extension Overlays** - Granular NTS2 and SEF25 features as individual modules
66
66
  - **✅ JSON Schema Validation** - Full JSON Schema Draft 07 support with AJV
67
67
  - **🔗 Verified Claims** - Support for verified data provenance tracking
68
68
  - **📚 Comprehensive Documentation** - Detailed usage guides and examples
@@ -82,7 +82,7 @@ if (!isValid) {
82
82
  │ │ │ │ └── extensions/ # Modular NTS2 extensions
83
83
  │ │ │ │ ├── jk.json # Japanese Knotweed
84
84
  │ │ │ │ ├── tf.json # Transfer Fees
85
- │ │ │ │ └── ... # 16 total extensions
85
+ │ │ │ │ └── ... # NTS2 + SEF25 extensions
86
86
  │ │ │ └── combined.json # Master schema for generation
87
87
  │ │ ├── v2/ # Legacy schema version
88
88
  │ │ ├── verifiedClaims/ # Verified claims schemas
@@ -163,31 +163,56 @@ const errors = validateVerifiedClaims(verifiedClaims, schemaId, ["nts2023"]);
163
163
 
164
164
  #### Extension Overlays
165
165
 
166
- Modular NTS2 features for selective adoption:
166
+ Modular features for selective adoption. Extensions are merged on top of an NTS base overlay.
167
167
 
168
168
  ```javascript
169
- // Individual extensions
170
- const schema = getTransactionSchema(schemaId, ["nts2023", "jk", "tf"]);
171
-
172
- // Multiple specialist issues
173
- const schema = getTransactionSchema(schemaId, [
174
- "nts2023",
175
- "as",
176
- "dr",
177
- "jk",
178
- "sb",
179
- ]);
169
+ // NTS2 extensions
170
+ const schema = getTransactionSchema(schemaId, ["nts2023", "jk", "tf", "ma"]);
171
+
172
+ // SEF25 extensions (Seller Enquiry Form)
173
+ const sef25 = ["sc", "pc", "ph", "dk", "rw", "sd", "lc", "wg", "ic", "nd", "mi", "tr"];
174
+ const schema = getTransactionSchema(schemaId, ["nts2023", ...sef25]);
180
175
  ```
181
176
 
182
- | Extension | Code | Description |
183
- | ----------------- | ---- | -------------------------------- |
184
- | Japanese Knotweed | `jk` | Knotweed presence and management |
185
- | Transfer Fees | `tf` | Additional leasehold fees |
186
- | Managing Agent | `ma` | Leasehold managing agent details |
187
- | Solar Panels | `sl` | Solar panel ownership details |
188
- | Asbestos | `as` | Asbestos presence and management |
177
+ **NTS2 Extensions:**
189
178
 
190
- [View all extensions →](src/schemas/v3/overlays/README.md#extension-overlays)
179
+ | Extension | Code | Description |
180
+ | --- | --- | --- |
181
+ | Japanese Knotweed | `jk` | Knotweed presence and management |
182
+ | Asbestos | `as` | Asbestos presence and management |
183
+ | Dry Rot | `dr` | Dry rot treatment |
184
+ | Subsidence | `sb` | Subsidence or structural fault |
185
+ | Health & Safety | `hs` | Ongoing health or safety issues |
186
+ | Outside Areas | `oa` | Outside areas details |
187
+ | Main Construction | `mc` | Construction type if standard form |
188
+ | Loft Access | `la` | Loft access and details |
189
+ | Spray Foam | `sf` | Spray foam insulation |
190
+ | Solar Panels | `sl` | Solar panel ownership details |
191
+ | Heating Installed | `hi` | Central heating installation date |
192
+ | Flood Defences | `fd` | Flood defence information |
193
+ | Estate Rentcharges | `er` | Estate rentcharges for freehold |
194
+ | Managing Agent | `ma` | Leasehold managing agent details |
195
+ | Transfer Fees | `tf` | Additional leasehold fees |
196
+ | Onward Chain | `oc` | Other property in chain |
197
+
198
+ **SEF25 Extensions (Seller Enquiry Form):**
199
+
200
+ | Extension | Code | Description |
201
+ | --- | --- | --- |
202
+ | Supply Costs | `sc` | Private water/sewerage costs |
203
+ | Parking Permit Cost | `pc` | Parking permit frequency |
204
+ | Property Hazards | `ph` | 4 hazard Yes/No questions |
205
+ | Dropped Kerb | `dk` | Dropped kerb access to parking |
206
+ | Private Right of Way | `rw` | Private right of way |
207
+ | Storm/Fire/Flood Damage | `sd` | Storm, fire or flood damage |
208
+ | Solar Lease Costs | `lc` | Solar panel lease costs |
209
+ | Warranties & Guarantees | `wg` | 7 warranty categories upfront |
210
+ | Insurance Claims | `ic` | Insurance claims upfront |
211
+ | Neighbour Development | `nd` | Neighbour development |
212
+ | Material Issue | `mi` | Other material issue upfront |
213
+ | Title Restrictions | `tr` | Title restrictions for freehold |
214
+
215
+ [View full SEF25 UI spec →](docs/sef25-extensions-ui-spec.md) | [View all extensions →](src/schemas/v3/overlays/README.md#extension-overlays)
191
216
 
192
217
  ## Usage Examples
193
218
 
@@ -204,7 +229,7 @@ const legalSchema = getTransactionSchema(schemaId, ["ta6ed4", "ta7ed3"]);
204
229
  const ntsSchema = getTransactionSchema(schemaId, ["nts2023"]);
205
230
  ```
206
231
 
207
- ### Modular NTS2 Features
232
+ ### Modular Extension Features
208
233
 
209
234
  ```javascript
210
235
  // Selective NTS2 adoption
@@ -215,15 +240,9 @@ const partialNts2 = getTransactionSchema(schemaId, [
215
240
  "ma", // Managing Agent
216
241
  ]);
217
242
 
218
- // Full specialist issues
219
- const specialistIssues = getTransactionSchema(schemaId, [
220
- "nts2023",
221
- "as",
222
- "dr",
223
- "jk",
224
- "sb",
225
- "hs",
226
- ]);
243
+ // SEF25 Seller Enquiry Form extensions
244
+ const sef25 = ["sc", "pc", "ph", "dk", "rw", "sd", "lc", "wg", "ic", "nd", "mi", "tr"];
245
+ const sellerEnquiry = getTransactionSchema(schemaId, ["nts2023", ...sef25]);
227
246
  ```
228
247
 
229
248
  ### Data Validation
@@ -0,0 +1,355 @@
1
+ # SEF25 Extension Overlays — Front-End UI Spec
2
+
3
+ ## Overview
4
+
5
+ Extension overlays add follow-up questions to existing form sections. Each overlay is loaded by passing its key to `getTransactionSchema(schemaId, overlays)`.
6
+
7
+ ### Pre-existing overlays
8
+
9
+ These were added before this PR and are already in production.
10
+
11
+ | Overlay Key | Name | Section | Fields Added |
12
+ |-------------|------|---------|-------------|
13
+ | `sc` | Supply Costs | Water & Drainage | Cost fields for private water and sewerage (restructured to hasCost Yes/No gate) |
14
+ | `pc` | Parking Permit Cost | Parking | 1 frequency field |
15
+ | `ph` | Property Hazards | Specialist Issues | 4 Yes/No + details fields |
16
+
17
+ ### New overlays (this PR)
18
+
19
+ | Overlay Key | Name | Section | Fields Added |
20
+ |-------------|------|---------|-------------|
21
+ | `dk` | Dropped Kerb | Parking | 1 Yes/No field |
22
+ | `rw` | Private Right of Way | Rights & Informal Arrangements | 1 Yes/No + details field |
23
+ | `sd` | Storm/Fire/Flood Damage | Environmental Issues | 1 Yes/No + details field |
24
+ | `lc` | Solar Lease Costs | Utilities (Solar Panels) | 1 cost field (hasCost + amount + frequency) |
25
+ | `wg` | Warranties & Guarantees | Guarantees & Warranties | Marks 7 warranty categories as required |
26
+ | `ic` | Insurance Claims | Insurance | Marks insuranceClaims required (isInsured=Yes branch) |
27
+ | `nd` | Neighbour Development | Notices | Adds sef25Ref to neighbourDevelopment (already required via NTS) |
28
+ | `mi` | Material Issue | Additional Information | Marks otherMaterialIssue required |
29
+ | `tr` | Title Restrictions | Ownership (Freehold) | Adds titleRestrictions field to freehold branch |
30
+
31
+ ### Existing fields with sef25Ref only (no separate overlay needed)
32
+
33
+ | Field | Path | sef25Ref | Notes |
34
+ |-------|------|----------|-------|
35
+ | `hasBeenFlooded` | `environmentalIssues.flooding.historicalFlooding.hasBeenFlooded` | E1.2 | Already required and rendered via NTS flooding section |
36
+
37
+ All fields use `sef25Ref` for reference codes.
38
+
39
+ ---
40
+
41
+ ## Pre-existing overlays (detail)
42
+
43
+ ### 1. Supply Costs (`sc`)
44
+
45
+ #### 1a. Private Water Cost
46
+
47
+ **Trigger:** Show when `propertyPack.waterAndDrainage.water.mainsWater.yesNo` = `"No"`
48
+
49
+ **Path:** `propertyPack.waterAndDrainage.water.mainsWater.associatedCost`
50
+
51
+ **UI:** Render alongside the existing "How is water supplied?" details field. First ask if there are costs, then show amount and frequency if Yes.
52
+
53
+ | Field | Path (relative) | Type | Validation | UI Element |
54
+ |-------|-----------------|------|------------|------------|
55
+ | Has costs? | `associatedCost.hasCost` | `string` | Required, enum | Yes/No radio |
56
+ | Cost (£) | `associatedCost.amount` | `number` | Required when Yes | Numeric input |
57
+ | Payment frequency | `associatedCost.frequency` | `string` | Required when Yes, enum | Dropdown |
58
+
59
+ **Frequency enum values:** `"Per month"`, `"Per year"`
60
+
61
+ **Ref:** `U1.1` (hasCost: `U1.1.1`, amount: `U1.1.2`, frequency: `U1.1.3`)
62
+
63
+ #### 1b. Private Sewerage Cost
64
+
65
+ **Trigger:** Show when `propertyPack.waterAndDrainage.drainage.mainsFoulDrainage.yesNo` = `"No"` or `"Not known"`
66
+
67
+ **Path:** `propertyPack.waterAndDrainage.drainage.mainsFoulDrainage.associatedCost`
68
+
69
+ **UI:** Same hasCost pattern as water cost above.
70
+
71
+ **Ref:** `U1.2` (hasCost: `U1.2.1`, amount: `U1.2.2`, frequency: `U1.2.3`)
72
+
73
+ ### 2. Parking Permit Cost (`pc`)
74
+
75
+ **Trigger:** Show when `propertyPack.parking.controlledParking.yesNo` = `"Yes"`
76
+
77
+ **Path:** `propertyPack.parking.controlledParking.costOfPermitFrequency`
78
+
79
+ **UI:** Render alongside the existing cost of permit field. The frequency dropdown clarifies whether the cost amount is monthly or annual.
80
+
81
+ | Field | Path (relative) | Type | Validation | UI Element |
82
+ |-------|-----------------|------|------------|------------|
83
+ | Payment frequency | `costOfPermitFrequency` | `string` | Enum | Dropdown |
84
+
85
+ **Frequency enum values:** `"Per month"`, `"Per year"`
86
+
87
+ **Ref:** `P1.1`
88
+
89
+ ### 3. Property Hazards (`ph`)
90
+
91
+ **Trigger:** Always shown (standalone questions within Specialist Issues).
92
+
93
+ **Path prefix:** `propertyPack.specialistIssues`
94
+
95
+ All 4 fields follow the same pattern: Yes/No, if Yes show required details.
96
+
97
+ | Property | Title | sef25Ref |
98
+ |----------|-------|----------|
99
+ | `wellsDitchesShaft` | Are there any wells, ditches, or shafts at the property? | H1.1 |
100
+ | `damagedOrExposedElectrics` | Are there any damaged or exposed electrics at the property? | H1.2 |
101
+ | `damageToFlooringOrStaircases` | Is there any damage to flooring and/or staircases? | H1.3 |
102
+ | `knownAreasInPoorCondition` | Are there any known areas in poor condition (internal or external)? | H1.4 |
103
+
104
+ ---
105
+
106
+ ## New overlays (detail)
107
+
108
+ ### 4. Dropped Kerb (`dk`)
109
+
110
+ **Trigger:** Always shown within Parking section.
111
+
112
+ **Path:** `propertyPack.parking.droppedKerbAccess`
113
+
114
+ | Field | Path (relative) | Type | Validation | UI Element |
115
+ |-------|-----------------|------|------------|------------|
116
+ | Dropped kerb? | `droppedKerbAccess.yesNo` | `string` | Required, enum | Yes/No radio |
117
+
118
+ **Ref:** `K1.1` (yesNo: `K1.1.1`)
119
+
120
+ ---
121
+
122
+ ### 5. Private Right of Way (`rw`)
123
+
124
+ **Trigger:** Always shown within Rights & Informal Arrangements, alongside the existing public right of way question.
125
+
126
+ **Path:** `propertyPack.rightsAndInformalArrangements.rightsOrArrangements.privateRightOfWay`
127
+
128
+ **UI:** Same pattern as `publicRightOfWay` — Yes/No, if Yes show details.
129
+
130
+ | Field | Path (relative) | Type | Validation | UI Element |
131
+ |-------|-----------------|------|------------|------------|
132
+ | Private ROW? | `privateRightOfWay.yesNo` | `string` | Required, enum | Yes/No radio |
133
+ | Details | `privateRightOfWay.details` | `string` | Required when Yes, minLength 1 | Text input |
134
+
135
+ **Ref:** `R1.1` (yesNo: `R1.1.1`, details: `R1.1.2`)
136
+
137
+ ---
138
+
139
+ ### 6. Storm, Fire or Flood Damage (`sd`)
140
+
141
+ **Trigger:** Always shown within Environmental Issues section.
142
+
143
+ **Path:** `propertyPack.environmentalIssues.stormFireFloodDamage`
144
+
145
+ | Field | Path (relative) | Type | Validation | UI Element |
146
+ |-------|-----------------|------|------------|------------|
147
+ | Damage? | `stormFireFloodDamage.yesNo` | `string` | Required, enum | Yes/No radio |
148
+ | Details | `stormFireFloodDamage.details` | `string` | Required when Yes, minLength 1 | Text area |
149
+
150
+ **Ref:** `E1.1` (yesNo: `E1.1.1`, details: `E1.1.2`)
151
+
152
+ ---
153
+
154
+ ### 7. Solar Panel Lease Costs (`lc`)
155
+
156
+ **Trigger:** Show when `propertyPack.electricity.solarPanels.panelsOwnedOutright.yesNo` = `"No"` (panels are leased).
157
+
158
+ **Path:** `propertyPack.electricity.solarPanels.panelsOwnedOutright.associatedCost`
159
+
160
+ **UI:** Render alongside existing lease details. First ask if there are costs, then show amount and frequency if Yes.
161
+
162
+ | Field | Path (relative) | Type | Validation | UI Element |
163
+ |-------|-----------------|------|------------|------------|
164
+ | Has costs? | `associatedCost.hasCost` | `string` | Required, enum | Yes/No radio |
165
+ | Amount (£) | `associatedCost.amount` | `number` | Required when Yes | Numeric input |
166
+ | Frequency | `associatedCost.frequency` | `string` | Required when Yes, enum | Dropdown |
167
+
168
+ **Frequency enum values:** `"Per month"`, `"Per year"`
169
+
170
+ **Ref:** `S1.1` (hasCost: `S1.1.1`, amount: `S1.1.2`, frequency: `S1.1.3`)
171
+
172
+ ---
173
+
174
+ ### 8. Warranties & Guarantees Upfront (`wg`)
175
+
176
+ **Trigger:** Always shown when overlay is loaded. Displayed upfront regardless of `hasValidGuaranteesOrWarranties` value.
177
+
178
+ **Path prefix:** `propertyPack.guaranteesWarrantiesAndIndemnityInsurances`
179
+
180
+ **UI:** For SEF25 upfront, show all 7 warranty categories as simple Yes/No questions. No details or "Years Remaining" needed at this stage — the law firm picks up details later.
181
+
182
+ | Property | Title | sef25Ref |
183
+ |----------|-------|----------|
184
+ | `newHomeWarranty` | New Home Warranty (NHBC or similar) | W1.1 |
185
+ | `roofingWork` | Roofing work | W1.2 |
186
+ | `dampProofingTreatment` | Damp proofing treatment | W1.3 |
187
+ | `centralHeatingAndorPlumbing` | Central heating and/or plumbing | W1.4 |
188
+ | `electricalRepairOrInstallation` | Electrical repair or installation | W1.5 |
189
+ | `subsidenceWork` | Underpinning or other preventative work / remedial action relating to subsidence | W1.6 |
190
+ | `otherGuarantees` | Other Guarantees or warranties | W1.7 |
191
+
192
+ **Note:** These fields already exist in the base schema under the `hasValidGuaranteesOrWarranties = "Yes"` branch. The `wg` overlay marks them as required within that branch. The Moverly app shows them upfront for SEF25 users without requiring the parent gate question first.
193
+
194
+ ---
195
+
196
+ ### 9. Insurance Claims (`ic`)
197
+
198
+ **Path:** `propertyPack.insurance.insuranceClaims` (inside `isInsured = "Yes"` branch)
199
+
200
+ **Ref:** `I1.1` (yesNo: `I1.1.1`) — "Have you made any buildings insurance claims?"
201
+
202
+ **Note:** The overlay marks insuranceClaims required within the `isInsured = "Yes"` oneOf branch.
203
+
204
+ ---
205
+
206
+ ### 10. Neighbour Development (`nd`)
207
+
208
+ **Path:** `propertyPack.notices.neighbourDevelopment`
209
+
210
+ **Ref:** `N1.1` — "Is the seller aware of any proposals to develop property or land nearby?"
211
+
212
+ **Note:** `neighbourDevelopment` is already required via `ntsRequired` at the notices level. The `nd` overlay adds `sef25Ref` and `ntsRef` annotations so the field is identifiable as SEF25-relevant.
213
+
214
+ ---
215
+
216
+ ### 11. Material Issue (`mi`)
217
+
218
+ **Path:** `propertyPack.additionalInformation.otherMaterialIssue`
219
+
220
+ **Ref:** `M1.1` (yesNo: `M1.1.1`) — "Are you aware of any other material issue or information which may affect the average person's decision to proceed?"
221
+
222
+ ---
223
+
224
+ ### 12. Title Restrictions (`tr`)
225
+
226
+ **Path:** `propertyPack.ownership.ownershipsToBeTransferred[Freehold].titleRestrictions`
227
+
228
+ **Ref:** `T1.1` (yesNo: `T1.1.1`, details: `T1.1.2`) — "Are there any known restrictions on the title?"
229
+
230
+ New field on the freehold ownership branch, modelled on `leaseRestrictions` for leasehold. Yes/No + details if Yes.
231
+
232
+ ---
233
+
234
+ ## Loading the Extensions
235
+
236
+ ```js
237
+ import { getTransactionSchema, getValidator } from "@pdtf/schemas";
238
+
239
+ const schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
240
+
241
+ // Load all SEF25 extensions
242
+ const sef25Extensions = ["sc", "pc", "ph", "dk", "rw", "sd", "lc", "wg", "ic", "nd", "mi", "tr"];
243
+ const schema = getTransactionSchema(schemaId, sef25Extensions);
244
+
245
+ // Or combine with other overlays
246
+ const schema = getTransactionSchema(schemaId, ["baspiV5", ...sef25Extensions]);
247
+
248
+ // Validate data against the merged schema
249
+ const validator = getValidator(schemaId, sef25Extensions);
250
+ ```
251
+
252
+ ---
253
+
254
+ ## Summary of Trigger Conditions
255
+
256
+ | Field | Visible when... |
257
+ |-------|----------------|
258
+ | Water cost | `mainsWater.yesNo` = `"No"` |
259
+ | Sewerage cost | `mainsFoulDrainage.yesNo` = `"No"` or `"Not known"` |
260
+ | Parking permit frequency | `controlledParking.yesNo` = `"Yes"` |
261
+ | Wells/ditches/shaft | Always (within Specialist Issues) |
262
+ | Damaged electrics | Always (within Specialist Issues) |
263
+ | Flooring/staircase damage | Always (within Specialist Issues) |
264
+ | Areas in poor condition | Always (within Specialist Issues) |
265
+ | Dropped kerb access | Always (within Parking) |
266
+ | Private right of way | Always (within Rights & Informal Arrangements) |
267
+ | Storm/fire/flood damage | Always (within Environmental Issues) |
268
+ | Solar panel lease costs | `panelsOwnedOutright.yesNo` = `"No"` |
269
+ | Warranty categories (7) | Always when `wg` overlay loaded (within Guarantees & Warranties) |
270
+ | Insurance claims | `isInsured.yesNo` = `"Yes"` (via `ic` overlay) |
271
+ | Neighbour development | Always (via `nd` overlay) |
272
+ | Other material issue | Always (via `mi` overlay) |
273
+ | Title restrictions | Always (via `tr` overlay, freehold only) |
274
+
275
+ ---
276
+
277
+ ## Moverly propertyPackTasks Changes
278
+
279
+ The following changes are needed in the Moverly app's `propertyPackTasks` configuration to surface the new SEF25 fields in the listing journey.
280
+
281
+ ### Existing tasks to deprecate
282
+
283
+ The flooding and planning tasks should be stepped up to their parent section level to cover the new sibling fields (`stormFireFloodDamage` and `neighbourDevelopment`).
284
+
285
+ ```js
286
+ // Replace existing flooding task with deprecated version
287
+ {
288
+ name: "Flooding",
289
+ path: "/propertyPack/environmentalIssues/flooding",
290
+ category: "listing",
291
+ overlay: "nts2023",
292
+ deprecated: true, // replaced by environmental issues check
293
+ },
294
+
295
+ // Replace existing planning task with deprecated version
296
+ {
297
+ name: "Planning and development",
298
+ path: "/propertyPack/notices/planningApplication",
299
+ category: "listing",
300
+ overlay: "nts2023",
301
+ deprecated: true, // replaced by notices check
302
+ },
303
+ ```
304
+
305
+ ### New listing tasks to add
306
+
307
+ ```js
308
+ {
309
+ name: "Environmental issues",
310
+ checkName: "sef25-environmentalIssues",
311
+ path: "/propertyPack/environmentalIssues",
312
+ category: "listing",
313
+ overlay: "sd",
314
+ },
315
+ {
316
+ name: "Notices",
317
+ checkName: "sef25-notices",
318
+ path: "/propertyPack/notices",
319
+ category: "listing",
320
+ overlay: "nd",
321
+ },
322
+ {
323
+ name: "Guarantees & warranties",
324
+ checkName: "sef25-warranties",
325
+ path: "/propertyPack/guaranteesWarrantiesAndIndemnityInsurances",
326
+ category: "listing",
327
+ overlay: "wg",
328
+ },
329
+ {
330
+ name: "Insurance claims",
331
+ checkName: "sef25-insurance",
332
+ path: "/propertyPack/insurance",
333
+ category: "listing",
334
+ overlay: "ic",
335
+ },
336
+ {
337
+ name: "Additional material information",
338
+ checkName: "sef25-additionalInformation",
339
+ path: "/propertyPack/additionalInformation",
340
+ category: "listing",
341
+ overlay: "mi",
342
+ },
343
+ ```
344
+
345
+ ### Fields covered by existing listing tasks (no new task needed)
346
+
347
+ | Extension | Covered by |
348
+ |-----------|------------|
349
+ | `dk` (dropped kerb) | Parking |
350
+ | `rw` (private right of way) | Rights and easements |
351
+ | `lc` (solar lease costs) | Electricity |
352
+ | `sc` (supply costs) | Water and drainage |
353
+ | `pc` (parking permit cost) | Parking |
354
+ | `ph` (property hazards) | Specialist issues |
355
+ | `tr` (title restrictions) | Ownership |