@pdtf/schemas 3.6.0-2 → 3.6.0-20
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 +17 -2
- package/docs/sef25-extensions-ui-spec.md +146 -0
- package/index.js +97 -58
- package/package.json +3 -2
- package/src/schemas/v3/combined.json +6997 -1310
- package/src/schemas/v3/compactSkeleton.txt +352 -10
- package/src/schemas/v3/overlays/baspi4.json +161 -17
- package/src/schemas/v3/overlays/baspi5.json +164 -17
- package/src/schemas/v3/overlays/extensions/jk.json +24 -6
- package/src/schemas/v3/overlays/extensions/pc.json +28 -0
- package/src/schemas/v3/overlays/extensions/ph.json +191 -0
- package/src/schemas/v3/overlays/extensions/sc.json +77 -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 -1
- package/src/schemas/v3/overlays/ntsl.json +68 -1
- package/src/schemas/v3/overlays/ntsl2.json +68 -1
- 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 +40 -14
- package/src/schemas/v3/overlays/ta6ed6.json +6436 -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 +4077 -611
- package/src/schemas/v3/skeleton.json +491 -34
- package/src/tests/v3/buyerCircumstances.test.js +543 -0
- package/src/tests/v3/extensionOverlays.test.js +433 -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 +2356 -0
- package/src/tests/v3/ta7ed5.test.js +567 -0
- package/src/utils/extractExtensionOverlays.js +103 -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,7 +34,23 @@
|
|
|
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
55
|
"deny": []
|
|
41
56
|
}
|
|
@@ -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,7 +246,10 @@ 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
|
}
|
|
@@ -246,6 +262,18 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
|
|
|
246
262
|
matchingProperty = aOneOf.items;
|
|
247
263
|
} else if (aOneOf.properties?.[pathElement]) {
|
|
248
264
|
matchingProperty = aOneOf.properties?.[pathElement];
|
|
265
|
+
} else if (aOneOf.patternProperties) {
|
|
266
|
+
for (const pattern in aOneOf.patternProperties) {
|
|
267
|
+
try {
|
|
268
|
+
const regex = new RegExp(pattern);
|
|
269
|
+
if (regex.test(pathElement)) {
|
|
270
|
+
matchingProperty = aOneOf.patternProperties[pattern];
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
} catch (e) {
|
|
274
|
+
// Invalid regex pattern - skip
|
|
275
|
+
}
|
|
276
|
+
}
|
|
249
277
|
}
|
|
250
278
|
});
|
|
251
279
|
if (matchingProperty) return matchingProperty;
|
|
@@ -258,7 +286,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
|
|
|
258
286
|
// Add schema to AJV and get validator
|
|
259
287
|
// Only add valid schemas to AJV
|
|
260
288
|
let validator;
|
|
261
|
-
if (subSchema && typeof subSchema ===
|
|
289
|
+
if (subSchema && typeof subSchema === "object") {
|
|
262
290
|
// Check if schema already exists in AJV to avoid duplicates
|
|
263
291
|
validator = ajv.getSchema(cacheKey);
|
|
264
292
|
if (!validator) {
|
|
@@ -275,7 +303,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
|
|
|
275
303
|
subSchema,
|
|
276
304
|
validator,
|
|
277
305
|
cacheKey,
|
|
278
|
-
createdAt: Date.now()
|
|
306
|
+
createdAt: Date.now(),
|
|
279
307
|
};
|
|
280
308
|
schemaCache.set(cacheKey, cached);
|
|
281
309
|
} else {
|
|
@@ -341,13 +369,13 @@ const getTitleAtPath = (schema, path, rootPath = path) => {
|
|
|
341
369
|
if (oneOfs) {
|
|
342
370
|
// only single dependency discriminator, oneOf keyword is supported
|
|
343
371
|
const matchingOneOf = oneOfs.find(
|
|
344
|
-
(oneOf) => oneOf["properties"][propertyName]
|
|
372
|
+
(oneOf) => oneOf["properties"][propertyName],
|
|
345
373
|
);
|
|
346
374
|
if (matchingOneOf)
|
|
347
375
|
return getTitleAtPath(
|
|
348
376
|
matchingOneOf["properties"][propertyName],
|
|
349
377
|
subPath,
|
|
350
|
-
rootPath
|
|
378
|
+
rootPath,
|
|
351
379
|
);
|
|
352
380
|
}
|
|
353
381
|
};
|
|
@@ -380,7 +408,7 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
|
|
|
380
408
|
}
|
|
381
409
|
} else {
|
|
382
410
|
validationErrorsArr.push(
|
|
383
|
-
`Path ${path} is not a valid PDTF schema path
|
|
411
|
+
`Path ${path} is not a valid PDTF schema path`,
|
|
384
412
|
);
|
|
385
413
|
}
|
|
386
414
|
}
|
|
@@ -392,8 +420,11 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
|
|
|
392
420
|
// Cache management functions
|
|
393
421
|
const getCacheStats = () => {
|
|
394
422
|
const runtime = Date.now() - cacheStats.cacheStartTime;
|
|
395
|
-
const hitRate =
|
|
396
|
-
|
|
423
|
+
const hitRate =
|
|
424
|
+
cacheStats.totalQueries > 0
|
|
425
|
+
? (cacheStats.hits / cacheStats.totalQueries) * 100
|
|
426
|
+
: 0;
|
|
427
|
+
|
|
397
428
|
return {
|
|
398
429
|
totalEntries: schemaCache.size,
|
|
399
430
|
hits: cacheStats.hits,
|
|
@@ -405,8 +436,8 @@ const getCacheStats = () => {
|
|
|
405
436
|
memoryUsage: {
|
|
406
437
|
entriesCount: schemaCache.size,
|
|
407
438
|
// Rough estimate of memory usage per entry
|
|
408
|
-
estimatedSizeKB: Math.round((schemaCache.size * 2) / 1024 * 100) / 100 // Rough estimate
|
|
409
|
-
}
|
|
439
|
+
estimatedSizeKB: Math.round(((schemaCache.size * 2) / 1024) * 100) / 100, // Rough estimate
|
|
440
|
+
},
|
|
410
441
|
};
|
|
411
442
|
};
|
|
412
443
|
|
|
@@ -416,15 +447,17 @@ const getDetailedCacheStats = () => {
|
|
|
416
447
|
key,
|
|
417
448
|
createdAt: value.createdAt,
|
|
418
449
|
age: Date.now() - value.createdAt,
|
|
419
|
-
hasValidator: typeof value.validator ===
|
|
420
|
-
hasSubSchema: value.subSchema !== undefined
|
|
450
|
+
hasValidator: typeof value.validator === "function",
|
|
451
|
+
hasSubSchema: value.subSchema !== undefined,
|
|
421
452
|
}));
|
|
422
453
|
|
|
423
454
|
return {
|
|
424
455
|
...baseStats,
|
|
425
456
|
entries: entries.sort((a, b) => b.createdAt - a.createdAt), // Most recent first
|
|
426
|
-
oldestEntry:
|
|
427
|
-
|
|
457
|
+
oldestEntry:
|
|
458
|
+
entries.length > 0 ? Math.max(...entries.map((e) => e.age)) : 0,
|
|
459
|
+
newestEntry:
|
|
460
|
+
entries.length > 0 ? Math.min(...entries.map((e) => e.age)) : 0,
|
|
428
461
|
};
|
|
429
462
|
};
|
|
430
463
|
|
|
@@ -437,17 +470,17 @@ const clearSchemaCache = (pattern) => {
|
|
|
437
470
|
keysToDelete.push(key);
|
|
438
471
|
}
|
|
439
472
|
});
|
|
440
|
-
keysToDelete.forEach(key => schemaCache.delete(key));
|
|
441
|
-
|
|
473
|
+
keysToDelete.forEach((key) => schemaCache.delete(key));
|
|
474
|
+
|
|
442
475
|
// Also remove matching schemas from AJV
|
|
443
|
-
keysToDelete.forEach(key => {
|
|
476
|
+
keysToDelete.forEach((key) => {
|
|
444
477
|
try {
|
|
445
478
|
ajv.removeSchema(key);
|
|
446
479
|
} catch (e) {
|
|
447
480
|
// Schema might not exist in AJV, ignore
|
|
448
481
|
}
|
|
449
482
|
});
|
|
450
|
-
|
|
483
|
+
|
|
451
484
|
return keysToDelete.length;
|
|
452
485
|
} else {
|
|
453
486
|
// Clear all cache
|
|
@@ -458,20 +491,24 @@ const clearSchemaCache = (pattern) => {
|
|
|
458
491
|
cacheStats.misses = 0;
|
|
459
492
|
cacheStats.totalQueries = 0;
|
|
460
493
|
cacheStats.cacheStartTime = Date.now();
|
|
461
|
-
|
|
494
|
+
|
|
462
495
|
// Also clear AJV's internal cache to prevent duplicates
|
|
463
496
|
ajv.removeSchema();
|
|
464
|
-
|
|
497
|
+
|
|
465
498
|
return entriesCleared;
|
|
466
499
|
}
|
|
467
500
|
};
|
|
468
501
|
|
|
469
|
-
const warmupCache = (
|
|
502
|
+
const warmupCache = (
|
|
503
|
+
paths,
|
|
504
|
+
schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
|
|
505
|
+
overlaysList = [],
|
|
506
|
+
) => {
|
|
470
507
|
const warmupStats = {
|
|
471
508
|
totalAttempted: 0,
|
|
472
509
|
successful: 0,
|
|
473
510
|
failed: 0,
|
|
474
|
-
errors: []
|
|
511
|
+
errors: [],
|
|
475
512
|
};
|
|
476
513
|
|
|
477
514
|
// Default common paths if none provided
|
|
@@ -483,7 +520,7 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
|
|
|
483
520
|
"/propertyPack/valuations",
|
|
484
521
|
"/propertyPack/waterAndDrainage",
|
|
485
522
|
"/propertyPack/ownership",
|
|
486
|
-
"/propertyPack/notices"
|
|
523
|
+
"/propertyPack/notices",
|
|
487
524
|
];
|
|
488
525
|
|
|
489
526
|
// Default common overlays if none provided
|
|
@@ -492,14 +529,15 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
|
|
|
492
529
|
["baspiV5"],
|
|
493
530
|
["ta6ed4"],
|
|
494
531
|
["nts2023"],
|
|
495
|
-
["baspiV5", "ta6ed4"]
|
|
532
|
+
["baspiV5", "ta6ed4"],
|
|
496
533
|
];
|
|
497
534
|
|
|
498
535
|
const pathsToWarm = paths || defaultPaths;
|
|
499
|
-
const overlaysToWarm =
|
|
536
|
+
const overlaysToWarm =
|
|
537
|
+
overlaysList.length > 0 ? overlaysList : defaultOverlays;
|
|
500
538
|
|
|
501
|
-
pathsToWarm.forEach(path => {
|
|
502
|
-
overlaysToWarm.forEach(overlays => {
|
|
539
|
+
pathsToWarm.forEach((path) => {
|
|
540
|
+
overlaysToWarm.forEach((overlays) => {
|
|
503
541
|
warmupStats.totalAttempted++;
|
|
504
542
|
try {
|
|
505
543
|
// This will populate the cache
|
|
@@ -511,7 +549,7 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
|
|
|
511
549
|
warmupStats.errors.push({
|
|
512
550
|
path,
|
|
513
551
|
overlays,
|
|
514
|
-
error: error.message
|
|
552
|
+
error: error.message,
|
|
515
553
|
});
|
|
516
554
|
}
|
|
517
555
|
});
|
|
@@ -523,14 +561,14 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
|
|
|
523
561
|
const pruneCacheByAge = (maxAgeMs) => {
|
|
524
562
|
const now = Date.now();
|
|
525
563
|
const keysToDelete = [];
|
|
526
|
-
|
|
564
|
+
|
|
527
565
|
schemaCache.forEach((value, key) => {
|
|
528
566
|
if (now - value.createdAt > maxAgeMs) {
|
|
529
567
|
keysToDelete.push(key);
|
|
530
568
|
}
|
|
531
569
|
});
|
|
532
|
-
|
|
533
|
-
keysToDelete.forEach(key => {
|
|
570
|
+
|
|
571
|
+
keysToDelete.forEach((key) => {
|
|
534
572
|
schemaCache.delete(key);
|
|
535
573
|
try {
|
|
536
574
|
ajv.removeSchema(key);
|
|
@@ -538,21 +576,22 @@ const pruneCacheByAge = (maxAgeMs) => {
|
|
|
538
576
|
// Schema might not exist in AJV, ignore
|
|
539
577
|
}
|
|
540
578
|
});
|
|
541
|
-
|
|
579
|
+
|
|
542
580
|
return keysToDelete.length;
|
|
543
581
|
};
|
|
544
582
|
|
|
545
583
|
const setCacheMaxSize = (maxSize) => {
|
|
546
584
|
if (schemaCache.size <= maxSize) return 0;
|
|
547
|
-
|
|
585
|
+
|
|
548
586
|
// Get entries sorted by creation time (oldest first)
|
|
549
|
-
const entries = Array.from(schemaCache.entries())
|
|
550
|
-
|
|
551
|
-
|
|
587
|
+
const entries = Array.from(schemaCache.entries()).sort(
|
|
588
|
+
(a, b) => a[1].createdAt - b[1].createdAt,
|
|
589
|
+
);
|
|
590
|
+
|
|
552
591
|
const toRemove = schemaCache.size - maxSize;
|
|
553
592
|
const keysToDelete = entries.slice(0, toRemove).map(([key]) => key);
|
|
554
|
-
|
|
555
|
-
keysToDelete.forEach(key => {
|
|
593
|
+
|
|
594
|
+
keysToDelete.forEach((key) => {
|
|
556
595
|
schemaCache.delete(key);
|
|
557
596
|
try {
|
|
558
597
|
ajv.removeSchema(key);
|
|
@@ -560,7 +599,7 @@ const setCacheMaxSize = (maxSize) => {
|
|
|
560
599
|
// Schema might not exist in AJV, ignore
|
|
561
600
|
}
|
|
562
601
|
});
|
|
563
|
-
|
|
602
|
+
|
|
564
603
|
return keysToDelete.length;
|
|
565
604
|
};
|
|
566
605
|
|
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-20",
|
|
4
4
|
"description": "Property Data Trust Framework Schemas and Utilities",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -8,7 +8,8 @@
|
|
|
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"
|
|
12
13
|
},
|
|
13
14
|
"repository": {
|
|
14
15
|
"type": "git",
|