@pdtf/schemas 3.6.0-9 → 3.6.0-dev.1
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.md +101 -0
- package/README.md +57 -33
- package/docs/backend-lms-profile-filtering-spec.md +180 -0
- package/docs/branch-release-workflow.md +138 -0
- package/docs/sef25-extensions-ui-spec.md +355 -0
- package/index.js +131 -75
- package/package.json +6 -2
- package/src/examples/v3/exampleTransaction.json +3 -3
- package/src/schemas/v3/combined.json +6981 -1234
- package/src/schemas/v3/compactSkeleton.txt +316 -5
- package/src/schemas/v3/overlays/baspi4.json +162 -20
- package/src/schemas/v3/overlays/baspi5.json +167 -22
- package/src/schemas/v3/overlays/extensions/ac.json +333 -0
- package/src/schemas/v3/overlays/extensions/as.json +4 -2
- package/src/schemas/v3/overlays/extensions/dk.json +32 -0
- package/src/schemas/v3/overlays/extensions/dr.json +9 -9
- package/src/schemas/v3/overlays/extensions/er.json +4 -2
- package/src/schemas/v3/overlays/extensions/fd.json +6 -3
- package/src/schemas/v3/overlays/extensions/hi.json +6 -3
- package/src/schemas/v3/overlays/extensions/hs.json +4 -2
- package/src/schemas/v3/overlays/extensions/ic.json +59 -0
- package/src/schemas/v3/overlays/extensions/jk.json +28 -8
- package/src/schemas/v3/overlays/extensions/la.json +4 -2
- package/src/schemas/v3/overlays/extensions/lc.json +75 -0
- package/src/schemas/v3/overlays/extensions/ma.json +6 -3
- package/src/schemas/v3/overlays/extensions/mc.json +4 -2
- package/src/schemas/v3/overlays/extensions/mi.json +69 -0
- package/src/schemas/v3/overlays/extensions/nd.json +43 -0
- package/src/schemas/v3/overlays/extensions/oa.json +4 -2
- package/src/schemas/v3/overlays/extensions/oc.json +4 -2
- package/src/schemas/v3/overlays/extensions/pc.json +37 -0
- package/src/schemas/v3/overlays/extensions/ph.json +194 -0
- package/src/schemas/v3/overlays/extensions/rw.json +68 -0
- package/src/schemas/v3/overlays/extensions/sb.json +4 -2
- package/src/schemas/v3/overlays/extensions/sc.json +129 -0
- package/src/schemas/v3/overlays/extensions/sd.json +63 -0
- package/src/schemas/v3/overlays/extensions/sf.json +4 -2
- package/src/schemas/v3/overlays/extensions/sl.json +4 -2
- package/src/schemas/v3/overlays/extensions/ta.json +252 -0
- package/src/schemas/v3/overlays/extensions/tf.json +6 -3
- package/src/schemas/v3/overlays/extensions/tr.json +73 -0
- package/src/schemas/v3/overlays/extensions/wg.json +350 -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 +37 -3
- package/src/schemas/v3/overlays/rds.json +117 -8
- package/src/schemas/v3/overlays/sef25.json +1639 -0
- package/src/schemas/v3/overlays/ta6.json +46 -16
- package/src/schemas/v3/overlays/ta6ed6.json +6533 -0
- package/src/schemas/v3/overlays/ta7.json +70 -6
- package/src/schemas/v3/overlays/ta7ed5.json +1551 -0
- package/src/schemas/v3/pdtf-transaction.json +3896 -686
- package/src/schemas/v3/skeleton.json +442 -35
- package/src/utils/extractExtensionOverlays.js +293 -31
- package/src/utils/extractOverlay.js +94 -27
- package/src/utils/pathSkeleton.js +1 -1
- package/.claude/settings.local.json +0 -42
- package/.github/workflows/pages.yml +0 -50
- package/CNAME +0 -1
- package/_config.yml +0 -1
- package/src/tests/v3/caching.test.js +0 -591
- package/src/tests/v3/enquiries.test.js +0 -1096
- package/src/tests/v3/extensionOverlays.test.js +0 -525
- package/src/tests/v3/offers.test.js +0 -823
- package/src/tests/v3/patternProperties.test.js +0 -688
- package/src/tests/v3/transactionSchema.test.js +0 -615
- package/src/tests/v3/verifiedClaimsValidator.test.js +0 -195
|
@@ -5,6 +5,41 @@ const path = require("path");
|
|
|
5
5
|
|
|
6
6
|
const combinedSchema = require("../schemas/v3/combined.json");
|
|
7
7
|
|
|
8
|
+
// Derive ref-related field names from a refType
|
|
9
|
+
function getRefConfig(refType) {
|
|
10
|
+
if (refType === "nts2Ref") {
|
|
11
|
+
// Legacy mapping: nts2Ref -> ntsRef in output
|
|
12
|
+
return {
|
|
13
|
+
ref: "nts2Ref",
|
|
14
|
+
outputRef: "ntsRef",
|
|
15
|
+
required: "nts2Required",
|
|
16
|
+
title: "nts2Title",
|
|
17
|
+
description: "nts2Description",
|
|
18
|
+
enumKey: "nts2Enum",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (refType === "sef25Ref") {
|
|
22
|
+
// SEF25 extensions also appear as ntsRef in output
|
|
23
|
+
return {
|
|
24
|
+
ref: "sef25Ref",
|
|
25
|
+
outputRef: "ntsRef",
|
|
26
|
+
required: "sef25Required",
|
|
27
|
+
title: "sef25Title",
|
|
28
|
+
description: "sef25Description",
|
|
29
|
+
enumKey: "sef25Enum",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// For other ref types, derive keys by replacing "Ref" suffix
|
|
33
|
+
return {
|
|
34
|
+
ref: refType,
|
|
35
|
+
outputRef: refType,
|
|
36
|
+
required: refType.replace("Ref", "Required"),
|
|
37
|
+
title: refType.replace("Ref", "Title"),
|
|
38
|
+
description: refType.replace("Ref", "Description"),
|
|
39
|
+
enumKey: refType.replace("Ref", "Enum"),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
8
43
|
// Define the extension overlay mappings
|
|
9
44
|
const extensionMappings = {
|
|
10
45
|
// Outside areas
|
|
@@ -150,18 +185,192 @@ const extensionMappings = {
|
|
|
150
185
|
"/properties/propertyPack/properties/completionAndMoving/properties/otherPropertyInChain",
|
|
151
186
|
],
|
|
152
187
|
},
|
|
188
|
+
|
|
189
|
+
// Private supply costs (water & sewerage)
|
|
190
|
+
sc: {
|
|
191
|
+
name: "supplyCosts",
|
|
192
|
+
description: "Associated costs for private water and sewerage supply",
|
|
193
|
+
refType: "sef25Ref",
|
|
194
|
+
paths: [
|
|
195
|
+
"/properties/propertyPack/properties/waterAndDrainage/properties/water/properties/mainsWater/oneOf/0/properties/associatedCost",
|
|
196
|
+
"/properties/propertyPack/properties/waterAndDrainage/properties/drainage/properties/mainsFoulDrainage/oneOf/2/properties/associatedCost",
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
// Parking permit costs
|
|
201
|
+
pc: {
|
|
202
|
+
name: "parkingPermitCost",
|
|
203
|
+
description: "Parking permit cost and frequency",
|
|
204
|
+
refType: "sef25Ref",
|
|
205
|
+
paths: [
|
|
206
|
+
"/properties/propertyPack/properties/parking/properties/controlledParking/oneOf/1/properties/costOfPermit",
|
|
207
|
+
"/properties/propertyPack/properties/parking/properties/controlledParking/oneOf/1/properties/costOfPermitFrequency",
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
// Property hazards
|
|
212
|
+
ph: {
|
|
213
|
+
name: "propertyHazards",
|
|
214
|
+
description: "Property hazards and known issues",
|
|
215
|
+
refType: "sef25Ref",
|
|
216
|
+
paths: [
|
|
217
|
+
"/properties/propertyPack/properties/specialistIssues",
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
// Dropped kerb access to parking
|
|
222
|
+
dk: {
|
|
223
|
+
name: "droppedKerbAccess",
|
|
224
|
+
description: "Dropped kerb access to parking",
|
|
225
|
+
refType: "sef25Ref",
|
|
226
|
+
paths: [
|
|
227
|
+
"/properties/propertyPack/properties/parking/properties/droppedKerbAccess",
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// Private right of way
|
|
232
|
+
rw: {
|
|
233
|
+
name: "privateRightOfWay",
|
|
234
|
+
description: "Private right of way",
|
|
235
|
+
refType: "sef25Ref",
|
|
236
|
+
paths: [
|
|
237
|
+
"/properties/propertyPack/properties/rightsAndInformalArrangements/properties/rightsOrArrangements/properties/privateRightOfWay",
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
// Storm, fire or flood damage
|
|
242
|
+
sd: {
|
|
243
|
+
name: "stormFireFloodDamage",
|
|
244
|
+
description: "Storm, fire or flood damage",
|
|
245
|
+
refType: "sef25Ref",
|
|
246
|
+
paths: [
|
|
247
|
+
"/properties/propertyPack/properties/environmentalIssues/properties/stormFireFloodDamage",
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
// Solar panel lease costs
|
|
252
|
+
lc: {
|
|
253
|
+
name: "solarLeaseCosts",
|
|
254
|
+
description: "Solar panel lease costs",
|
|
255
|
+
refType: "sef25Ref",
|
|
256
|
+
paths: [
|
|
257
|
+
"/properties/propertyPack/properties/electricity/properties/solarPanels/oneOf/1/properties/panelsOwnedOutright/oneOf/1/properties/associatedCost",
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
// Warranties and guarantees upfront
|
|
262
|
+
wg: {
|
|
263
|
+
name: "warrantiesGuarantees",
|
|
264
|
+
description: "Warranties and guarantees upfront Y/N for SEF25",
|
|
265
|
+
refType: "sef25Ref",
|
|
266
|
+
paths: [
|
|
267
|
+
"/properties/propertyPack/properties/guaranteesWarrantiesAndIndemnityInsurances",
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// Insurance claims upfront
|
|
272
|
+
ic: {
|
|
273
|
+
name: "insuranceClaims",
|
|
274
|
+
description: "Insurance claims upfront for SEF25",
|
|
275
|
+
refType: "sef25Ref",
|
|
276
|
+
paths: [
|
|
277
|
+
"/properties/propertyPack/properties/insurance/oneOf/1/properties/insuranceClaims",
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
// Neighbour development upfront
|
|
282
|
+
nd: {
|
|
283
|
+
name: "neighbourDevelopment",
|
|
284
|
+
description: "Neighbour development upfront for SEF25",
|
|
285
|
+
refType: "sef25Ref",
|
|
286
|
+
paths: [
|
|
287
|
+
"/properties/propertyPack/properties/notices/properties/neighbourDevelopment",
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// Other material issue upfront
|
|
292
|
+
mi: {
|
|
293
|
+
name: "otherMaterialIssue",
|
|
294
|
+
description: "Other material issue upfront for SEF25",
|
|
295
|
+
refType: "sef25Ref",
|
|
296
|
+
paths: [
|
|
297
|
+
"/properties/propertyPack/properties/additionalInformation/properties/otherMaterialIssue",
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
// Title restrictions for freehold
|
|
302
|
+
tr: {
|
|
303
|
+
name: "titleRestrictions",
|
|
304
|
+
description: "Title restrictions for freehold properties",
|
|
305
|
+
refType: "sef25Ref",
|
|
306
|
+
paths: [
|
|
307
|
+
"/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/titleRestrictions",
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
// Alterations and changes
|
|
312
|
+
ac: {
|
|
313
|
+
name: "alterationsAndChanges",
|
|
314
|
+
description: "Selected alterations and changes fields for SEF25",
|
|
315
|
+
refType: "sef25Ref",
|
|
316
|
+
preserveDiscriminators: true,
|
|
317
|
+
paths: [
|
|
318
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/extension/properties/yesNo",
|
|
319
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/details",
|
|
320
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/planningPermission/properties/yesNo",
|
|
321
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/buildingRegApproval/properties/yesNo",
|
|
322
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/properties/yesNo",
|
|
323
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/details",
|
|
324
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/planningPermission/properties/yesNo",
|
|
325
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/buildingRegApproval/properties/yesNo",
|
|
326
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/properties/yesNo",
|
|
327
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/details",
|
|
328
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/planningPermission/properties/yesNo",
|
|
329
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/buildingRegApproval/properties/yesNo",
|
|
330
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/properties/yesNo",
|
|
331
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/details",
|
|
332
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/planningPermission/properties/yesNo",
|
|
333
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/buildingRegApproval/properties/yesNo",
|
|
334
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/properties/yesNo",
|
|
335
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/details",
|
|
336
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/planningPermission/properties/yesNo",
|
|
337
|
+
"/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/buildingRegApproval/properties/yesNo",
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
// TA6 edition 6 compatibility for shared MI/NTS validation
|
|
342
|
+
ta: {
|
|
343
|
+
name: "ta6Ed6Compatibility",
|
|
344
|
+
description:
|
|
345
|
+
"Compatibility enum ranges for validating shared PDTF state containing TA6 edition 6 answers",
|
|
346
|
+
refType: "ta6ed6CompatRef",
|
|
347
|
+
paths: [
|
|
348
|
+
"/properties/propertyPack/properties/parking",
|
|
349
|
+
"/properties/propertyPack/properties/heating",
|
|
350
|
+
"/properties/propertyPack/properties/rightsAndInformalArrangements",
|
|
351
|
+
"/properties/propertyPack/properties/legalBoundaries",
|
|
352
|
+
],
|
|
353
|
+
},
|
|
153
354
|
};
|
|
154
355
|
|
|
155
|
-
// Function to extract properties at specific paths with
|
|
156
|
-
function
|
|
356
|
+
// Function to extract properties at specific paths with given ref type
|
|
357
|
+
function extractPathsWithRef(
|
|
358
|
+
schema,
|
|
359
|
+
paths,
|
|
360
|
+
refConfig,
|
|
361
|
+
preserveDiscriminators = false
|
|
362
|
+
) {
|
|
157
363
|
const result = {};
|
|
158
364
|
|
|
159
365
|
paths.forEach((path) => {
|
|
160
366
|
try {
|
|
161
367
|
const value = jp.get(schema, path);
|
|
162
|
-
if (value &&
|
|
368
|
+
if (value && hasRef(value, refConfig)) {
|
|
163
369
|
// Set the value at the same path in result
|
|
164
|
-
jp.set(result, path,
|
|
370
|
+
jp.set(result, path, extractRefProperties(value, refConfig));
|
|
371
|
+
if (preserveDiscriminators) {
|
|
372
|
+
addAncestorDiscriminators(result, schema, path);
|
|
373
|
+
}
|
|
165
374
|
}
|
|
166
375
|
} catch (err) {
|
|
167
376
|
console.warn(`Path not found: ${path}`);
|
|
@@ -171,11 +380,31 @@ function extractPathsWithNts2Ref(schema, paths) {
|
|
|
171
380
|
return result;
|
|
172
381
|
}
|
|
173
382
|
|
|
174
|
-
|
|
175
|
-
|
|
383
|
+
function addAncestorDiscriminators(result, schema, path) {
|
|
384
|
+
const parts = path.split("/").filter(Boolean);
|
|
385
|
+
|
|
386
|
+
for (let index = 1; index <= parts.length; index += 1) {
|
|
387
|
+
const ancestorPath = `/${parts.slice(0, index).join("/")}`;
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
const schemaNode = jp.get(schema, ancestorPath);
|
|
391
|
+
if (!schemaNode?.discriminator) continue;
|
|
392
|
+
|
|
393
|
+
const resultNode = jp.get(result, ancestorPath);
|
|
394
|
+
if (resultNode && typeof resultNode === "object") {
|
|
395
|
+
resultNode.discriminator = schemaNode.discriminator;
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
// Ancestor was not materialised by this selected path.
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Check if an object or its descendants have the specified ref
|
|
404
|
+
function hasRef(obj, refConfig) {
|
|
176
405
|
let found = false;
|
|
177
406
|
traverse(obj).forEach(function (element) {
|
|
178
|
-
if (element && element.
|
|
407
|
+
if (element && element[refConfig.ref]) {
|
|
179
408
|
found = true;
|
|
180
409
|
this.stop();
|
|
181
410
|
}
|
|
@@ -183,16 +412,16 @@ function hasNts2Ref(obj) {
|
|
|
183
412
|
return found;
|
|
184
413
|
}
|
|
185
414
|
|
|
186
|
-
// Extract only
|
|
187
|
-
function
|
|
415
|
+
// Extract only ref-specific properties
|
|
416
|
+
function extractRefProperties(obj, refConfig, parentKey) {
|
|
188
417
|
const result = {};
|
|
189
418
|
|
|
190
|
-
// Copy
|
|
191
|
-
if (obj.
|
|
192
|
-
if (obj.
|
|
193
|
-
if (obj.
|
|
194
|
-
if (obj.
|
|
195
|
-
if (obj.
|
|
419
|
+
// Copy ref-specific metadata at current level
|
|
420
|
+
if (obj[refConfig.ref]) result[refConfig.outputRef] = obj[refConfig.ref];
|
|
421
|
+
if (obj[refConfig.required]) result.required = obj[refConfig.required];
|
|
422
|
+
if (obj[refConfig.title]) result.title = obj[refConfig.title];
|
|
423
|
+
if (obj[refConfig.description]) result.description = obj[refConfig.description];
|
|
424
|
+
if (obj[refConfig.enumKey]) result.enum = obj[refConfig.enumKey];
|
|
196
425
|
|
|
197
426
|
// Handle discriminator
|
|
198
427
|
if (obj.discriminator) {
|
|
@@ -204,11 +433,11 @@ function extractNts2Properties(obj, parentKey) {
|
|
|
204
433
|
result.properties = {};
|
|
205
434
|
Object.keys(obj.properties).forEach((key) => {
|
|
206
435
|
const prop = obj.properties[key];
|
|
207
|
-
if (
|
|
208
|
-
result.properties[key] =
|
|
436
|
+
if (hasRef(prop, refConfig)) {
|
|
437
|
+
result.properties[key] = extractRefProperties(prop, refConfig, key);
|
|
209
438
|
}
|
|
210
439
|
});
|
|
211
|
-
// Only keep properties if we found some with
|
|
440
|
+
// Only keep properties if we found some with ref
|
|
212
441
|
if (Object.keys(result.properties).length === 0) {
|
|
213
442
|
delete result.properties;
|
|
214
443
|
}
|
|
@@ -216,8 +445,8 @@ function extractNts2Properties(obj, parentKey) {
|
|
|
216
445
|
|
|
217
446
|
// Handle arrays
|
|
218
447
|
if (obj.items) {
|
|
219
|
-
if (
|
|
220
|
-
result.items =
|
|
448
|
+
if (hasRef(obj.items, refConfig)) {
|
|
449
|
+
result.items = extractRefProperties(obj.items, refConfig);
|
|
221
450
|
}
|
|
222
451
|
}
|
|
223
452
|
|
|
@@ -225,8 +454,8 @@ function extractNts2Properties(obj, parentKey) {
|
|
|
225
454
|
if (obj.oneOf) {
|
|
226
455
|
// Always preserve the full array structure and order
|
|
227
456
|
const processedOneOf = obj.oneOf.map((schema, index) => {
|
|
228
|
-
if (
|
|
229
|
-
const extracted =
|
|
457
|
+
if (hasRef(schema, refConfig)) {
|
|
458
|
+
const extracted = extractRefProperties(schema, refConfig);
|
|
230
459
|
// For oneOf schemas, we need to preserve discriminator enum values
|
|
231
460
|
if (obj.discriminator && schema.properties) {
|
|
232
461
|
const propName = obj.discriminator.propertyName;
|
|
@@ -234,7 +463,7 @@ function extractNts2Properties(obj, parentKey) {
|
|
|
234
463
|
if (!extracted.properties) extracted.properties = {};
|
|
235
464
|
extracted.properties[propName] = {
|
|
236
465
|
enum:
|
|
237
|
-
schema.properties[propName].
|
|
466
|
+
schema.properties[propName][refConfig.enumKey] ||
|
|
238
467
|
schema.properties[propName].enum,
|
|
239
468
|
};
|
|
240
469
|
}
|
|
@@ -258,7 +487,7 @@ function extractNts2Properties(obj, parentKey) {
|
|
|
258
487
|
}
|
|
259
488
|
return extracted;
|
|
260
489
|
} else {
|
|
261
|
-
// If this branch doesn't have
|
|
490
|
+
// If this branch doesn't have ref, preserve the base discriminator enum
|
|
262
491
|
if (obj.discriminator && schema.properties) {
|
|
263
492
|
const propName = obj.discriminator.propertyName;
|
|
264
493
|
if (schema.properties[propName]) {
|
|
@@ -281,7 +510,7 @@ function extractNts2Properties(obj, parentKey) {
|
|
|
281
510
|
// If parentKey is set, and this object has extension metadata, return a stub for the parent
|
|
282
511
|
if (
|
|
283
512
|
parentKey &&
|
|
284
|
-
(result.
|
|
513
|
+
(result[refConfig.outputRef] || result.required || result.discriminator)
|
|
285
514
|
) {
|
|
286
515
|
// This is an extension property inside a oneOf branch
|
|
287
516
|
// Return a stub for the parent property with metadata and oneOf structure
|
|
@@ -311,16 +540,16 @@ function extractNts2Properties(obj, parentKey) {
|
|
|
311
540
|
}
|
|
312
541
|
|
|
313
542
|
// Add required array at parent levels if needed
|
|
314
|
-
function addRequiredArrays(overlay, schema) {
|
|
543
|
+
function addRequiredArrays(overlay, schema, refConfig) {
|
|
315
544
|
traverse(overlay).forEach(function (node) {
|
|
316
545
|
if (node && node.properties) {
|
|
317
546
|
const schemaPath = "/" + this.path.join("/");
|
|
318
547
|
try {
|
|
319
548
|
const schemaNode = jp.get(schema, schemaPath);
|
|
320
|
-
if (schemaNode && schemaNode.
|
|
549
|
+
if (schemaNode && schemaNode[refConfig.required]) {
|
|
321
550
|
// Filter to only include properties that exist in this overlay
|
|
322
551
|
const overlayProps = Object.keys(node.properties);
|
|
323
|
-
const requiredProps = schemaNode.
|
|
552
|
+
const requiredProps = schemaNode[refConfig.required].filter((prop) =>
|
|
324
553
|
overlayProps.includes(prop)
|
|
325
554
|
);
|
|
326
555
|
if (requiredProps.length > 0) {
|
|
@@ -335,15 +564,48 @@ function addRequiredArrays(overlay, schema) {
|
|
|
335
564
|
return overlay;
|
|
336
565
|
}
|
|
337
566
|
|
|
567
|
+
// Annotate intermediate nodes with refs from the source schema
|
|
568
|
+
// This ensures every level of the overlay tree carries an ntsRef
|
|
569
|
+
function addIntermediateRefs(overlay, schema, refConfig) {
|
|
570
|
+
traverse(overlay).forEach(function (node) {
|
|
571
|
+
if (node && typeof node === "object" && !Array.isArray(node) && node.properties) {
|
|
572
|
+
// Skip if this node already has a ref
|
|
573
|
+
if (node[refConfig.outputRef]) return;
|
|
574
|
+
|
|
575
|
+
const schemaPath = "/" + this.path.join("/");
|
|
576
|
+
try {
|
|
577
|
+
const schemaNode = jp.get(schema, schemaPath);
|
|
578
|
+
if (schemaNode && schemaNode[refConfig.ref]) {
|
|
579
|
+
node[refConfig.outputRef] = schemaNode[refConfig.ref];
|
|
580
|
+
}
|
|
581
|
+
} catch (err) {
|
|
582
|
+
// Path doesn't exist in schema, skip
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
return overlay;
|
|
587
|
+
}
|
|
588
|
+
|
|
338
589
|
// Generate extension overlays
|
|
339
590
|
Object.entries(extensionMappings).forEach(([code, config]) => {
|
|
340
|
-
|
|
591
|
+
const refType = config.refType || "nts2Ref";
|
|
592
|
+
const refConfig = getRefConfig(refType);
|
|
593
|
+
|
|
594
|
+
console.log(`\nGenerating extension overlay: ${code} (${config.name}) [${refType}]`);
|
|
341
595
|
|
|
342
596
|
// Extract the specific paths
|
|
343
|
-
let overlay =
|
|
597
|
+
let overlay = extractPathsWithRef(
|
|
598
|
+
combinedSchema,
|
|
599
|
+
config.paths,
|
|
600
|
+
refConfig,
|
|
601
|
+
config.preserveDiscriminators
|
|
602
|
+
);
|
|
344
603
|
|
|
345
604
|
// Add required arrays where needed
|
|
346
|
-
overlay = addRequiredArrays(overlay, combinedSchema);
|
|
605
|
+
overlay = addRequiredArrays(overlay, combinedSchema, refConfig);
|
|
606
|
+
|
|
607
|
+
// Add refs to intermediate nodes in the path hierarchy
|
|
608
|
+
overlay = addIntermediateRefs(overlay, combinedSchema, refConfig);
|
|
347
609
|
|
|
348
610
|
// Add schema metadata
|
|
349
611
|
overlay.$schema = "http://json-schema.org/draft-07/schema#";
|
|
@@ -2,9 +2,24 @@ const { dereference } = require("@jdw/jst");
|
|
|
2
2
|
const traverse = require("traverse");
|
|
3
3
|
const jp = require("jsonpointer");
|
|
4
4
|
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
5
6
|
const merge = require("deepmerge");
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const combinedPath =
|
|
9
|
+
process.env.COMBINED_PATH ||
|
|
10
|
+
path.resolve(__dirname, "../schemas/v3/combined.json");
|
|
11
|
+
const outputDir =
|
|
12
|
+
process.env.OUTPUT_DIR ||
|
|
13
|
+
path.resolve(__dirname, "../schemas/v3");
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(combinedPath)) {
|
|
16
|
+
throw new Error(`Combined schema not found: ${combinedPath}`);
|
|
17
|
+
}
|
|
18
|
+
if (!fs.existsSync(outputDir)) {
|
|
19
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const combinedSchema = JSON.parse(fs.readFileSync(combinedPath, "utf8"));
|
|
8
23
|
|
|
9
24
|
const extractFields = [
|
|
10
25
|
"baspi4",
|
|
@@ -14,7 +29,9 @@ const extractFields = [
|
|
|
14
29
|
"ntsl",
|
|
15
30
|
"ntsl2",
|
|
16
31
|
"ta6",
|
|
32
|
+
"ta6ed6",
|
|
17
33
|
"ta7",
|
|
34
|
+
"ta7ed5",
|
|
18
35
|
"ta10",
|
|
19
36
|
"lpe1",
|
|
20
37
|
"fme1",
|
|
@@ -25,8 +42,11 @@ const extractFields = [
|
|
|
25
42
|
"rds",
|
|
26
43
|
"oc1",
|
|
27
44
|
"sr24",
|
|
45
|
+
"sef25",
|
|
28
46
|
];
|
|
29
47
|
|
|
48
|
+
const metadataFields = [...extractFields, "ta6ed6Compat"];
|
|
49
|
+
|
|
30
50
|
const flattenSkeleton = (schema) => {
|
|
31
51
|
if (!schema) return undefined;
|
|
32
52
|
|
|
@@ -68,23 +88,21 @@ const flattenSkeleton = (schema) => {
|
|
|
68
88
|
});
|
|
69
89
|
}
|
|
70
90
|
|
|
71
|
-
// Handle oneOf - merge all possible properties
|
|
91
|
+
// Handle oneOf - merge all possible properties (recursively handles nested oneOf)
|
|
72
92
|
if (schema.oneOf) {
|
|
73
93
|
schema.oneOf.forEach((aOneOf) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (returnStructure[key] && JSON.stringify(returnStructure[key]) !== JSON.stringify(
|
|
79
|
-
|
|
80
|
-
if (typeof returnStructure[key] === "string" || typeof newValue === "string") {
|
|
94
|
+
const variantResult = flattenSkeleton(aOneOf);
|
|
95
|
+
if (variantResult && typeof variantResult === "object" && !Array.isArray(variantResult)) {
|
|
96
|
+
Object.entries(variantResult).forEach(([key, value]) => {
|
|
97
|
+
if (key === "_variants") return;
|
|
98
|
+
if (returnStructure[key] && JSON.stringify(returnStructure[key]) !== JSON.stringify(value)) {
|
|
99
|
+
if (typeof returnStructure[key] === "string" || typeof value === "string") {
|
|
81
100
|
returnStructure[key] = "variant";
|
|
82
101
|
} else {
|
|
83
|
-
|
|
84
|
-
returnStructure[key] = { ...returnStructure[key], ...newValue, _variants: true };
|
|
102
|
+
returnStructure[key] = { ...returnStructure[key], ...value, _variants: true };
|
|
85
103
|
}
|
|
86
104
|
} else {
|
|
87
|
-
returnStructure[key] =
|
|
105
|
+
returnStructure[key] = value;
|
|
88
106
|
}
|
|
89
107
|
});
|
|
90
108
|
}
|
|
@@ -97,6 +115,23 @@ const flattenSkeleton = (schema) => {
|
|
|
97
115
|
|
|
98
116
|
const extractOverlay = (sourceSchema, ref) => {
|
|
99
117
|
const refName = `${ref}Ref`;
|
|
118
|
+
const extendedTagMap = {
|
|
119
|
+
Type: "type",
|
|
120
|
+
MinLength: "minLength",
|
|
121
|
+
MaxLength: "maxLength",
|
|
122
|
+
Pattern: "pattern",
|
|
123
|
+
Minimum: "minimum",
|
|
124
|
+
Maximum: "maximum",
|
|
125
|
+
MinItems: "minItems",
|
|
126
|
+
MaxItems: "maxItems",
|
|
127
|
+
UniqueItems: "uniqueItems",
|
|
128
|
+
AdditionalProperties: "additionalProperties",
|
|
129
|
+
Format: "format",
|
|
130
|
+
MinProperties: "minProperties",
|
|
131
|
+
MaxProperties: "maxProperties",
|
|
132
|
+
MultipleOf: "multipleOf",
|
|
133
|
+
Const: "const",
|
|
134
|
+
};
|
|
100
135
|
const returnSchema = {
|
|
101
136
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
102
137
|
$id: `https://trust.propdata.org.uk/schemas/v3/overlays/${ref}.json`,
|
|
@@ -107,7 +142,7 @@ const extractOverlay = (sourceSchema, ref) => {
|
|
|
107
142
|
if (element[refName]) {
|
|
108
143
|
jp.set(returnSchema, `${path}/${refName}`, element[refName]);
|
|
109
144
|
|
|
110
|
-
if (element.discriminator) {
|
|
145
|
+
if (element.discriminator && Array.isArray(element.oneOf)) {
|
|
111
146
|
jp.set(returnSchema, `${path}/discriminator`, element.discriminator);
|
|
112
147
|
const { propertyName } = element.discriminator;
|
|
113
148
|
element.oneOf.forEach((oneOf, index) => {
|
|
@@ -134,7 +169,7 @@ const extractOverlay = (sourceSchema, ref) => {
|
|
|
134
169
|
}
|
|
135
170
|
|
|
136
171
|
// also handle discriminator properties nested in items
|
|
137
|
-
if (element.items?.discriminator) {
|
|
172
|
+
if (element.items?.discriminator && Array.isArray(element.items.oneOf)) {
|
|
138
173
|
jp.set(
|
|
139
174
|
returnSchema,
|
|
140
175
|
`${path}/items/discriminator`,
|
|
@@ -184,6 +219,18 @@ const extractOverlay = (sourceSchema, ref) => {
|
|
|
184
219
|
if (element[refEnum]) {
|
|
185
220
|
jp.set(returnSchema, `${path}/enum`, element[refEnum]);
|
|
186
221
|
}
|
|
222
|
+
|
|
223
|
+
const refConst = `${ref}Const`;
|
|
224
|
+
if (Object.prototype.hasOwnProperty.call(element, refConst)) {
|
|
225
|
+
jp.set(returnSchema, `${path}/const`, element[refConst]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
Object.entries(extendedTagMap).forEach(([suffix, schemaKey]) => {
|
|
229
|
+
const tagKey = `${ref}${suffix}`;
|
|
230
|
+
if (Object.prototype.hasOwnProperty.call(element, tagKey)) {
|
|
231
|
+
jp.set(returnSchema, `${path}/${schemaKey}`, element[tagKey]);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
187
234
|
});
|
|
188
235
|
return returnSchema;
|
|
189
236
|
};
|
|
@@ -198,28 +245,48 @@ const deleteProperties = (sourceSchema, propertyNames) => {
|
|
|
198
245
|
};
|
|
199
246
|
|
|
200
247
|
const overlays = {};
|
|
248
|
+
const overlaysDir = path.join(outputDir, "overlays");
|
|
249
|
+
if (!fs.existsSync(overlaysDir)) {
|
|
250
|
+
fs.mkdirSync(overlaysDir, { recursive: true });
|
|
251
|
+
}
|
|
201
252
|
|
|
202
253
|
extractFields.forEach((key) => {
|
|
203
254
|
let overlay = extractOverlay(combinedSchema, key);
|
|
204
255
|
overlays[key] = overlay;
|
|
205
|
-
const fileName =
|
|
256
|
+
const fileName = path.join(overlaysDir, `${key}.json`);
|
|
206
257
|
fs.writeFileSync(fileName, JSON.stringify(overlay, null, 2));
|
|
207
258
|
console.log(`Overlay ${key} written to ${fileName}`);
|
|
208
259
|
});
|
|
209
260
|
|
|
210
261
|
const coreSchema = deleteProperties(combinedSchema, [
|
|
211
262
|
"discriminator",
|
|
212
|
-
...
|
|
213
|
-
...
|
|
214
|
-
...
|
|
215
|
-
...
|
|
263
|
+
...metadataFields.map((item) => `${item}Ref`),
|
|
264
|
+
...metadataFields.map((item) => `${item}Required`),
|
|
265
|
+
...metadataFields.map((item) => `${item}Title`),
|
|
266
|
+
...metadataFields.map((item) => `${item}Description`),
|
|
267
|
+
...metadataFields.map((item) => `${item}Enum`),
|
|
268
|
+
...metadataFields.map((item) => `${item}Type`),
|
|
269
|
+
...metadataFields.map((item) => `${item}MinLength`),
|
|
270
|
+
...metadataFields.map((item) => `${item}MaxLength`),
|
|
271
|
+
...metadataFields.map((item) => `${item}Pattern`),
|
|
272
|
+
...metadataFields.map((item) => `${item}Minimum`),
|
|
273
|
+
...metadataFields.map((item) => `${item}Maximum`),
|
|
274
|
+
...metadataFields.map((item) => `${item}MinItems`),
|
|
275
|
+
...metadataFields.map((item) => `${item}MaxItems`),
|
|
276
|
+
...metadataFields.map((item) => `${item}UniqueItems`),
|
|
277
|
+
...metadataFields.map((item) => `${item}AdditionalProperties`),
|
|
278
|
+
...metadataFields.map((item) => `${item}Format`),
|
|
279
|
+
...metadataFields.map((item) => `${item}MinProperties`),
|
|
280
|
+
...metadataFields.map((item) => `${item}MaxProperties`),
|
|
281
|
+
...metadataFields.map((item) => `${item}MultipleOf`),
|
|
282
|
+
...metadataFields.map((item) => `${item}Const`),
|
|
216
283
|
]);
|
|
217
284
|
|
|
218
285
|
fs.writeFileSync(
|
|
219
|
-
"
|
|
286
|
+
path.join(outputDir, "pdtf-transaction.json"),
|
|
220
287
|
JSON.stringify(coreSchema, null, 2)
|
|
221
288
|
);
|
|
222
|
-
console.log(
|
|
289
|
+
console.log(`Core schema written to ${path.join(outputDir, "pdtf-transaction.json")}`);
|
|
223
290
|
|
|
224
291
|
const skeletonSchema = deleteProperties(coreSchema, [
|
|
225
292
|
"$schema",
|
|
@@ -238,10 +305,10 @@ const skeletonSchema = deleteProperties(coreSchema, [
|
|
|
238
305
|
const skeletonSchemaFlattened = flattenSkeleton(skeletonSchema);
|
|
239
306
|
|
|
240
307
|
fs.writeFileSync(
|
|
241
|
-
"
|
|
308
|
+
path.join(outputDir, "skeleton.json"),
|
|
242
309
|
JSON.stringify(skeletonSchemaFlattened, null, 2)
|
|
243
310
|
);
|
|
244
|
-
console.log(
|
|
311
|
+
console.log(`Flat Skeleton schema written to ${path.join(outputDir, "skeleton.json")}`);
|
|
245
312
|
|
|
246
313
|
// Generate compact skeleton format for better token efficiency
|
|
247
314
|
const toCompact = (obj, indent = "") => {
|
|
@@ -267,8 +334,8 @@ const toCompact = (obj, indent = "") => {
|
|
|
267
334
|
// Check if this is a keyed object (has "*" key)
|
|
268
335
|
if (keys.length === 1 && keys[0] === "*") {
|
|
269
336
|
const inner = toCompact(obj["*"], indent);
|
|
270
|
-
if (inner === "") {
|
|
271
|
-
return "{*}"; // Keyed object of primitives
|
|
337
|
+
if (inner === "" || inner === "{*}") {
|
|
338
|
+
return "{*}"; // Keyed object of primitives (or nested keyed objects with no structure)
|
|
272
339
|
}
|
|
273
340
|
return `{*}\n${inner}`;
|
|
274
341
|
}
|
|
@@ -308,7 +375,7 @@ const innerContent = toCompact(skeletonSchemaFlattened, "");
|
|
|
308
375
|
const compactSkeleton = innerContent ? innerContent : "";
|
|
309
376
|
|
|
310
377
|
fs.writeFileSync(
|
|
311
|
-
"
|
|
378
|
+
path.join(outputDir, "compactSkeleton.txt"),
|
|
312
379
|
compactSkeleton
|
|
313
380
|
);
|
|
314
|
-
console.log(
|
|
381
|
+
console.log(`Compact Skeleton written to ${path.join(outputDir, "compactSkeleton.txt")}`);
|
|
@@ -6,7 +6,7 @@ const combinedSchema = require("../schemas/v3/combined.json");
|
|
|
6
6
|
// Extract fields that have overlay-specific properties
|
|
7
7
|
const extractFields = [
|
|
8
8
|
"baspi4", "baspi5", "nts", "nts2", "ntsl", "ntsl2",
|
|
9
|
-
"ta6", "ta7", "ta10", "lpe1", "fme1", "piq",
|
|
9
|
+
"ta6", "ta7", "ta7ed5", "ta10", "lpe1", "fme1", "piq",
|
|
10
10
|
"con29R", "con29DW", "llc1", "rds", "oc1", "sr24",
|
|
11
11
|
];
|
|
12
12
|
|