@pdtf/schemas 3.6.0-3 → 3.6.0-31
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/.claude/settings.local.json +22 -3
- package/docs/sef25-extensions-ui-spec.md +146 -0
- package/index.js +121 -66
- package/package.json +4 -2
- package/src/schemas/v3/combined.json +7123 -1363
- package/src/schemas/v3/compactSkeleton.txt +351 -12
- package/src/schemas/v3/overlays/baspi4.json +163 -18
- package/src/schemas/v3/overlays/baspi5.json +168 -20
- package/src/schemas/v3/overlays/extensions/dr.json +5 -7
- package/src/schemas/v3/overlays/extensions/jk.json +24 -6
- package/src/schemas/v3/overlays/extensions/pc.json +36 -0
- package/src/schemas/v3/overlays/extensions/ph.json +197 -0
- package/src/schemas/v3/overlays/extensions/sc.json +83 -0
- package/src/schemas/v3/overlays/fme1.json +16 -14
- package/src/schemas/v3/overlays/lpe1.json +60 -30
- package/src/schemas/v3/overlays/nts.json +68 -1
- package/src/schemas/v3/overlays/nts2.json +68 -4
- package/src/schemas/v3/overlays/ntsl.json +68 -1
- package/src/schemas/v3/overlays/ntsl2.json +68 -4
- package/src/schemas/v3/overlays/piq.json +38 -1
- package/src/schemas/v3/overlays/rds.json +118 -6
- package/src/schemas/v3/overlays/ta6.json +46 -16
- package/src/schemas/v3/overlays/ta6ed6.json +6498 -0
- package/src/schemas/v3/overlays/ta7.json +70 -6
- package/src/schemas/v3/overlays/ta7ed5.json +1539 -0
- package/src/schemas/v3/pdtf-transaction.json +4335 -798
- package/src/schemas/v3/skeleton.json +486 -34
- package/src/tests/v3/buyerCircumstances.test.js +543 -0
- package/src/tests/v3/extensionOverlays.test.js +436 -0
- package/src/tests/v3/offers.test.js +37 -40
- package/src/tests/v3/patternProperties.test.js +149 -30
- package/src/tests/v3/ta6ed6.test.js +2317 -0
- package/src/tests/v3/ta7ed5.test.js +567 -0
- package/src/tests/v3/transactionSchema.test.js +31 -0
- package/src/utils/extractExtensionOverlays.js +104 -31
- package/src/utils/extractOverlay.js +87 -23
- package/src/utils/pathSkeleton.js +1 -1
|
@@ -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
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# SEF25 Extension Overlays — Front-End UI Spec
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Three new extension overlays add follow-up questions to existing form sections. Each overlay is loaded by passing its key to `getTransactionSchema(schemaId, overlays)`.
|
|
6
|
+
|
|
7
|
+
| Overlay Key | Name | Section | Fields Added |
|
|
8
|
+
|-------------|------|---------|-------------|
|
|
9
|
+
| `sc` | Supply Costs | Water & Drainage | 2 cost fields (private water, private sewerage) |
|
|
10
|
+
| `pc` | Parking Permit Cost | Parking | 1 frequency field |
|
|
11
|
+
| `ph` | Property Hazards | Specialist Issues | 4 Yes/No + details fields |
|
|
12
|
+
|
|
13
|
+
All fields use `sef25Ref` for reference codes.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Supply Costs (`sc`)
|
|
18
|
+
|
|
19
|
+
### 1a. Private Water Cost
|
|
20
|
+
|
|
21
|
+
**Trigger:** Show when `propertyPack.waterAndDrainage.water.mainsWater.yesNo` = `"No"`
|
|
22
|
+
|
|
23
|
+
**Path:** `propertyPack.waterAndDrainage.water.mainsWater.associatedCost`
|
|
24
|
+
|
|
25
|
+
**UI:** Render alongside the existing "How is water supplied?" details field.
|
|
26
|
+
|
|
27
|
+
| Field | Path (relative) | Type | Validation | UI Element |
|
|
28
|
+
|-------|-----------------|------|------------|------------|
|
|
29
|
+
| Cost (£) | `associatedCost.amount` | `number` | Required | Numeric input |
|
|
30
|
+
| Payment frequency | `associatedCost.frequency` | `string` | Required, enum | Dropdown |
|
|
31
|
+
|
|
32
|
+
**Frequency enum values:** `"Per month"`, `"Per year"`
|
|
33
|
+
|
|
34
|
+
**Ref:** `U1.1` (amount: `U1.1.1`, frequency: `U1.1.2`)
|
|
35
|
+
|
|
36
|
+
### 1b. Private Sewerage Cost
|
|
37
|
+
|
|
38
|
+
**Trigger:** Show when `propertyPack.waterAndDrainage.drainage.mainsFoulDrainage.yesNo` = `"No"` or `"Not known"`
|
|
39
|
+
|
|
40
|
+
**Path:** `propertyPack.waterAndDrainage.drainage.mainsFoulDrainage.associatedCost`
|
|
41
|
+
|
|
42
|
+
**UI:** Render alongside the existing off-mains drainage system questions.
|
|
43
|
+
|
|
44
|
+
| Field | Path (relative) | Type | Validation | UI Element |
|
|
45
|
+
|-------|-----------------|------|------------|------------|
|
|
46
|
+
| Cost (£) | `associatedCost.amount` | `number` | Required | Numeric input |
|
|
47
|
+
| Payment frequency | `associatedCost.frequency` | `string` | Required, enum | Dropdown |
|
|
48
|
+
|
|
49
|
+
**Frequency enum values:** `"Per month"`, `"Per year"`
|
|
50
|
+
|
|
51
|
+
**Ref:** `U1.2` (amount: `U1.2.1`, frequency: `U1.2.2`)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. Parking Permit Cost (`pc`)
|
|
56
|
+
|
|
57
|
+
**Trigger:** Show when `propertyPack.parking.controlledParking.yesNo` = `"Yes"`
|
|
58
|
+
|
|
59
|
+
**Path:** `propertyPack.parking.controlledParking.costOfPermitFrequency`
|
|
60
|
+
|
|
61
|
+
**UI:** Render alongside the existing cost of permit field (previously titled "Annual cost of permit (£)", now retitled to "Cost of permit (£)"). The frequency dropdown clarifies whether the cost amount is monthly or annual.
|
|
62
|
+
|
|
63
|
+
| Field | Path (relative) | Type | Validation | UI Element |
|
|
64
|
+
|-------|-----------------|------|------------|------------|
|
|
65
|
+
| Payment frequency | `costOfPermitFrequency` | `string` | Enum | Dropdown |
|
|
66
|
+
|
|
67
|
+
**Frequency enum values:** `"Per month"`, `"Per year"`
|
|
68
|
+
|
|
69
|
+
**Note:** The existing `annualCostOfPermit` numeric field remains unchanged. The new frequency field sits beside it to indicate whether the entered amount is per month or per year.
|
|
70
|
+
|
|
71
|
+
**Ref:** `P1.1`
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 3. Property Hazards (`ph`)
|
|
76
|
+
|
|
77
|
+
**Trigger:** Always shown (no conditional trigger — these are standalone questions within Specialist Issues).
|
|
78
|
+
|
|
79
|
+
**Path prefix:** `propertyPack.specialistIssues`
|
|
80
|
+
|
|
81
|
+
All 4 fields follow the same pattern:
|
|
82
|
+
|
|
83
|
+
- **Initial question:** Yes/No radio/toggle
|
|
84
|
+
- **If "Yes":** Show a required free-text "Give details" field
|
|
85
|
+
|
|
86
|
+
### Fields
|
|
87
|
+
|
|
88
|
+
| Property | Title | sef25Ref |
|
|
89
|
+
|----------|-------|----------|
|
|
90
|
+
| `wellsDitchesShaft` | Are there any wells, ditches, or shafts at the property? | H1.1 |
|
|
91
|
+
| `damagedOrExposedElectrics` | Are there any damaged or exposed electrics at the property? | H1.2 |
|
|
92
|
+
| `damageToFlooringOrStaircases` | Is there any damage to flooring and/or staircases? | H1.3 |
|
|
93
|
+
| `knownAreasInPoorCondition` | Are there any known areas in poor condition (internal or external)? | H1.4 |
|
|
94
|
+
|
|
95
|
+
### Data shape per field
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
// When "No":
|
|
99
|
+
{ "yesNo": "No" }
|
|
100
|
+
|
|
101
|
+
// When "Yes":
|
|
102
|
+
{ "yesNo": "Yes", "details": "Free text description..." }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Validation
|
|
106
|
+
|
|
107
|
+
- `yesNo` is required (enum: `["Yes", "No"]`)
|
|
108
|
+
- When `yesNo` = `"Yes"`, `details` is required (string, minLength: 1)
|
|
109
|
+
- When `yesNo` = `"No"`, `details` should not be shown or submitted
|
|
110
|
+
|
|
111
|
+
### UI pattern
|
|
112
|
+
|
|
113
|
+
These follow the identical pattern to existing specialist issues (dry rot, asbestos, subsidence, health & safety). Render them the same way — group them under "Hazards and Known Issues" within the Specialist Issues section.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Loading the Extensions
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
import { getTransactionSchema, getValidator } from "@pdtf/schemas";
|
|
121
|
+
|
|
122
|
+
const schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
|
|
123
|
+
|
|
124
|
+
// Load with one or more SEF25 extensions
|
|
125
|
+
const schema = getTransactionSchema(schemaId, ["sc", "pc", "ph"]);
|
|
126
|
+
|
|
127
|
+
// Or combine with other overlays
|
|
128
|
+
const schema = getTransactionSchema(schemaId, ["baspiV5", "sc", "pc", "ph"]);
|
|
129
|
+
|
|
130
|
+
// Validate data against the merged schema
|
|
131
|
+
const validator = getValidator(schemaId, ["sc", "pc", "ph"]);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Summary of Trigger Conditions
|
|
137
|
+
|
|
138
|
+
| Field | Visible when... |
|
|
139
|
+
|-------|----------------|
|
|
140
|
+
| Water cost | `mainsWater.yesNo` = `"No"` |
|
|
141
|
+
| Sewerage cost | `mainsFoulDrainage.yesNo` = `"No"` or `"Not known"` |
|
|
142
|
+
| Parking permit frequency | `controlledParking.yesNo` = `"Yes"` |
|
|
143
|
+
| Wells/ditches/shaft | Always (within Specialist Issues) |
|
|
144
|
+
| Damaged electrics | Always (within Specialist Issues) |
|
|
145
|
+
| Flooring/staircase damage | Always (within Specialist Issues) |
|
|
146
|
+
| Areas in poor condition | Always (within Specialist Issues) |
|
package/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const cacheStats = {
|
|
|
18
18
|
hits: 0,
|
|
19
19
|
misses: 0,
|
|
20
20
|
totalQueries: 0,
|
|
21
|
-
cacheStartTime: Date.now()
|
|
21
|
+
cacheStartTime: Date.now(),
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const verifiedClaimsSchema = require("./src/schemas/verifiedClaims/pdtf-verified-claims.json");
|
|
@@ -52,6 +52,11 @@ const extensionOverlays = {
|
|
|
52
52
|
|
|
53
53
|
// Transaction Extensions
|
|
54
54
|
oc: require("./src/schemas/v3/overlays/extensions/oc.json"),
|
|
55
|
+
|
|
56
|
+
// SEF25 Extensions
|
|
57
|
+
sc: require("./src/schemas/v3/overlays/extensions/sc.json"),
|
|
58
|
+
pc: require("./src/schemas/v3/overlays/extensions/pc.json"),
|
|
59
|
+
ph: require("./src/schemas/v3/overlays/extensions/ph.json"),
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
const overlaysMap = {
|
|
@@ -75,7 +80,9 @@ const overlaysMap = {
|
|
|
75
80
|
baspiV4: require("./src/schemas/v3/overlays/baspi4.json"),
|
|
76
81
|
baspiV5: require("./src/schemas/v3/overlays/baspi5.json"),
|
|
77
82
|
ta6ed4: require("./src/schemas/v3/overlays/ta6.json"),
|
|
83
|
+
ta6ed6: require("./src/schemas/v3/overlays/ta6ed6.json"),
|
|
78
84
|
ta7ed3: require("./src/schemas/v3/overlays/ta7.json"),
|
|
85
|
+
ta7ed5: require("./src/schemas/v3/overlays/ta7ed5.json"),
|
|
79
86
|
ta10ed3: require("./src/schemas/v3/overlays/ta10.json"),
|
|
80
87
|
lpe1ed4: require("./src/schemas/v3/overlays/lpe1.json"),
|
|
81
88
|
fme1ed2: require("./src/schemas/v3/overlays/fme1.json"),
|
|
@@ -117,21 +124,27 @@ const arrayMerge = (target, source, options) => {
|
|
|
117
124
|
// Detect if this is a required array by checking the parent key
|
|
118
125
|
// Required arrays are property names (camelCase, no spaces)
|
|
119
126
|
// Enum arrays are values (can contain spaces, capitals, etc.)
|
|
120
|
-
const isRequiredArray =
|
|
121
|
-
(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
(item
|
|
134
|
-
|
|
127
|
+
const isRequiredArray =
|
|
128
|
+
target.every(
|
|
129
|
+
(item) =>
|
|
130
|
+
typeof item === "string" &&
|
|
131
|
+
item.length > 0 &&
|
|
132
|
+
// Required field names are typically camelCase without spaces
|
|
133
|
+
!item.includes(" ") &&
|
|
134
|
+
// First character is usually lowercase for field names
|
|
135
|
+
(item[0] === item[0].toLowerCase() ||
|
|
136
|
+
item.startsWith("is") ||
|
|
137
|
+
item.startsWith("has")),
|
|
138
|
+
) &&
|
|
139
|
+
source.every(
|
|
140
|
+
(item) =>
|
|
141
|
+
typeof item === "string" &&
|
|
142
|
+
item.length > 0 &&
|
|
143
|
+
!item.includes(" ") &&
|
|
144
|
+
(item[0] === item[0].toLowerCase() ||
|
|
145
|
+
item.startsWith("is") ||
|
|
146
|
+
item.startsWith("has")),
|
|
147
|
+
);
|
|
135
148
|
|
|
136
149
|
if (isRequiredArray) {
|
|
137
150
|
// Combine required arrays
|
|
@@ -176,7 +189,7 @@ const arrayMerge = (target, source, options) => {
|
|
|
176
189
|
|
|
177
190
|
const getTransactionSchema = (
|
|
178
191
|
schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
|
|
179
|
-
overlays
|
|
192
|
+
overlays,
|
|
180
193
|
) => {
|
|
181
194
|
const sourceSchema = transactionSchemas[schemaId];
|
|
182
195
|
if (!overlays || overlays.length < 1) return sourceSchema;
|
|
@@ -184,7 +197,7 @@ const getTransactionSchema = (
|
|
|
184
197
|
let mergedSchema = JSON.parse(JSON.stringify(sourceSchema)); // Deep copy
|
|
185
198
|
overlays.forEach((overlay) => {
|
|
186
199
|
const overlaySchema = JSON.parse(
|
|
187
|
-
JSON.stringify(overlaysMap[schemaId][overlay] || {})
|
|
200
|
+
JSON.stringify(overlaysMap[schemaId][overlay] || {}),
|
|
188
201
|
); // Deep copy
|
|
189
202
|
mergedSchema = merge(mergedSchema, overlaySchema, { arrayMerge });
|
|
190
203
|
});
|
|
@@ -211,7 +224,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
|
|
|
211
224
|
const sourceSchema = getTransactionSchema(schemaId, overlays);
|
|
212
225
|
const pathArray = path.split("/").slice(1);
|
|
213
226
|
let subSchema = sourceSchema;
|
|
214
|
-
|
|
227
|
+
|
|
215
228
|
if (pathArray.length >= 1) {
|
|
216
229
|
subSchema = pathArray.reduce((schema, pathElement) => {
|
|
217
230
|
if (!schema) return undefined;
|
|
@@ -233,21 +246,52 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
|
|
|
233
246
|
}
|
|
234
247
|
} catch (e) {
|
|
235
248
|
// Invalid regex pattern in schema - skip it
|
|
236
|
-
console.warn(
|
|
249
|
+
console.warn(
|
|
250
|
+
`Invalid regex pattern in patternProperties: ${pattern}`,
|
|
251
|
+
e,
|
|
252
|
+
);
|
|
237
253
|
}
|
|
238
254
|
}
|
|
239
255
|
}
|
|
240
256
|
|
|
241
|
-
// Check oneOf discriminators
|
|
257
|
+
// Check oneOf discriminators (recursively for nested oneOf)
|
|
242
258
|
if (oneOf) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
259
|
+
const findInOneOf = (branches) => {
|
|
260
|
+
let arrayFallback;
|
|
261
|
+
for (const branch of branches) {
|
|
262
|
+
if (!branch) continue;
|
|
263
|
+
// Track array matches as fallback (prefer property matches)
|
|
264
|
+
if (
|
|
265
|
+
branch.type === "array" &&
|
|
266
|
+
!Number.isNaN(pathElement) &&
|
|
267
|
+
!arrayFallback
|
|
268
|
+
) {
|
|
269
|
+
arrayFallback = branch.items;
|
|
270
|
+
}
|
|
271
|
+
if (branch.properties?.[pathElement]) {
|
|
272
|
+
return branch.properties[pathElement];
|
|
273
|
+
}
|
|
274
|
+
if (branch.patternProperties) {
|
|
275
|
+
for (const pattern in branch.patternProperties) {
|
|
276
|
+
try {
|
|
277
|
+
const regex = new RegExp(pattern);
|
|
278
|
+
if (regex.test(pathElement)) {
|
|
279
|
+
return branch.patternProperties[pattern];
|
|
280
|
+
}
|
|
281
|
+
} catch (e) {
|
|
282
|
+
// Invalid regex pattern - skip
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Recurse into nested oneOf branches
|
|
287
|
+
if (branch.oneOf) {
|
|
288
|
+
const nested = findInOneOf(branch.oneOf);
|
|
289
|
+
if (nested) return nested;
|
|
290
|
+
}
|
|
249
291
|
}
|
|
250
|
-
|
|
292
|
+
return arrayFallback;
|
|
293
|
+
};
|
|
294
|
+
const matchingProperty = findInOneOf(oneOf);
|
|
251
295
|
if (matchingProperty) return matchingProperty;
|
|
252
296
|
}
|
|
253
297
|
|
|
@@ -258,7 +302,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
|
|
|
258
302
|
// Add schema to AJV and get validator
|
|
259
303
|
// Only add valid schemas to AJV
|
|
260
304
|
let validator;
|
|
261
|
-
if (subSchema && typeof subSchema ===
|
|
305
|
+
if (subSchema && typeof subSchema === "object") {
|
|
262
306
|
// Check if schema already exists in AJV to avoid duplicates
|
|
263
307
|
validator = ajv.getSchema(cacheKey);
|
|
264
308
|
if (!validator) {
|
|
@@ -275,7 +319,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
|
|
|
275
319
|
subSchema,
|
|
276
320
|
validator,
|
|
277
321
|
cacheKey,
|
|
278
|
-
createdAt: Date.now()
|
|
322
|
+
createdAt: Date.now(),
|
|
279
323
|
};
|
|
280
324
|
schemaCache.set(cacheKey, cached);
|
|
281
325
|
} else {
|
|
@@ -341,13 +385,13 @@ const getTitleAtPath = (schema, path, rootPath = path) => {
|
|
|
341
385
|
if (oneOfs) {
|
|
342
386
|
// only single dependency discriminator, oneOf keyword is supported
|
|
343
387
|
const matchingOneOf = oneOfs.find(
|
|
344
|
-
(oneOf) => oneOf["properties"][propertyName]
|
|
388
|
+
(oneOf) => oneOf["properties"][propertyName],
|
|
345
389
|
);
|
|
346
390
|
if (matchingOneOf)
|
|
347
391
|
return getTitleAtPath(
|
|
348
392
|
matchingOneOf["properties"][propertyName],
|
|
349
393
|
subPath,
|
|
350
|
-
rootPath
|
|
394
|
+
rootPath,
|
|
351
395
|
);
|
|
352
396
|
}
|
|
353
397
|
};
|
|
@@ -380,7 +424,7 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
|
|
|
380
424
|
}
|
|
381
425
|
} else {
|
|
382
426
|
validationErrorsArr.push(
|
|
383
|
-
`Path ${path} is not a valid PDTF schema path
|
|
427
|
+
`Path ${path} is not a valid PDTF schema path`,
|
|
384
428
|
);
|
|
385
429
|
}
|
|
386
430
|
}
|
|
@@ -392,8 +436,11 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
|
|
|
392
436
|
// Cache management functions
|
|
393
437
|
const getCacheStats = () => {
|
|
394
438
|
const runtime = Date.now() - cacheStats.cacheStartTime;
|
|
395
|
-
const hitRate =
|
|
396
|
-
|
|
439
|
+
const hitRate =
|
|
440
|
+
cacheStats.totalQueries > 0
|
|
441
|
+
? (cacheStats.hits / cacheStats.totalQueries) * 100
|
|
442
|
+
: 0;
|
|
443
|
+
|
|
397
444
|
return {
|
|
398
445
|
totalEntries: schemaCache.size,
|
|
399
446
|
hits: cacheStats.hits,
|
|
@@ -405,8 +452,8 @@ const getCacheStats = () => {
|
|
|
405
452
|
memoryUsage: {
|
|
406
453
|
entriesCount: schemaCache.size,
|
|
407
454
|
// Rough estimate of memory usage per entry
|
|
408
|
-
estimatedSizeKB: Math.round((schemaCache.size * 2) / 1024 * 100) / 100 // Rough estimate
|
|
409
|
-
}
|
|
455
|
+
estimatedSizeKB: Math.round(((schemaCache.size * 2) / 1024) * 100) / 100, // Rough estimate
|
|
456
|
+
},
|
|
410
457
|
};
|
|
411
458
|
};
|
|
412
459
|
|
|
@@ -416,15 +463,17 @@ const getDetailedCacheStats = () => {
|
|
|
416
463
|
key,
|
|
417
464
|
createdAt: value.createdAt,
|
|
418
465
|
age: Date.now() - value.createdAt,
|
|
419
|
-
hasValidator: typeof value.validator ===
|
|
420
|
-
hasSubSchema: value.subSchema !== undefined
|
|
466
|
+
hasValidator: typeof value.validator === "function",
|
|
467
|
+
hasSubSchema: value.subSchema !== undefined,
|
|
421
468
|
}));
|
|
422
469
|
|
|
423
470
|
return {
|
|
424
471
|
...baseStats,
|
|
425
472
|
entries: entries.sort((a, b) => b.createdAt - a.createdAt), // Most recent first
|
|
426
|
-
oldestEntry:
|
|
427
|
-
|
|
473
|
+
oldestEntry:
|
|
474
|
+
entries.length > 0 ? Math.max(...entries.map((e) => e.age)) : 0,
|
|
475
|
+
newestEntry:
|
|
476
|
+
entries.length > 0 ? Math.min(...entries.map((e) => e.age)) : 0,
|
|
428
477
|
};
|
|
429
478
|
};
|
|
430
479
|
|
|
@@ -437,17 +486,17 @@ const clearSchemaCache = (pattern) => {
|
|
|
437
486
|
keysToDelete.push(key);
|
|
438
487
|
}
|
|
439
488
|
});
|
|
440
|
-
keysToDelete.forEach(key => schemaCache.delete(key));
|
|
441
|
-
|
|
489
|
+
keysToDelete.forEach((key) => schemaCache.delete(key));
|
|
490
|
+
|
|
442
491
|
// Also remove matching schemas from AJV
|
|
443
|
-
keysToDelete.forEach(key => {
|
|
492
|
+
keysToDelete.forEach((key) => {
|
|
444
493
|
try {
|
|
445
494
|
ajv.removeSchema(key);
|
|
446
495
|
} catch (e) {
|
|
447
496
|
// Schema might not exist in AJV, ignore
|
|
448
497
|
}
|
|
449
498
|
});
|
|
450
|
-
|
|
499
|
+
|
|
451
500
|
return keysToDelete.length;
|
|
452
501
|
} else {
|
|
453
502
|
// Clear all cache
|
|
@@ -458,20 +507,24 @@ const clearSchemaCache = (pattern) => {
|
|
|
458
507
|
cacheStats.misses = 0;
|
|
459
508
|
cacheStats.totalQueries = 0;
|
|
460
509
|
cacheStats.cacheStartTime = Date.now();
|
|
461
|
-
|
|
510
|
+
|
|
462
511
|
// Also clear AJV's internal cache to prevent duplicates
|
|
463
512
|
ajv.removeSchema();
|
|
464
|
-
|
|
513
|
+
|
|
465
514
|
return entriesCleared;
|
|
466
515
|
}
|
|
467
516
|
};
|
|
468
517
|
|
|
469
|
-
const warmupCache = (
|
|
518
|
+
const warmupCache = (
|
|
519
|
+
paths,
|
|
520
|
+
schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
|
|
521
|
+
overlaysList = [],
|
|
522
|
+
) => {
|
|
470
523
|
const warmupStats = {
|
|
471
524
|
totalAttempted: 0,
|
|
472
525
|
successful: 0,
|
|
473
526
|
failed: 0,
|
|
474
|
-
errors: []
|
|
527
|
+
errors: [],
|
|
475
528
|
};
|
|
476
529
|
|
|
477
530
|
// Default common paths if none provided
|
|
@@ -483,7 +536,7 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
|
|
|
483
536
|
"/propertyPack/valuations",
|
|
484
537
|
"/propertyPack/waterAndDrainage",
|
|
485
538
|
"/propertyPack/ownership",
|
|
486
|
-
"/propertyPack/notices"
|
|
539
|
+
"/propertyPack/notices",
|
|
487
540
|
];
|
|
488
541
|
|
|
489
542
|
// Default common overlays if none provided
|
|
@@ -492,14 +545,15 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
|
|
|
492
545
|
["baspiV5"],
|
|
493
546
|
["ta6ed4"],
|
|
494
547
|
["nts2023"],
|
|
495
|
-
["baspiV5", "ta6ed4"]
|
|
548
|
+
["baspiV5", "ta6ed4"],
|
|
496
549
|
];
|
|
497
550
|
|
|
498
551
|
const pathsToWarm = paths || defaultPaths;
|
|
499
|
-
const overlaysToWarm =
|
|
552
|
+
const overlaysToWarm =
|
|
553
|
+
overlaysList.length > 0 ? overlaysList : defaultOverlays;
|
|
500
554
|
|
|
501
|
-
pathsToWarm.forEach(path => {
|
|
502
|
-
overlaysToWarm.forEach(overlays => {
|
|
555
|
+
pathsToWarm.forEach((path) => {
|
|
556
|
+
overlaysToWarm.forEach((overlays) => {
|
|
503
557
|
warmupStats.totalAttempted++;
|
|
504
558
|
try {
|
|
505
559
|
// This will populate the cache
|
|
@@ -511,7 +565,7 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
|
|
|
511
565
|
warmupStats.errors.push({
|
|
512
566
|
path,
|
|
513
567
|
overlays,
|
|
514
|
-
error: error.message
|
|
568
|
+
error: error.message,
|
|
515
569
|
});
|
|
516
570
|
}
|
|
517
571
|
});
|
|
@@ -523,14 +577,14 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
|
|
|
523
577
|
const pruneCacheByAge = (maxAgeMs) => {
|
|
524
578
|
const now = Date.now();
|
|
525
579
|
const keysToDelete = [];
|
|
526
|
-
|
|
580
|
+
|
|
527
581
|
schemaCache.forEach((value, key) => {
|
|
528
582
|
if (now - value.createdAt > maxAgeMs) {
|
|
529
583
|
keysToDelete.push(key);
|
|
530
584
|
}
|
|
531
585
|
});
|
|
532
|
-
|
|
533
|
-
keysToDelete.forEach(key => {
|
|
586
|
+
|
|
587
|
+
keysToDelete.forEach((key) => {
|
|
534
588
|
schemaCache.delete(key);
|
|
535
589
|
try {
|
|
536
590
|
ajv.removeSchema(key);
|
|
@@ -538,21 +592,22 @@ const pruneCacheByAge = (maxAgeMs) => {
|
|
|
538
592
|
// Schema might not exist in AJV, ignore
|
|
539
593
|
}
|
|
540
594
|
});
|
|
541
|
-
|
|
595
|
+
|
|
542
596
|
return keysToDelete.length;
|
|
543
597
|
};
|
|
544
598
|
|
|
545
599
|
const setCacheMaxSize = (maxSize) => {
|
|
546
600
|
if (schemaCache.size <= maxSize) return 0;
|
|
547
|
-
|
|
601
|
+
|
|
548
602
|
// Get entries sorted by creation time (oldest first)
|
|
549
|
-
const entries = Array.from(schemaCache.entries())
|
|
550
|
-
|
|
551
|
-
|
|
603
|
+
const entries = Array.from(schemaCache.entries()).sort(
|
|
604
|
+
(a, b) => a[1].createdAt - b[1].createdAt,
|
|
605
|
+
);
|
|
606
|
+
|
|
552
607
|
const toRemove = schemaCache.size - maxSize;
|
|
553
608
|
const keysToDelete = entries.slice(0, toRemove).map(([key]) => key);
|
|
554
|
-
|
|
555
|
-
keysToDelete.forEach(key => {
|
|
609
|
+
|
|
610
|
+
keysToDelete.forEach((key) => {
|
|
556
611
|
schemaCache.delete(key);
|
|
557
612
|
try {
|
|
558
613
|
ajv.removeSchema(key);
|
|
@@ -560,7 +615,7 @@ const setCacheMaxSize = (maxSize) => {
|
|
|
560
615
|
// Schema might not exist in AJV, ignore
|
|
561
616
|
}
|
|
562
617
|
});
|
|
563
|
-
|
|
618
|
+
|
|
564
619
|
return keysToDelete.length;
|
|
565
620
|
};
|
|
566
621
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pdtf/schemas",
|
|
3
|
-
"version": "3.6.0-
|
|
3
|
+
"version": "3.6.0-31",
|
|
4
4
|
"description": "Property Data Trust Framework Schemas and Utilities",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "jest",
|
|
11
|
-
"test:watch": "jest --watch"
|
|
11
|
+
"test:watch": "jest --watch",
|
|
12
|
+
"extract-overlays": "cd src/utils && node extractOverlay.js",
|
|
13
|
+
"extract-extension-overlays": "cd src/utils && node extractExtensionOverlays.js"
|
|
12
14
|
},
|
|
13
15
|
"repository": {
|
|
14
16
|
"type": "git",
|