@pdtf/schemas 3.6.0-41 → 3.6.0-44

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.
@@ -117,7 +117,12 @@
117
117
  ],
118
118
  "required": [
119
119
  "role"
120
- ]
120
+ ],
121
+ "properties": {
122
+ "role": {
123
+ "ta6ed6Ref": "1"
124
+ }
125
+ }
121
126
  }
122
127
  },
123
128
  "propertyPack": {
@@ -838,7 +843,12 @@
838
843
  ],
839
844
  "required": [
840
845
  "yesNo"
841
- ]
846
+ ],
847
+ "properties": {
848
+ "yesNo": {
849
+ "ta6ed6Ref": "5.1a(f)"
850
+ }
851
+ }
842
852
  },
843
853
  "deedRestrictionConsent": {
844
854
  "ta6ed6Ref": "5.1a(g)",
@@ -868,7 +878,12 @@
868
878
  ],
869
879
  "required": [
870
880
  "yesNo"
871
- ]
881
+ ],
882
+ "properties": {
883
+ "yesNo": {
884
+ "ta6ed6Ref": "5.1a(g)"
885
+ }
886
+ }
872
887
  },
873
888
  "workCompleted": {
874
889
  "ta6ed6Ref": "5.1a(b)",
@@ -1255,7 +1270,12 @@
1255
1270
  ],
1256
1271
  "required": [
1257
1272
  "yesNo"
1258
- ]
1273
+ ],
1274
+ "properties": {
1275
+ "yesNo": {
1276
+ "ta6ed6Ref": "5.1c(f)"
1277
+ }
1278
+ }
1259
1279
  },
1260
1280
  "deedRestrictionConsent": {
1261
1281
  "ta6ed6Ref": "5.1c(g)",
@@ -1285,7 +1305,12 @@
1285
1305
  ],
1286
1306
  "required": [
1287
1307
  "yesNo"
1288
- ]
1308
+ ],
1309
+ "properties": {
1310
+ "yesNo": {
1311
+ "ta6ed6Ref": "5.1c(g)"
1312
+ }
1313
+ }
1289
1314
  },
1290
1315
  "workCompleted": {
1291
1316
  "ta6ed6Ref": "5.1c(b)",
@@ -2895,7 +2920,12 @@
2895
2920
  ],
2896
2921
  "required": [
2897
2922
  "yesNo"
2898
- ]
2923
+ ],
2924
+ "properties": {
2925
+ "yesNo": {
2926
+ "ta6ed6Ref": "5.6c"
2927
+ }
2928
+ }
2899
2929
  },
2900
2930
  "panelProviderLease": {
2901
2931
  "ta6ed6Ref": "5.6d",
@@ -3367,6 +3397,7 @@
3367
3397
  ],
3368
3398
  "properties": {
3369
3399
  "yesNo": {
3400
+ "ta6ed6Ref": "11.5b",
3370
3401
  "enum": [
3371
3402
  "Yes",
3372
3403
  "No",
@@ -3655,7 +3686,12 @@
3655
3686
  ],
3656
3687
  "required": [
3657
3688
  "yesNo"
3658
- ]
3689
+ ],
3690
+ "properties": {
3691
+ "yesNo": {
3692
+ "ta6ed6Ref": "11.7j"
3693
+ }
3694
+ }
3659
3695
  },
3660
3696
  "plantDrainsIntoWaterway": {
3661
3697
  "ta6ed6Ref": "11.7f",
@@ -4210,8 +4246,7 @@
4210
4246
  ],
4211
4247
  "required": [
4212
4248
  "isInsured",
4213
- "difficultiesObtainingInsurance",
4214
- "insuranceClaims"
4249
+ "difficultiesObtainingInsurance"
4215
4250
  ],
4216
4251
  "properties": {
4217
4252
  "difficultiesObtainingInsurance": {
@@ -4483,12 +4518,7 @@
4483
4518
  "description": "If yes, give details of how much, how often and who you pay:",
4484
4519
  "properties": {
4485
4520
  "yesNo": {
4486
- "ta6ed6Ref": "9.2.1",
4487
- "enum": [
4488
- "Yes",
4489
- "No",
4490
- "Not applicable"
4491
- ]
4521
+ "ta6ed6Ref": "9.2.1"
4492
4522
  }
4493
4523
  }
4494
4524
  },
@@ -6288,7 +6318,12 @@
6288
6318
  "ta6ed6Ref": "13.6a",
6289
6319
  "required": [
6290
6320
  "yesNo"
6291
- ]
6321
+ ],
6322
+ "properties": {
6323
+ "yesNo": {
6324
+ "ta6ed6Ref": "13.6a"
6325
+ }
6326
+ }
6292
6327
  },
6293
6328
  "aged17OrOverWillSignToConfirmWillVacate": {
6294
6329
  "ta6ed6Ref": "13.6b",
@@ -6377,19 +6412,19 @@
6377
6412
  "properties": {
6378
6413
  "removeRubbish": {
6379
6414
  "ta6ed6Ref": "14.2a",
6380
- "title": "(a) all rubbish is removed from the property (including from the loft, garden, outbuildings, garages and sheds) and that the property will be left in a clean and tidy condition?"
6415
+ "title": "All rubbish is removed from the property (including from the loft, garden, outbuildings, garages and sheds) and that the property will be left in a clean and tidy condition?"
6381
6416
  },
6382
6417
  "replaceLightFittings": {
6383
6418
  "ta6ed6Ref": "14.2b",
6384
- "title": "(b) if light fittings are removed, the fittings will be replaced with ceiling rose, flex, bulb holder and bulb?"
6419
+ "title": "If light fittings are removed, the fittings will be replaced with ceiling rose, flex, bulb holder and bulb?"
6385
6420
  },
6386
6421
  "takeReasonableCare": {
6387
6422
  "ta6ed6Ref": "14.2c",
6388
- "title": "(c) reasonable care will be taken when removing any other fittings or contents?"
6423
+ "title": "Reasonable care will be taken when removing any other fittings or contents?"
6389
6424
  },
6390
6425
  "leaveKeys": {
6391
6426
  "ta6ed6Ref": "14.2d",
6392
- "title": "(d) keys to all windows and doors and details of codes for alarms and any other equipment will be left at the property or with the estate agent?"
6427
+ "title": "Keys to all windows and doors and details of codes for alarms and any other equipment will be left at the property or with the estate agent?"
6393
6428
  }
6394
6429
  }
6395
6430
  },
@@ -74,8 +74,6 @@
74
74
  "ta7ed5Ref": "2",
75
75
  "required": [
76
76
  "typeOfLeasehold",
77
- "sharedOwnership",
78
- "leaseTerm",
79
77
  "contactDetails",
80
78
  "consents",
81
79
  "groundRent",
@@ -84,8 +82,6 @@
84
82
  "disputes",
85
83
  "buildingSafetyAct",
86
84
  "enfranchisement",
87
- "buildingSafety",
88
- "general",
89
85
  "requiredDocuments",
90
86
  "additionalInformation",
91
87
  "confirmationOfAccuracy"
@@ -1451,6 +1447,17 @@
1451
1447
  "ta7ed5Ref": "12.2"
1452
1448
  }
1453
1449
  }
1450
+ },
1451
+ "confirmationOfAccuracy": {
1452
+ "ta7ed5Ref": "13",
1453
+ "required": [
1454
+ "confirmation"
1455
+ ],
1456
+ "properties": {
1457
+ "confirmation": {
1458
+ "ta7ed5Ref": "13.1"
1459
+ }
1460
+ }
1454
1461
  }
1455
1462
  }
1456
1463
  }
@@ -1472,7 +1479,12 @@
1472
1479
  "ta7ed5Ref": "2",
1473
1480
  "required": [
1474
1481
  "ownershipType"
1475
- ]
1482
+ ],
1483
+ "properties": {
1484
+ "ownershipType": {
1485
+ "ta7ed5Ref": "2"
1486
+ }
1487
+ }
1476
1488
  }
1477
1489
  }
1478
1490
  }
@@ -241,6 +241,8 @@ describe("Extension Overlays", () => {
241
241
  "nd",
242
242
  "mi",
243
243
  "tr", // SEF25 (new)
244
+ "ac", // SEF25 (new)
245
+ "ta", // Compatibility
244
246
  ];
245
247
 
246
248
  expect(Object.keys(extensionOverlays).sort()).toEqual(
@@ -258,6 +260,65 @@ describe("Extension Overlays", () => {
258
260
  });
259
261
  });
260
262
 
263
+ describe("TA6 edition 6 compatibility extension", () => {
264
+ test("keeps parking enum strict to TA6 edition 6 when compatibility is applied", () => {
265
+ const ta6Validator = getSubschemaValidator(
266
+ "/propertyPack/parking/controlledParking",
267
+ schemaId,
268
+ ["ta6ed6"]
269
+ );
270
+ expect(ta6Validator({ yesNo: "Not known" })).toBe(false);
271
+
272
+ const compatValidator = getSubschemaValidator(
273
+ "/propertyPack/parking/controlledParking",
274
+ schemaId,
275
+ ["nts2025", "ta"]
276
+ );
277
+ expect(compatValidator({ yesNo: "Not known" })).toBe(false);
278
+ expect(compatValidator({ yesNo: "No" })).toBe(true);
279
+ });
280
+
281
+ test("applies TA6 edition 6 selector values on shared rights and boundaries paths", () => {
282
+ const schema = getTransactionSchema(schemaId, [
283
+ "nts2025",
284
+ "ta",
285
+ ]);
286
+ const propertyPack = schema.properties.propertyPack.properties;
287
+
288
+ expect(
289
+ propertyPack.rightsAndInformalArrangements.properties
290
+ .sharedContributions.oneOf[0].properties.yesNo.enum
291
+ ).toEqual(["No", "Not applicable"]);
292
+
293
+ expect(
294
+ propertyPack.rightsAndInformalArrangements.properties
295
+ .accessRestrictionAttempts.oneOf[0].properties.yesNo.enum
296
+ ).toEqual(["No", "Not known"]);
297
+
298
+ expect(
299
+ propertyPack.legalBoundaries.properties.flyingFreehold.oneOf[0]
300
+ .properties.yesNo.enum
301
+ ).toEqual(["No", "Not known"]);
302
+ });
303
+
304
+ test("applies TA6 edition 6 heating values and report enum when compatibility is applied", () => {
305
+ const heatingFeatures = getSubschemaValidator(
306
+ "/propertyPack/heating/otherHeatingFeatures",
307
+ schemaId,
308
+ ["nts2025", "ta"]
309
+ );
310
+ expect(heatingFeatures(["Mains gas", "LPG", "Biomass"])).toBe(true);
311
+
312
+ const compatReport = getSubschemaValidator(
313
+ "/propertyPack/heating/heatingSystem/centralHeatingDetails/heatingLastServicedReport",
314
+ schemaId,
315
+ ["nts2025", "ta"]
316
+ );
317
+ expect(compatReport("Not available")).toBe(false);
318
+ expect(compatReport("Attached")).toBe(true);
319
+ });
320
+ });
321
+
261
322
  describe("NTS2 equivalence test", () => {
262
323
  test("should produce equivalent result to nts2 when all relevant extensions are combined", () => {
263
324
  // Get the full NTS2 schema
@@ -1404,8 +1465,110 @@ describe("Extension Overlays", () => {
1404
1465
  });
1405
1466
  });
1406
1467
 
1468
+ describe("Alterations and changes (ac)", () => {
1469
+ const alterationFields = [
1470
+ "extension",
1471
+ "loftConversion",
1472
+ "garageConversion",
1473
+ "removalOfInternalWalls",
1474
+ "changeOfUse",
1475
+ ];
1476
+
1477
+ test("should expose only selected alterations and changes fields", () => {
1478
+ const schema = getTransactionSchema(schemaId, ["ac"]);
1479
+ const alterations =
1480
+ schema.properties?.propertyPack?.properties?.alterationsAndChanges;
1481
+ const acOverlayAlterations =
1482
+ extensionOverlays.ac.properties?.propertyPack?.properties
1483
+ ?.alterationsAndChanges;
1484
+
1485
+ expect(alterations?.ntsRef).toBe("A1");
1486
+ expect(alterations?.required).toEqual(
1487
+ expect.arrayContaining(alterationFields)
1488
+ );
1489
+ expect(Object.keys(acOverlayAlterations?.properties || {}).sort()).toEqual(
1490
+ alterationFields.slice().sort()
1491
+ );
1492
+
1493
+ alterationFields.forEach((field) => {
1494
+ const fieldSchema = alterations.properties?.[field];
1495
+ const yesBranch = fieldSchema?.oneOf?.[1];
1496
+ const planningPermission =
1497
+ yesBranch?.properties?.planningPermission;
1498
+ const buildingRegApproval =
1499
+ yesBranch?.properties?.buildingRegApproval;
1500
+ const overlayFieldSchema = acOverlayAlterations.properties?.[field];
1501
+ const overlayYesBranch =
1502
+ overlayFieldSchema?.oneOf?.[1];
1503
+ const overlayPlanningPermission =
1504
+ overlayYesBranch?.properties?.planningPermission;
1505
+ const overlayBuildingRegApproval =
1506
+ overlayYesBranch?.properties?.buildingRegApproval;
1507
+
1508
+ expect(fieldSchema?.required).toContain("yesNo");
1509
+ expect(fieldSchema?.discriminator).toEqual({
1510
+ propertyName: "yesNo",
1511
+ });
1512
+ expect(overlayFieldSchema?.discriminator).toEqual({
1513
+ propertyName: "yesNo",
1514
+ });
1515
+ expect(fieldSchema?.properties?.yesNo?.ntsRef).toMatch(/^A1\./);
1516
+ expect(yesBranch?.required).toEqual(
1517
+ expect.arrayContaining([
1518
+ "details",
1519
+ "planningPermission",
1520
+ "buildingRegApproval",
1521
+ ])
1522
+ );
1523
+ expect(Object.keys(overlayYesBranch?.properties || {}).sort()).toEqual([
1524
+ "buildingRegApproval",
1525
+ "details",
1526
+ "planningPermission",
1527
+ ]);
1528
+ expect(planningPermission?.discriminator).toEqual({
1529
+ propertyName: "yesNo",
1530
+ });
1531
+ expect(buildingRegApproval?.discriminator).toEqual({
1532
+ propertyName: "yesNo",
1533
+ });
1534
+ expect(overlayPlanningPermission?.discriminator).toEqual({
1535
+ propertyName: "yesNo",
1536
+ });
1537
+ expect(overlayBuildingRegApproval?.discriminator).toEqual({
1538
+ propertyName: "yesNo",
1539
+ });
1540
+ expect(yesBranch?.properties?.workCompleted?.ntsRef).toBeUndefined();
1541
+ expect(yesBranch?.properties?.documents?.ntsRef).toBeUndefined();
1542
+ expect(yesBranch?.properties?.yearCompleted?.ntsRef).toBeUndefined();
1543
+ expect(yesBranch?.properties?.listedBuildingConsent?.ntsRef).toBeUndefined();
1544
+ expect(yesBranch?.properties?.deedRestrictionConsent?.ntsRef).toBeUndefined();
1545
+ });
1546
+ });
1547
+
1548
+ test("should require details and consent yesNo fields for Yes answers", () => {
1549
+ alterationFields.forEach((field) => {
1550
+ const validator = getSubschemaValidator(
1551
+ `/propertyPack/alterationsAndChanges/${field}`,
1552
+ schemaId,
1553
+ ["ac"]
1554
+ );
1555
+
1556
+ expect(validator({ yesNo: "No" })).toBe(true);
1557
+ expect(validator({ yesNo: "Yes" })).toBe(false);
1558
+ expect(
1559
+ validator({
1560
+ yesNo: "Yes",
1561
+ details: `${field} details`,
1562
+ planningPermission: { yesNo: "Not required" },
1563
+ buildingRegApproval: { yesNo: "Yes" },
1564
+ })
1565
+ ).toBe(true);
1566
+ });
1567
+ });
1568
+ });
1569
+
1407
1570
  describe("All SEF25 extensions combined", () => {
1408
- const allSef25 = ["sc", "pc", "ph", "dk", "rw", "sd", "lc", "wg", "ic", "nd", "mi", "tr"];
1571
+ const allSef25 = ["sc", "pc", "ph", "dk", "rw", "sd", "lc", "wg", "ic", "nd", "mi", "tr", "ac"];
1409
1572
 
1410
1573
  test("should load all SEF25 extensions together without error", () => {
1411
1574
  const schema = getTransactionSchema(schemaId, ["baspiV5", ...allSef25]);
@@ -2,10 +2,38 @@ const {
2
2
  getTransactionSchema,
3
3
  getSubschemaValidator,
4
4
  } = require("../../../index.js");
5
+ const ta6ed6Overlay = require("../../schemas/v3/overlays/ta6ed6.json");
5
6
 
6
7
  const schemaId =
7
8
  "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
8
9
 
10
+ const collectRequiredWithoutPropertyIssues = (schema) => {
11
+ const issues = [];
12
+
13
+ const walk = (node, path = "#") => {
14
+ if (!node || typeof node !== "object") return;
15
+
16
+ if (Array.isArray(node.required)) {
17
+ node.required.forEach((propertyName) => {
18
+ if (!node.properties?.[propertyName]) {
19
+ issues.push(`${path} requires ${propertyName}`);
20
+ }
21
+ });
22
+ }
23
+
24
+ Object.entries(node.properties || {}).forEach(([key, value]) =>
25
+ walk(value, `${path}/properties/${key}`)
26
+ );
27
+ (node.oneOf || []).forEach((value, index) =>
28
+ walk(value, `${path}/oneOf/${index}`)
29
+ );
30
+ if (node.items) walk(node.items, `${path}/items`);
31
+ };
32
+
33
+ walk(schema);
34
+ return issues;
35
+ };
36
+
9
37
  describe("TA6 Edition 6 overlay", () => {
10
38
  describe("Schema merging", () => {
11
39
  test("merged schema contains ta6ed6Ref annotations", () => {
@@ -21,6 +49,10 @@ describe("TA6 Edition 6 overlay", () => {
21
49
  ).toBe("11");
22
50
  });
23
51
 
52
+ test("generated overlay exposes every TA6 edition 6 required property", () => {
53
+ expect(collectRequiredWithoutPropertyIssues(ta6ed6Overlay)).toEqual([]);
54
+ });
55
+
24
56
  test("merged schema has all 22 ta6ed6 required propertyPack sections", () => {
25
57
  const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
26
58
  const required = schema.properties.propertyPack.required;
@@ -751,11 +783,26 @@ describe("TA6 Edition 6 overlay", () => {
751
783
  refused: { yesNo: "No" },
752
784
  },
753
785
  whoInsures: "Landlord insures the building",
754
- insuranceClaims: { yesNo: "No" },
755
786
  };
756
787
  expect(validator(data)).toBe(true);
757
788
  });
758
789
 
790
+ test("isInsured Yes without insuranceClaims is invalid", () => {
791
+ const data = {
792
+ isInsured: "Yes",
793
+ difficultiesObtainingInsurance: {
794
+ abnormalRiseInPremiums: { yesNo: "No" },
795
+ subjectToHighExcesses: { yesNo: "No" },
796
+ subjectToUnusualConditions: { yesNo: "No" },
797
+ refused: { yesNo: "No" },
798
+ },
799
+ };
800
+ expect(validator(data)).toBe(false);
801
+ expect(
802
+ validator.errors.some((e) => e.message.includes("'insuranceClaims'"))
803
+ ).toBe(true);
804
+ });
805
+
759
806
  test("isInsured No without whoInsures is invalid", () => {
760
807
  const data = {
761
808
  isInsured: "No",
@@ -1585,6 +1632,16 @@ describe("TA6 Edition 6 overlay", () => {
1585
1632
  expect(mfd.discriminator).toBeDefined();
1586
1633
  });
1587
1634
 
1635
+ test("plant registered yesNo carries TA6 edition 6 ref", () => {
1636
+ const schema = getTransactionSchema(schemaId, ["ta6ed6"]);
1637
+ const plantRegistered =
1638
+ schema.properties.propertyPack.properties.waterAndDrainage.properties
1639
+ .drainage.properties.mainsFoulDrainage.oneOf[2].properties
1640
+ .offMainsDrainageSystem.properties.plantRegistered;
1641
+ expect(plantRegistered.ta6ed6Ref).toBe("11.7j");
1642
+ expect(plantRegistered.properties.yesNo.ta6ed6Ref).toBe("11.7j");
1643
+ });
1644
+
1588
1645
  test("mainsFoulDrainage No with object is accepted", () => {
1589
1646
  const data = {
1590
1647
  water: {
@@ -2,12 +2,40 @@ const {
2
2
  getTransactionSchema,
3
3
  getSubschemaValidator,
4
4
  } = require("../../../index.js");
5
+ const ta7ed5Overlay = require("../../schemas/v3/overlays/ta7ed5.json");
5
6
 
6
7
  const schemaId =
7
8
  "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
8
9
 
9
10
  const overlays = ["ta7ed5"];
10
11
 
12
+ const collectRequiredWithoutPropertyIssues = (schema) => {
13
+ const issues = [];
14
+
15
+ const walk = (node, path = "#") => {
16
+ if (!node || typeof node !== "object") return;
17
+
18
+ if (Array.isArray(node.required)) {
19
+ node.required.forEach((propertyName) => {
20
+ if (!node.properties?.[propertyName]) {
21
+ issues.push(`${path} requires ${propertyName}`);
22
+ }
23
+ });
24
+ }
25
+
26
+ Object.entries(node.properties || {}).forEach(([key, value]) =>
27
+ walk(value, `${path}/properties/${key}`)
28
+ );
29
+ (node.oneOf || []).forEach((value, index) =>
30
+ walk(value, `${path}/oneOf/${index}`)
31
+ );
32
+ if (node.items) walk(node.items, `${path}/items`);
33
+ };
34
+
35
+ walk(schema);
36
+ return issues;
37
+ };
38
+
11
39
  // Helper: navigate to a deeply nested property in the merged schema
12
40
  const getSchemaProperty = (schema, dotPath) => {
13
41
  return dotPath.split(".").reduce((obj, key) => obj?.[key], schema);
@@ -37,7 +65,11 @@ describe("TA7 5th edition overlay", () => {
37
65
  expect(required).toContain("ownership");
38
66
  });
39
67
 
40
- test("leasehold branch requires the 15 ta7ed5 sections", () => {
68
+ test("generated overlay exposes every TA7 edition 5 required property", () => {
69
+ expect(collectRequiredWithoutPropertyIssues(ta7ed5Overlay)).toEqual([]);
70
+ });
71
+
72
+ test("leasehold branch requires the referenced ta7ed5 sections", () => {
41
73
  const leaseholdOneOf =
42
74
  schema.properties?.propertyPack?.properties?.ownership?.properties
43
75
  ?.ownershipsToBeTransferred?.items?.oneOf;
@@ -60,6 +92,7 @@ describe("TA7 5th edition overlay", () => {
60
92
  expect(required).toContain("requiredDocuments");
61
93
  expect(required).toContain("additionalInformation");
62
94
  expect(required).toContain("confirmationOfAccuracy");
95
+ expect(required).not.toContain("buildingSafety");
63
96
  });
64
97
  });
65
98
 
@@ -307,10 +307,59 @@ const extensionMappings = {
307
307
  "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/titleRestrictions",
308
308
  ],
309
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
+ },
310
354
  };
311
355
 
312
356
  // Function to extract properties at specific paths with given ref type
313
- function extractPathsWithRef(schema, paths, refConfig) {
357
+ function extractPathsWithRef(
358
+ schema,
359
+ paths,
360
+ refConfig,
361
+ preserveDiscriminators = false
362
+ ) {
314
363
  const result = {};
315
364
 
316
365
  paths.forEach((path) => {
@@ -319,6 +368,9 @@ function extractPathsWithRef(schema, paths, refConfig) {
319
368
  if (value && hasRef(value, refConfig)) {
320
369
  // Set the value at the same path in result
321
370
  jp.set(result, path, extractRefProperties(value, refConfig));
371
+ if (preserveDiscriminators) {
372
+ addAncestorDiscriminators(result, schema, path);
373
+ }
322
374
  }
323
375
  } catch (err) {
324
376
  console.warn(`Path not found: ${path}`);
@@ -328,6 +380,26 @@ function extractPathsWithRef(schema, paths, refConfig) {
328
380
  return result;
329
381
  }
330
382
 
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
+
331
403
  // Check if an object or its descendants have the specified ref
332
404
  function hasRef(obj, refConfig) {
333
405
  let found = false;
@@ -522,7 +594,12 @@ Object.entries(extensionMappings).forEach(([code, config]) => {
522
594
  console.log(`\nGenerating extension overlay: ${code} (${config.name}) [${refType}]`);
523
595
 
524
596
  // Extract the specific paths
525
- let overlay = extractPathsWithRef(combinedSchema, config.paths, refConfig);
597
+ let overlay = extractPathsWithRef(
598
+ combinedSchema,
599
+ config.paths,
600
+ refConfig,
601
+ config.preserveDiscriminators
602
+ );
526
603
 
527
604
  // Add required arrays where needed
528
605
  overlay = addRequiredArrays(overlay, combinedSchema, refConfig);