@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.
Files changed (37) hide show
  1. package/.claude/settings.local.json +22 -3
  2. package/docs/sef25-extensions-ui-spec.md +146 -0
  3. package/index.js +121 -66
  4. package/package.json +4 -2
  5. package/src/schemas/v3/combined.json +7123 -1363
  6. package/src/schemas/v3/compactSkeleton.txt +351 -12
  7. package/src/schemas/v3/overlays/baspi4.json +163 -18
  8. package/src/schemas/v3/overlays/baspi5.json +168 -20
  9. package/src/schemas/v3/overlays/extensions/dr.json +5 -7
  10. package/src/schemas/v3/overlays/extensions/jk.json +24 -6
  11. package/src/schemas/v3/overlays/extensions/pc.json +36 -0
  12. package/src/schemas/v3/overlays/extensions/ph.json +197 -0
  13. package/src/schemas/v3/overlays/extensions/sc.json +83 -0
  14. package/src/schemas/v3/overlays/fme1.json +16 -14
  15. package/src/schemas/v3/overlays/lpe1.json +60 -30
  16. package/src/schemas/v3/overlays/nts.json +68 -1
  17. package/src/schemas/v3/overlays/nts2.json +68 -4
  18. package/src/schemas/v3/overlays/ntsl.json +68 -1
  19. package/src/schemas/v3/overlays/ntsl2.json +68 -4
  20. package/src/schemas/v3/overlays/piq.json +38 -1
  21. package/src/schemas/v3/overlays/rds.json +118 -6
  22. package/src/schemas/v3/overlays/ta6.json +46 -16
  23. package/src/schemas/v3/overlays/ta6ed6.json +6498 -0
  24. package/src/schemas/v3/overlays/ta7.json +70 -6
  25. package/src/schemas/v3/overlays/ta7ed5.json +1539 -0
  26. package/src/schemas/v3/pdtf-transaction.json +4335 -798
  27. package/src/schemas/v3/skeleton.json +486 -34
  28. package/src/tests/v3/buyerCircumstances.test.js +543 -0
  29. package/src/tests/v3/extensionOverlays.test.js +436 -0
  30. package/src/tests/v3/offers.test.js +37 -40
  31. package/src/tests/v3/patternProperties.test.js +149 -30
  32. package/src/tests/v3/ta6ed6.test.js +2317 -0
  33. package/src/tests/v3/ta7ed5.test.js +567 -0
  34. package/src/tests/v3/transactionSchema.test.js +31 -0
  35. package/src/utils/extractExtensionOverlays.js +104 -31
  36. package/src/utils/extractOverlay.js +87 -23
  37. 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 = target.every(
121
- (item) =>
122
- typeof item === "string" &&
123
- item.length > 0 &&
124
- // Required field names are typically camelCase without spaces
125
- !item.includes(" ") &&
126
- // First character is usually lowercase for field names
127
- (item[0] === item[0].toLowerCase() || item.startsWith("is") || item.startsWith("has"))
128
- ) && source.every(
129
- (item) =>
130
- typeof item === "string" &&
131
- item.length > 0 &&
132
- !item.includes(" ") &&
133
- (item[0] === item[0].toLowerCase() || item.startsWith("is") || item.startsWith("has"))
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(`Invalid regex pattern in patternProperties: ${pattern}`, e);
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
- let matchingProperty;
244
- oneOf.forEach((aOneOf) => {
245
- if (aOneOf.type === "array" && !Number.isNaN(pathElement)) {
246
- matchingProperty = aOneOf.items;
247
- } else if (aOneOf.properties?.[pathElement]) {
248
- matchingProperty = aOneOf.properties?.[pathElement];
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 === 'object') {
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 = cacheStats.totalQueries > 0 ? (cacheStats.hits / cacheStats.totalQueries * 100) : 0;
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 === 'function',
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: entries.length > 0 ? Math.max(...entries.map(e => e.age)) : 0,
427
- newestEntry: entries.length > 0 ? Math.min(...entries.map(e => e.age)) : 0
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 = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json", overlaysList = []) => {
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 = overlaysList.length > 0 ? overlaysList : defaultOverlays;
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
- .sort((a, b) => a[1].createdAt - b[1].createdAt);
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",
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",