@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
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,20 @@ 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"),
60
+ dk: require("./src/schemas/v3/overlays/extensions/dk.json"),
61
+ rw: require("./src/schemas/v3/overlays/extensions/rw.json"),
62
+ sd: require("./src/schemas/v3/overlays/extensions/sd.json"),
63
+ lc: require("./src/schemas/v3/overlays/extensions/lc.json"),
64
+ wg: require("./src/schemas/v3/overlays/extensions/wg.json"),
65
+ ic: require("./src/schemas/v3/overlays/extensions/ic.json"),
66
+ nd: require("./src/schemas/v3/overlays/extensions/nd.json"),
67
+ mi: require("./src/schemas/v3/overlays/extensions/mi.json"),
68
+ tr: require("./src/schemas/v3/overlays/extensions/tr.json"),
55
69
  };
56
70
 
57
71
  const overlaysMap = {
@@ -75,7 +89,9 @@ const overlaysMap = {
75
89
  baspiV4: require("./src/schemas/v3/overlays/baspi4.json"),
76
90
  baspiV5: require("./src/schemas/v3/overlays/baspi5.json"),
77
91
  ta6ed4: require("./src/schemas/v3/overlays/ta6.json"),
92
+ ta6ed6: require("./src/schemas/v3/overlays/ta6ed6.json"),
78
93
  ta7ed3: require("./src/schemas/v3/overlays/ta7.json"),
94
+ ta7ed5: require("./src/schemas/v3/overlays/ta7ed5.json"),
79
95
  ta10ed3: require("./src/schemas/v3/overlays/ta10.json"),
80
96
  lpe1ed4: require("./src/schemas/v3/overlays/lpe1.json"),
81
97
  fme1ed2: require("./src/schemas/v3/overlays/fme1.json"),
@@ -117,21 +133,27 @@ const arrayMerge = (target, source, options) => {
117
133
  // Detect if this is a required array by checking the parent key
118
134
  // Required arrays are property names (camelCase, no spaces)
119
135
  // 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
- );
136
+ const isRequiredArray =
137
+ target.every(
138
+ (item) =>
139
+ typeof item === "string" &&
140
+ item.length > 0 &&
141
+ // Required field names are typically camelCase without spaces
142
+ !item.includes(" ") &&
143
+ // First character is usually lowercase for field names
144
+ (item[0] === item[0].toLowerCase() ||
145
+ item.startsWith("is") ||
146
+ item.startsWith("has")),
147
+ ) &&
148
+ source.every(
149
+ (item) =>
150
+ typeof item === "string" &&
151
+ item.length > 0 &&
152
+ !item.includes(" ") &&
153
+ (item[0] === item[0].toLowerCase() ||
154
+ item.startsWith("is") ||
155
+ item.startsWith("has")),
156
+ );
135
157
 
136
158
  if (isRequiredArray) {
137
159
  // Combine required arrays
@@ -176,7 +198,7 @@ const arrayMerge = (target, source, options) => {
176
198
 
177
199
  const getTransactionSchema = (
178
200
  schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
179
- overlays
201
+ overlays,
180
202
  ) => {
181
203
  const sourceSchema = transactionSchemas[schemaId];
182
204
  if (!overlays || overlays.length < 1) return sourceSchema;
@@ -184,7 +206,7 @@ const getTransactionSchema = (
184
206
  let mergedSchema = JSON.parse(JSON.stringify(sourceSchema)); // Deep copy
185
207
  overlays.forEach((overlay) => {
186
208
  const overlaySchema = JSON.parse(
187
- JSON.stringify(overlaysMap[schemaId][overlay] || {})
209
+ JSON.stringify(overlaysMap[schemaId][overlay] || {}),
188
210
  ); // Deep copy
189
211
  mergedSchema = merge(mergedSchema, overlaySchema, { arrayMerge });
190
212
  });
@@ -211,7 +233,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
211
233
  const sourceSchema = getTransactionSchema(schemaId, overlays);
212
234
  const pathArray = path.split("/").slice(1);
213
235
  let subSchema = sourceSchema;
214
-
236
+
215
237
  if (pathArray.length >= 1) {
216
238
  subSchema = pathArray.reduce((schema, pathElement) => {
217
239
  if (!schema) return undefined;
@@ -233,21 +255,52 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
233
255
  }
234
256
  } catch (e) {
235
257
  // Invalid regex pattern in schema - skip it
236
- console.warn(`Invalid regex pattern in patternProperties: ${pattern}`, e);
258
+ console.warn(
259
+ `Invalid regex pattern in patternProperties: ${pattern}`,
260
+ e,
261
+ );
237
262
  }
238
263
  }
239
264
  }
240
265
 
241
- // Check oneOf discriminators
266
+ // Check oneOf discriminators (recursively for nested oneOf)
242
267
  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];
268
+ const findInOneOf = (branches) => {
269
+ let arrayFallback;
270
+ for (const branch of branches) {
271
+ if (!branch) continue;
272
+ // Track array matches as fallback (prefer property matches)
273
+ if (
274
+ branch.type === "array" &&
275
+ !Number.isNaN(pathElement) &&
276
+ !arrayFallback
277
+ ) {
278
+ arrayFallback = branch.items;
279
+ }
280
+ if (branch.properties?.[pathElement]) {
281
+ return branch.properties[pathElement];
282
+ }
283
+ if (branch.patternProperties) {
284
+ for (const pattern in branch.patternProperties) {
285
+ try {
286
+ const regex = new RegExp(pattern);
287
+ if (regex.test(pathElement)) {
288
+ return branch.patternProperties[pattern];
289
+ }
290
+ } catch (e) {
291
+ // Invalid regex pattern - skip
292
+ }
293
+ }
294
+ }
295
+ // Recurse into nested oneOf branches
296
+ if (branch.oneOf) {
297
+ const nested = findInOneOf(branch.oneOf);
298
+ if (nested) return nested;
299
+ }
249
300
  }
250
- });
301
+ return arrayFallback;
302
+ };
303
+ const matchingProperty = findInOneOf(oneOf);
251
304
  if (matchingProperty) return matchingProperty;
252
305
  }
253
306
 
@@ -258,7 +311,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
258
311
  // Add schema to AJV and get validator
259
312
  // Only add valid schemas to AJV
260
313
  let validator;
261
- if (subSchema && typeof subSchema === 'object') {
314
+ if (subSchema && typeof subSchema === "object") {
262
315
  // Check if schema already exists in AJV to avoid duplicates
263
316
  validator = ajv.getSchema(cacheKey);
264
317
  if (!validator) {
@@ -275,7 +328,7 @@ const getCachedSchemaData = (path, schemaId, overlays) => {
275
328
  subSchema,
276
329
  validator,
277
330
  cacheKey,
278
- createdAt: Date.now()
331
+ createdAt: Date.now(),
279
332
  };
280
333
  schemaCache.set(cacheKey, cached);
281
334
  } else {
@@ -341,13 +394,13 @@ const getTitleAtPath = (schema, path, rootPath = path) => {
341
394
  if (oneOfs) {
342
395
  // only single dependency discriminator, oneOf keyword is supported
343
396
  const matchingOneOf = oneOfs.find(
344
- (oneOf) => oneOf["properties"][propertyName]
397
+ (oneOf) => oneOf["properties"][propertyName],
345
398
  );
346
399
  if (matchingOneOf)
347
400
  return getTitleAtPath(
348
401
  matchingOneOf["properties"][propertyName],
349
402
  subPath,
350
- rootPath
403
+ rootPath,
351
404
  );
352
405
  }
353
406
  };
@@ -380,7 +433,7 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
380
433
  }
381
434
  } else {
382
435
  validationErrorsArr.push(
383
- `Path ${path} is not a valid PDTF schema path`
436
+ `Path ${path} is not a valid PDTF schema path`,
384
437
  );
385
438
  }
386
439
  }
@@ -392,8 +445,11 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
392
445
  // Cache management functions
393
446
  const getCacheStats = () => {
394
447
  const runtime = Date.now() - cacheStats.cacheStartTime;
395
- const hitRate = cacheStats.totalQueries > 0 ? (cacheStats.hits / cacheStats.totalQueries * 100) : 0;
396
-
448
+ const hitRate =
449
+ cacheStats.totalQueries > 0
450
+ ? (cacheStats.hits / cacheStats.totalQueries) * 100
451
+ : 0;
452
+
397
453
  return {
398
454
  totalEntries: schemaCache.size,
399
455
  hits: cacheStats.hits,
@@ -405,8 +461,8 @@ const getCacheStats = () => {
405
461
  memoryUsage: {
406
462
  entriesCount: schemaCache.size,
407
463
  // Rough estimate of memory usage per entry
408
- estimatedSizeKB: Math.round((schemaCache.size * 2) / 1024 * 100) / 100 // Rough estimate
409
- }
464
+ estimatedSizeKB: Math.round(((schemaCache.size * 2) / 1024) * 100) / 100, // Rough estimate
465
+ },
410
466
  };
411
467
  };
412
468
 
@@ -416,15 +472,17 @@ const getDetailedCacheStats = () => {
416
472
  key,
417
473
  createdAt: value.createdAt,
418
474
  age: Date.now() - value.createdAt,
419
- hasValidator: typeof value.validator === 'function',
420
- hasSubSchema: value.subSchema !== undefined
475
+ hasValidator: typeof value.validator === "function",
476
+ hasSubSchema: value.subSchema !== undefined,
421
477
  }));
422
478
 
423
479
  return {
424
480
  ...baseStats,
425
481
  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
482
+ oldestEntry:
483
+ entries.length > 0 ? Math.max(...entries.map((e) => e.age)) : 0,
484
+ newestEntry:
485
+ entries.length > 0 ? Math.min(...entries.map((e) => e.age)) : 0,
428
486
  };
429
487
  };
430
488
 
@@ -437,17 +495,17 @@ const clearSchemaCache = (pattern) => {
437
495
  keysToDelete.push(key);
438
496
  }
439
497
  });
440
- keysToDelete.forEach(key => schemaCache.delete(key));
441
-
498
+ keysToDelete.forEach((key) => schemaCache.delete(key));
499
+
442
500
  // Also remove matching schemas from AJV
443
- keysToDelete.forEach(key => {
501
+ keysToDelete.forEach((key) => {
444
502
  try {
445
503
  ajv.removeSchema(key);
446
504
  } catch (e) {
447
505
  // Schema might not exist in AJV, ignore
448
506
  }
449
507
  });
450
-
508
+
451
509
  return keysToDelete.length;
452
510
  } else {
453
511
  // Clear all cache
@@ -458,20 +516,24 @@ const clearSchemaCache = (pattern) => {
458
516
  cacheStats.misses = 0;
459
517
  cacheStats.totalQueries = 0;
460
518
  cacheStats.cacheStartTime = Date.now();
461
-
519
+
462
520
  // Also clear AJV's internal cache to prevent duplicates
463
521
  ajv.removeSchema();
464
-
522
+
465
523
  return entriesCleared;
466
524
  }
467
525
  };
468
526
 
469
- const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json", overlaysList = []) => {
527
+ const warmupCache = (
528
+ paths,
529
+ schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
530
+ overlaysList = [],
531
+ ) => {
470
532
  const warmupStats = {
471
533
  totalAttempted: 0,
472
534
  successful: 0,
473
535
  failed: 0,
474
- errors: []
536
+ errors: [],
475
537
  };
476
538
 
477
539
  // Default common paths if none provided
@@ -483,7 +545,7 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
483
545
  "/propertyPack/valuations",
484
546
  "/propertyPack/waterAndDrainage",
485
547
  "/propertyPack/ownership",
486
- "/propertyPack/notices"
548
+ "/propertyPack/notices",
487
549
  ];
488
550
 
489
551
  // Default common overlays if none provided
@@ -492,14 +554,15 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
492
554
  ["baspiV5"],
493
555
  ["ta6ed4"],
494
556
  ["nts2023"],
495
- ["baspiV5", "ta6ed4"]
557
+ ["baspiV5", "ta6ed4"],
496
558
  ];
497
559
 
498
560
  const pathsToWarm = paths || defaultPaths;
499
- const overlaysToWarm = overlaysList.length > 0 ? overlaysList : defaultOverlays;
561
+ const overlaysToWarm =
562
+ overlaysList.length > 0 ? overlaysList : defaultOverlays;
500
563
 
501
- pathsToWarm.forEach(path => {
502
- overlaysToWarm.forEach(overlays => {
564
+ pathsToWarm.forEach((path) => {
565
+ overlaysToWarm.forEach((overlays) => {
503
566
  warmupStats.totalAttempted++;
504
567
  try {
505
568
  // This will populate the cache
@@ -511,7 +574,7 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
511
574
  warmupStats.errors.push({
512
575
  path,
513
576
  overlays,
514
- error: error.message
577
+ error: error.message,
515
578
  });
516
579
  }
517
580
  });
@@ -523,14 +586,14 @@ const warmupCache = (paths, schemaId = "https://trust.propdata.org.uk/schemas/v3
523
586
  const pruneCacheByAge = (maxAgeMs) => {
524
587
  const now = Date.now();
525
588
  const keysToDelete = [];
526
-
589
+
527
590
  schemaCache.forEach((value, key) => {
528
591
  if (now - value.createdAt > maxAgeMs) {
529
592
  keysToDelete.push(key);
530
593
  }
531
594
  });
532
-
533
- keysToDelete.forEach(key => {
595
+
596
+ keysToDelete.forEach((key) => {
534
597
  schemaCache.delete(key);
535
598
  try {
536
599
  ajv.removeSchema(key);
@@ -538,21 +601,22 @@ const pruneCacheByAge = (maxAgeMs) => {
538
601
  // Schema might not exist in AJV, ignore
539
602
  }
540
603
  });
541
-
604
+
542
605
  return keysToDelete.length;
543
606
  };
544
607
 
545
608
  const setCacheMaxSize = (maxSize) => {
546
609
  if (schemaCache.size <= maxSize) return 0;
547
-
610
+
548
611
  // 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
-
612
+ const entries = Array.from(schemaCache.entries()).sort(
613
+ (a, b) => a[1].createdAt - b[1].createdAt,
614
+ );
615
+
552
616
  const toRemove = schemaCache.size - maxSize;
553
617
  const keysToDelete = entries.slice(0, toRemove).map(([key]) => key);
554
-
555
- keysToDelete.forEach(key => {
618
+
619
+ keysToDelete.forEach((key) => {
556
620
  schemaCache.delete(key);
557
621
  try {
558
622
  ajv.removeSchema(key);
@@ -560,7 +624,7 @@ const setCacheMaxSize = (maxSize) => {
560
624
  // Schema might not exist in AJV, ignore
561
625
  }
562
626
  });
563
-
627
+
564
628
  return keysToDelete.length;
565
629
  };
566
630
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pdtf/schemas",
3
- "version": "3.6.0-4",
3
+ "version": "3.6.0-41",
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",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "role": "Seller",
21
21
  "sellersCapacity": { "capacity": "Legal Owner" },
22
- "externalIds": { "Moverly": "123434" }
22
+ "externalIds": { "Platform": "123434" }
23
23
  },
24
24
  {
25
25
  "role": "Seller's Conveyancer",
@@ -988,7 +988,7 @@
988
988
  "name": "Example Agency Agreement",
989
989
  "url": "https://example.com/example-agency-agreement",
990
990
  "externalIds": {
991
- "Moverly": "file123"
991
+ "Platform": "file123"
992
992
  },
993
993
  "requiredSignatories": [
994
994
  {
@@ -1027,7 +1027,7 @@
1027
1027
  "name": "Peter Ronald Hetherington-Smythe",
1028
1028
  "signedOn": "2024-11-29T16:15:02.735Z",
1029
1029
  "externalIds": {
1030
- "Moverly": "123434"
1030
+ "Platform": "123434"
1031
1031
  },
1032
1032
  "role": "Seller",
1033
1033
  "contractHash": "IYnBjUI8TauXFop6WCvnPA=="