@pdtf/schemas 3.4.1-1 → 3.4.1-3
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/index.js +67 -73
- package/package.json +1 -1
- package/src/schemas/v3/overlays/extensions/as.json +15 -0
- package/src/schemas/v3/overlays/extensions/dr.json +14 -1
- package/src/schemas/v3/overlays/extensions/er.json +9 -0
- package/src/schemas/v3/overlays/extensions/fd.json +9 -0
- package/src/schemas/v3/overlays/extensions/hi.json +4 -0
- package/src/schemas/v3/overlays/extensions/hs.json +15 -0
- package/src/schemas/v3/overlays/extensions/jk.json +22 -1
- package/src/schemas/v3/overlays/extensions/la.json +19 -2
- package/src/schemas/v3/overlays/extensions/oc.json +20 -0
- package/src/schemas/v3/overlays/extensions/sb.json +15 -0
- package/src/schemas/v3/overlays/extensions/sf.json +15 -0
- package/src/schemas/v3/overlays/extensions/sl.json +20 -0
- package/src/schemas/v3/overlays/extensions/tf.json +9 -0
- package/src/schemas/v3/property-transaction-milestones.json +91 -0
- package/src/tests/v3/extensionOverlays.test.js +270 -81
- package/src/tests/v3/transactionSchema.test.js +0 -228
- package/src/utils/extractExtensionOverlays.js +153 -62
|
@@ -8,7 +8,6 @@ const {
|
|
|
8
8
|
getValidator,
|
|
9
9
|
getSubschemaValidator,
|
|
10
10
|
getTitleAtPath,
|
|
11
|
-
overlaysMap,
|
|
12
11
|
} = require("../../../index.js");
|
|
13
12
|
|
|
14
13
|
const exampleTransaction = require("../../examples/v3/exampleTransaction.json");
|
|
@@ -33,14 +32,12 @@ test("sample is valid BASPI", () => {
|
|
|
33
32
|
expect(testSchema.properties.propertyPack.baspi4Ref).toEqual("0");
|
|
34
33
|
const validator = getValidator(schemaId, ["baspiV4"]);
|
|
35
34
|
const isValid = validator(exampleTransaction);
|
|
36
|
-
if (!isValid) console.log(validator.errors);
|
|
37
35
|
expect(isValid).toBe(true);
|
|
38
36
|
});
|
|
39
37
|
|
|
40
38
|
test("sample is valid NTS", () => {
|
|
41
39
|
const validator = getValidator(schemaId, ["nts2023"]);
|
|
42
40
|
const isValid = validator(exampleTransaction);
|
|
43
|
-
if (!isValid) console.log(validator.errors);
|
|
44
41
|
expect(isValid).toBe(true);
|
|
45
42
|
});
|
|
46
43
|
|
|
@@ -102,7 +99,6 @@ test("sample is valid NTSL if we change it accordingly", () => {
|
|
|
102
99
|
delete clonedExampleTransaction.propertyPack.priceInformation;
|
|
103
100
|
delete clonedExampleTransaction.propertyPack.ownership;
|
|
104
101
|
const isValid = validator(clonedExampleTransaction);
|
|
105
|
-
if (!isValid) console.log(validator.errors);
|
|
106
102
|
expect(isValid).toBe(true);
|
|
107
103
|
});
|
|
108
104
|
|
|
@@ -452,227 +448,3 @@ test("validates a valid contract", () => {
|
|
|
452
448
|
const isValid = validator(data);
|
|
453
449
|
expect(isValid).toBe(true);
|
|
454
450
|
});
|
|
455
|
-
|
|
456
|
-
test("overlay as key and as object produce identical results", () => {
|
|
457
|
-
// Get the NTS overlay object directly from the overlays map
|
|
458
|
-
const ntsOverlayObject = { ...overlaysMap[schemaId]["nts2023"] };
|
|
459
|
-
// Remove the $id to prevent schema registration conflicts
|
|
460
|
-
ntsOverlayObject.$id =
|
|
461
|
-
"https://trust.propdata.org.uk/schemas/v3/overlays/nts2024.json";
|
|
462
|
-
|
|
463
|
-
// Get schema using the key
|
|
464
|
-
const schemaWithKey = getTransactionSchema(schemaId, ["nts2023"]);
|
|
465
|
-
|
|
466
|
-
// Get schema using the object
|
|
467
|
-
const schemaWithObject = getTransactionSchema(schemaId, [ntsOverlayObject]);
|
|
468
|
-
|
|
469
|
-
// Create copies without $id for comparison
|
|
470
|
-
const schemaWithKeyNoId = { ...schemaWithKey };
|
|
471
|
-
const schemaWithObjectNoId = { ...schemaWithObject };
|
|
472
|
-
delete schemaWithKeyNoId.$id;
|
|
473
|
-
delete schemaWithObjectNoId.$id;
|
|
474
|
-
|
|
475
|
-
// Deep compare the results (excluding $id)
|
|
476
|
-
expect(schemaWithObjectNoId).toEqual(schemaWithKeyNoId);
|
|
477
|
-
|
|
478
|
-
// Test that validation produces the same errors
|
|
479
|
-
const validatorWithKey = getValidator(schemaId, ["nts2023"]);
|
|
480
|
-
const validatorWithObject = getValidator(schemaId, [ntsOverlayObject]);
|
|
481
|
-
|
|
482
|
-
validatorWithKey(exampleTransaction);
|
|
483
|
-
validatorWithObject(exampleTransaction);
|
|
484
|
-
|
|
485
|
-
// Compare validation errors
|
|
486
|
-
expect(validatorWithObject.errors).toEqual(validatorWithKey.errors);
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
test("correctly merges custom Japanese Knotweed overlay with base schema", () => {
|
|
490
|
-
// Load the custom overlay
|
|
491
|
-
const customOverlay = require("../../examples/v3/exampleCustomOverlay.json");
|
|
492
|
-
|
|
493
|
-
// Get base schema and schema with overlay
|
|
494
|
-
const baseSchema = getTransactionSchema(schemaId);
|
|
495
|
-
const schemaWithOverlay = getTransactionSchema(schemaId, [customOverlay]);
|
|
496
|
-
|
|
497
|
-
// Verify that other properties in propertyPack are preserved
|
|
498
|
-
expect(
|
|
499
|
-
Object.keys(schemaWithOverlay.properties.propertyPack.properties)
|
|
500
|
-
).toEqual(Object.keys(baseSchema.properties.propertyPack.properties));
|
|
501
|
-
|
|
502
|
-
// Verify that other properties in specialistIssues are preserved
|
|
503
|
-
const baseSpecialistIssues =
|
|
504
|
-
baseSchema.properties.propertyPack.properties.specialistIssues;
|
|
505
|
-
const overlaidSpecialistIssues =
|
|
506
|
-
schemaWithOverlay.properties.propertyPack.properties.specialistIssues;
|
|
507
|
-
expect(Object.keys(overlaidSpecialistIssues.properties)).toEqual(
|
|
508
|
-
Object.keys(baseSpecialistIssues.properties)
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
// Verify that Japanese Knotweed section is updated
|
|
512
|
-
const japaneseKnotweed = overlaidSpecialistIssues.properties.japaneseKnotweed;
|
|
513
|
-
expect(japaneseKnotweed.title).toBe(
|
|
514
|
-
"Is the property affected by Japanese knotweed?"
|
|
515
|
-
);
|
|
516
|
-
expect(japaneseKnotweed.required).toEqual(["yesNo"]);
|
|
517
|
-
|
|
518
|
-
// Verify the oneOf conditions are updated
|
|
519
|
-
expect(japaneseKnotweed.oneOf).toHaveLength(2);
|
|
520
|
-
expect(japaneseKnotweed.oneOf[0].properties.yesNo.enum).toEqual([
|
|
521
|
-
"No",
|
|
522
|
-
"Not known",
|
|
523
|
-
]);
|
|
524
|
-
expect(japaneseKnotweed.oneOf[1].properties.yesNo.enum).toEqual(["Yes"]);
|
|
525
|
-
expect(japaneseKnotweed.oneOf[1].required).toEqual([
|
|
526
|
-
"managementPlanInPlace",
|
|
527
|
-
"attachments",
|
|
528
|
-
]);
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
test("correctly merges multiple overlays using both key and object references", () => {
|
|
532
|
-
// Load the custom overlay
|
|
533
|
-
const customOverlay = require("../../examples/v3/exampleCustomOverlay.json");
|
|
534
|
-
|
|
535
|
-
// Get schema with both overlays
|
|
536
|
-
const schemaWithOverlays = getTransactionSchema(schemaId, [
|
|
537
|
-
"nts2023",
|
|
538
|
-
customOverlay,
|
|
539
|
-
]);
|
|
540
|
-
|
|
541
|
-
// Get individual overlay schemas for comparison
|
|
542
|
-
const ntsSchema = getTransactionSchema(schemaId, ["nts2023"]);
|
|
543
|
-
const customSchema = getTransactionSchema(schemaId, [customOverlay]);
|
|
544
|
-
|
|
545
|
-
// Verify propertyPack structure is preserved
|
|
546
|
-
const propertyPack = schemaWithOverlays.properties.propertyPack;
|
|
547
|
-
expect(Object.keys(propertyPack.properties)).toEqual(
|
|
548
|
-
expect.arrayContaining(
|
|
549
|
-
Object.keys(ntsSchema.properties.propertyPack.properties)
|
|
550
|
-
)
|
|
551
|
-
);
|
|
552
|
-
|
|
553
|
-
// Verify Japanese Knotweed changes from custom overlay are present
|
|
554
|
-
const japaneseKnotweed =
|
|
555
|
-
propertyPack.properties.specialistIssues.properties.japaneseKnotweed;
|
|
556
|
-
expect(japaneseKnotweed.title).toBe(
|
|
557
|
-
"Is the property affected by Japanese knotweed?"
|
|
558
|
-
);
|
|
559
|
-
expect(japaneseKnotweed.oneOf[1].required).toEqual([
|
|
560
|
-
"managementPlanInPlace",
|
|
561
|
-
"attachments",
|
|
562
|
-
]);
|
|
563
|
-
|
|
564
|
-
// Verify customRef properties are correctly merged
|
|
565
|
-
expect(propertyPack.customRef).toBe("JKW");
|
|
566
|
-
expect(propertyPack.properties.specialistIssues.customRef).toBe("JKW.7");
|
|
567
|
-
expect(japaneseKnotweed.customRef).toBe("JKW.7.8");
|
|
568
|
-
expect(
|
|
569
|
-
japaneseKnotweed.oneOf[1].properties.managementPlanInPlace.customRef
|
|
570
|
-
).toBe("JKW.7.8.2");
|
|
571
|
-
expect(japaneseKnotweed.oneOf[1].properties.attachments.customRef).toBe(
|
|
572
|
-
"JKW.7.8.3"
|
|
573
|
-
);
|
|
574
|
-
expect(japaneseKnotweed.properties.yesNo.customRef).toBe("JKW.7.8.1");
|
|
575
|
-
|
|
576
|
-
// Verify NTS specific changes are present (e.g., simplified enums)
|
|
577
|
-
const mainsElectricity =
|
|
578
|
-
propertyPack.properties.electricity.properties.mainsElectricity;
|
|
579
|
-
expect(mainsElectricity.properties.yesNo.enum).toEqual(["Yes", "No"]);
|
|
580
|
-
|
|
581
|
-
// Verify required fields from both overlays are merged
|
|
582
|
-
expect(propertyPack.required).toEqual(
|
|
583
|
-
expect.arrayContaining([
|
|
584
|
-
...ntsSchema.properties.propertyPack.required,
|
|
585
|
-
...customSchema.properties.propertyPack.required,
|
|
586
|
-
])
|
|
587
|
-
);
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
test("correctly merges NTS and both custom overlays with proper required fields", () => {
|
|
591
|
-
// Load both custom overlays
|
|
592
|
-
const japaneseKnoweedOverlay = require("../../examples/v3/exampleCustomOverlay.json");
|
|
593
|
-
const managingAgentOverlay = require("../../examples/v3/exampleCustomOverlay2.json");
|
|
594
|
-
|
|
595
|
-
// Get schema with all overlays
|
|
596
|
-
const schemaWithOverlays = getTransactionSchema(schemaId, [
|
|
597
|
-
"nts2023",
|
|
598
|
-
japaneseKnoweedOverlay,
|
|
599
|
-
managingAgentOverlay,
|
|
600
|
-
]);
|
|
601
|
-
|
|
602
|
-
// Navigate to the leasehold section in the oneOf array
|
|
603
|
-
const leaseholdSchema =
|
|
604
|
-
schemaWithOverlays.properties.propertyPack.properties.ownership.properties.ownershipsToBeTransferred.items.oneOf.find(
|
|
605
|
-
(schema) => schema.properties.ownershipType.enum[0] === "Leasehold"
|
|
606
|
-
);
|
|
607
|
-
|
|
608
|
-
// Verify the leasehold information structure
|
|
609
|
-
const leaseholdInfo = leaseholdSchema.properties.leaseholdInformation;
|
|
610
|
-
|
|
611
|
-
// Check required fields - should include both NTS and custom overlay requirements
|
|
612
|
-
expect(leaseholdInfo.required).toContain("contactDetails");
|
|
613
|
-
expect(leaseholdInfo.required).toContain("leaseTerm"); // from NTS
|
|
614
|
-
expect(leaseholdInfo.required).toContain("groundRent"); // from NTS
|
|
615
|
-
|
|
616
|
-
// Verify managing agent contact structure and refs
|
|
617
|
-
const managingAgent =
|
|
618
|
-
leaseholdInfo.properties.contactDetails.properties.contacts.properties
|
|
619
|
-
.managingAgent;
|
|
620
|
-
expect(managingAgent.macRef).toBe("MAC.4.1c");
|
|
621
|
-
|
|
622
|
-
// Verify contact details structure
|
|
623
|
-
const contact = managingAgent.properties.contact;
|
|
624
|
-
expect(contact.properties.nameOrOrganisation.macRef).toBe("MAC.4.1c.1");
|
|
625
|
-
expect(contact.properties.address.macRef).toBe("MAC.4.1c.0");
|
|
626
|
-
expect(contact.properties.telephone.macRef).toBe("MAC.4.1c.7");
|
|
627
|
-
expect(contact.properties.emailAddress.macRef).toBe("MAC.4.1c.8");
|
|
628
|
-
|
|
629
|
-
// Verify Japanese Knotweed section is still present and correct
|
|
630
|
-
const japaneseKnotweed =
|
|
631
|
-
schemaWithOverlays.properties.propertyPack.properties.specialistIssues
|
|
632
|
-
.properties.japaneseKnotweed;
|
|
633
|
-
expect(japaneseKnotweed.customRef).toBe("JKW.7.8");
|
|
634
|
-
|
|
635
|
-
// Verify NTS changes are still present
|
|
636
|
-
const mainsElectricity =
|
|
637
|
-
schemaWithOverlays.properties.propertyPack.properties.electricity.properties
|
|
638
|
-
.mainsElectricity;
|
|
639
|
-
expect(mainsElectricity.properties.yesNo.enum).toEqual(["Yes", "No"]);
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
test("ta7 overlay as key and as object produce identical results at leasehold info level", () => {
|
|
643
|
-
// Get the TA7 overlay object directly from the overlays map
|
|
644
|
-
const ta7OverlayObject = { ...overlaysMap[schemaId]["ta7ed3"] };
|
|
645
|
-
// Remove the $id to prevent schema registration conflicts
|
|
646
|
-
ta7OverlayObject.$id =
|
|
647
|
-
"https://trust.propdata.org.uk/schemas/v3/overlays/ta7ed3-copy.json";
|
|
648
|
-
|
|
649
|
-
// Get schemas using both methods
|
|
650
|
-
const schemaWithKey = getTransactionSchema(schemaId, ["ta7ed3"]);
|
|
651
|
-
const schemaWithObject = getTransactionSchema(schemaId, [ta7OverlayObject]);
|
|
652
|
-
|
|
653
|
-
// Navigate to the leasehold information section in both schemas
|
|
654
|
-
const getLeaseholdInfo = (schema) => {
|
|
655
|
-
const ownershipsToBeTransferred =
|
|
656
|
-
schema.properties.propertyPack.properties.ownership.properties
|
|
657
|
-
.ownershipsToBeTransferred;
|
|
658
|
-
const leaseholdSchema = ownershipsToBeTransferred.items.oneOf.find(
|
|
659
|
-
(schema) => schema.properties.ownershipType.enum[0] === "Leasehold"
|
|
660
|
-
);
|
|
661
|
-
return leaseholdSchema.properties.leaseholdInformation;
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
const leaseholdInfoWithKey = getLeaseholdInfo(schemaWithKey);
|
|
665
|
-
const leaseholdInfoWithObject = getLeaseholdInfo(schemaWithObject);
|
|
666
|
-
|
|
667
|
-
// Deep compare the leasehold information sections
|
|
668
|
-
expect(leaseholdInfoWithObject).toEqual(leaseholdInfoWithKey);
|
|
669
|
-
|
|
670
|
-
// Verify specific TA7 requirements are present in both
|
|
671
|
-
expect(leaseholdInfoWithKey.required).toContain("contactDetails");
|
|
672
|
-
expect(leaseholdInfoWithObject.required).toContain("contactDetails");
|
|
673
|
-
|
|
674
|
-
// Verify TA7 refs are present and identical
|
|
675
|
-
const contactDetails = leaseholdInfoWithKey.properties.contactDetails;
|
|
676
|
-
expect(contactDetails.ta7Ref).toBe("4.1");
|
|
677
|
-
expect(leaseholdInfoWithObject.properties.contactDetails.ta7Ref).toBe("4.1");
|
|
678
|
-
});
|
|
@@ -11,120 +11,152 @@ const extensionMappings = {
|
|
|
11
11
|
oa: {
|
|
12
12
|
name: "outsideAreas",
|
|
13
13
|
description: "Outside areas extension for NTS",
|
|
14
|
-
paths: [
|
|
14
|
+
paths: [
|
|
15
|
+
"/properties/propertyPack/properties/residentialPropertyFeatures/properties/outsideAreas",
|
|
16
|
+
],
|
|
15
17
|
},
|
|
16
|
-
|
|
18
|
+
|
|
17
19
|
// Estate rentcharges for freehold
|
|
18
20
|
er: {
|
|
19
21
|
name: "estateRentcharges",
|
|
20
22
|
description: "Estate rentcharges for freehold properties",
|
|
21
|
-
paths: [
|
|
23
|
+
paths: [
|
|
24
|
+
"/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/estateRentcharges",
|
|
25
|
+
],
|
|
22
26
|
},
|
|
23
|
-
|
|
27
|
+
|
|
24
28
|
// Managing agent contact details for leasehold
|
|
25
29
|
ma: {
|
|
26
30
|
name: "managingAgent",
|
|
27
31
|
description: "Managing agent contact details for leasehold",
|
|
28
|
-
paths: [
|
|
32
|
+
paths: [
|
|
33
|
+
"/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/contactDetails",
|
|
34
|
+
],
|
|
29
35
|
},
|
|
30
|
-
|
|
36
|
+
|
|
31
37
|
// Transfer fees for leasehold
|
|
32
38
|
tf: {
|
|
33
39
|
name: "transferFees",
|
|
34
40
|
description: "Transfer fees for leasehold",
|
|
35
|
-
paths: [
|
|
41
|
+
paths: [
|
|
42
|
+
"/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/serviceCharge/oneOf/1/properties/transferFees",
|
|
43
|
+
],
|
|
36
44
|
},
|
|
37
|
-
|
|
45
|
+
|
|
38
46
|
// Main construction type if standard
|
|
39
47
|
mc: {
|
|
40
48
|
name: "mainConstruction",
|
|
41
49
|
description: "Main construction type if standard form",
|
|
42
|
-
paths: [
|
|
50
|
+
paths: [
|
|
51
|
+
"/properties/propertyPack/properties/typeOfConstruction/properties/isStandardForm/oneOf/0/properties/constructionType",
|
|
52
|
+
],
|
|
43
53
|
},
|
|
44
|
-
|
|
54
|
+
|
|
45
55
|
// Loft access and details
|
|
46
56
|
la: {
|
|
47
57
|
name: "loftAccess",
|
|
48
58
|
description: "Loft access and details",
|
|
49
|
-
paths: [
|
|
59
|
+
paths: [
|
|
60
|
+
"/properties/propertyPack/properties/typeOfConstruction/properties/loft",
|
|
61
|
+
],
|
|
50
62
|
},
|
|
51
|
-
|
|
63
|
+
|
|
52
64
|
// Spray foam insulation
|
|
53
65
|
sf: {
|
|
54
66
|
name: "sprayFoam",
|
|
55
67
|
description: "Spray foam insulation",
|
|
56
|
-
paths: [
|
|
68
|
+
paths: [
|
|
69
|
+
"/properties/propertyPack/properties/typeOfConstruction/properties/sprayFoamInsulation",
|
|
70
|
+
],
|
|
57
71
|
},
|
|
58
|
-
|
|
72
|
+
|
|
59
73
|
// Specialist issues - dry rot
|
|
60
74
|
dr: {
|
|
61
75
|
name: "dryRot",
|
|
62
76
|
description: "Dry rot treatment specialist issue",
|
|
63
|
-
paths: [
|
|
77
|
+
paths: [
|
|
78
|
+
"/properties/propertyPack/properties/specialistIssues/properties/dryRotEtcTreatment",
|
|
79
|
+
],
|
|
64
80
|
},
|
|
65
|
-
|
|
81
|
+
|
|
66
82
|
// Specialist issues - asbestos
|
|
67
83
|
as: {
|
|
68
84
|
name: "asbestos",
|
|
69
85
|
description: "Asbestos specialist issue",
|
|
70
|
-
paths: [
|
|
86
|
+
paths: [
|
|
87
|
+
"/properties/propertyPack/properties/specialistIssues/properties/containsAsbestos",
|
|
88
|
+
],
|
|
71
89
|
},
|
|
72
|
-
|
|
90
|
+
|
|
73
91
|
// Specialist issues - Japanese knotweed
|
|
74
92
|
jk: {
|
|
75
93
|
name: "japaneseKnotweed",
|
|
76
94
|
description: "Japanese knotweed specialist issue",
|
|
77
|
-
paths: [
|
|
95
|
+
paths: [
|
|
96
|
+
"/properties/propertyPack/properties/specialistIssues/properties/japaneseKnotweed",
|
|
97
|
+
],
|
|
78
98
|
},
|
|
79
|
-
|
|
99
|
+
|
|
80
100
|
// Specialist issues - subsidence
|
|
81
101
|
sb: {
|
|
82
102
|
name: "subsidence",
|
|
83
103
|
description: "Subsidence or structural fault specialist issue",
|
|
84
|
-
paths: [
|
|
104
|
+
paths: [
|
|
105
|
+
"/properties/propertyPack/properties/specialistIssues/properties/subsidenceOrStructuralFault",
|
|
106
|
+
],
|
|
85
107
|
},
|
|
86
|
-
|
|
108
|
+
|
|
87
109
|
// Specialist issues - health and safety
|
|
88
110
|
hs: {
|
|
89
111
|
name: "healthSafety",
|
|
90
112
|
description: "Health and safety specialist issue",
|
|
91
|
-
paths: [
|
|
113
|
+
paths: [
|
|
114
|
+
"/properties/propertyPack/properties/specialistIssues/properties/ongoingHealthOrSafetyIssue",
|
|
115
|
+
],
|
|
92
116
|
},
|
|
93
|
-
|
|
117
|
+
|
|
94
118
|
// Solar panels leased
|
|
95
119
|
sl: {
|
|
96
120
|
name: "solarPanelsLeased",
|
|
97
121
|
description: "Solar panels ownership details",
|
|
98
|
-
paths: [
|
|
122
|
+
paths: [
|
|
123
|
+
"/properties/propertyPack/properties/electricity/properties/solarPanels/oneOf/1/properties/panelsOwnedOutright",
|
|
124
|
+
],
|
|
99
125
|
},
|
|
100
|
-
|
|
126
|
+
|
|
101
127
|
// Heating installation date
|
|
102
128
|
hi: {
|
|
103
129
|
name: "heatingInstalled",
|
|
104
130
|
description: "Central heating installation date",
|
|
105
|
-
paths: [
|
|
131
|
+
paths: [
|
|
132
|
+
"/properties/propertyPack/properties/heating/properties/heatingSystem/oneOf/1/properties/centralHeatingDetails/properties/centralHeatingInstalled",
|
|
133
|
+
],
|
|
106
134
|
},
|
|
107
|
-
|
|
135
|
+
|
|
108
136
|
// Flood defences
|
|
109
137
|
fd: {
|
|
110
138
|
name: "floodDefences",
|
|
111
139
|
description: "Flood defence information",
|
|
112
|
-
paths: [
|
|
140
|
+
paths: [
|
|
141
|
+
"/properties/propertyPack/properties/environmentalIssues/properties/flooding/properties/floodDefences",
|
|
142
|
+
],
|
|
113
143
|
},
|
|
114
|
-
|
|
144
|
+
|
|
115
145
|
// Other property in chain
|
|
116
146
|
oc: {
|
|
117
147
|
name: "otherPropertyChain",
|
|
118
148
|
description: "Other property in chain dependency",
|
|
119
|
-
paths: [
|
|
120
|
-
|
|
149
|
+
paths: [
|
|
150
|
+
"/properties/propertyPack/properties/completionAndMoving/properties/otherPropertyInChain",
|
|
151
|
+
],
|
|
152
|
+
},
|
|
121
153
|
};
|
|
122
154
|
|
|
123
155
|
// Function to extract properties at specific paths with nts2Ref
|
|
124
156
|
function extractPathsWithNts2Ref(schema, paths) {
|
|
125
157
|
const result = {};
|
|
126
|
-
|
|
127
|
-
paths.forEach(path => {
|
|
158
|
+
|
|
159
|
+
paths.forEach((path) => {
|
|
128
160
|
try {
|
|
129
161
|
const value = jp.get(schema, path);
|
|
130
162
|
if (value && hasNts2Ref(value)) {
|
|
@@ -135,14 +167,14 @@ function extractPathsWithNts2Ref(schema, paths) {
|
|
|
135
167
|
console.warn(`Path not found: ${path}`);
|
|
136
168
|
}
|
|
137
169
|
});
|
|
138
|
-
|
|
170
|
+
|
|
139
171
|
return result;
|
|
140
172
|
}
|
|
141
173
|
|
|
142
174
|
// Check if an object or its descendants have nts2Ref
|
|
143
175
|
function hasNts2Ref(obj) {
|
|
144
176
|
let found = false;
|
|
145
|
-
traverse(obj).forEach(function(element) {
|
|
177
|
+
traverse(obj).forEach(function (element) {
|
|
146
178
|
if (element && element.nts2Ref) {
|
|
147
179
|
found = true;
|
|
148
180
|
this.stop();
|
|
@@ -152,28 +184,28 @@ function hasNts2Ref(obj) {
|
|
|
152
184
|
}
|
|
153
185
|
|
|
154
186
|
// Extract only NTS2-specific properties (with nts2Ref)
|
|
155
|
-
function extractNts2Properties(obj) {
|
|
187
|
+
function extractNts2Properties(obj, parentKey) {
|
|
156
188
|
const result = {};
|
|
157
|
-
|
|
189
|
+
|
|
158
190
|
// Copy nts2-specific metadata at current level
|
|
159
191
|
if (obj.nts2Ref) result.ntsRef = obj.nts2Ref;
|
|
160
192
|
if (obj.nts2Required) result.required = obj.nts2Required;
|
|
161
193
|
if (obj.nts2Title) result.title = obj.nts2Title;
|
|
162
194
|
if (obj.nts2Description) result.description = obj.nts2Description;
|
|
163
195
|
if (obj.nts2Enum) result.enum = obj.nts2Enum;
|
|
164
|
-
|
|
196
|
+
|
|
165
197
|
// Handle discriminator
|
|
166
198
|
if (obj.discriminator) {
|
|
167
199
|
result.discriminator = obj.discriminator;
|
|
168
200
|
}
|
|
169
|
-
|
|
201
|
+
|
|
170
202
|
// Recursively process properties
|
|
171
203
|
if (obj.properties) {
|
|
172
204
|
result.properties = {};
|
|
173
|
-
Object.keys(obj.properties).forEach(key => {
|
|
205
|
+
Object.keys(obj.properties).forEach((key) => {
|
|
174
206
|
const prop = obj.properties[key];
|
|
175
207
|
if (hasNts2Ref(prop)) {
|
|
176
|
-
result.properties[key] = extractNts2Properties(prop);
|
|
208
|
+
result.properties[key] = extractNts2Properties(prop, key);
|
|
177
209
|
}
|
|
178
210
|
});
|
|
179
211
|
// Only keep properties if we found some with nts2Ref
|
|
@@ -181,18 +213,18 @@ function extractNts2Properties(obj) {
|
|
|
181
213
|
delete result.properties;
|
|
182
214
|
}
|
|
183
215
|
}
|
|
184
|
-
|
|
216
|
+
|
|
185
217
|
// Handle arrays
|
|
186
218
|
if (obj.items) {
|
|
187
219
|
if (hasNts2Ref(obj.items)) {
|
|
188
220
|
result.items = extractNts2Properties(obj.items);
|
|
189
221
|
}
|
|
190
222
|
}
|
|
191
|
-
|
|
223
|
+
|
|
192
224
|
// Handle oneOf
|
|
193
225
|
if (obj.oneOf) {
|
|
194
|
-
|
|
195
|
-
obj.oneOf.
|
|
226
|
+
// Always preserve the full array structure and order
|
|
227
|
+
const processedOneOf = obj.oneOf.map((schema, index) => {
|
|
196
228
|
if (hasNts2Ref(schema)) {
|
|
197
229
|
const extracted = extractNts2Properties(schema);
|
|
198
230
|
// For oneOf schemas, we need to preserve discriminator enum values
|
|
@@ -201,31 +233,86 @@ function extractNts2Properties(obj) {
|
|
|
201
233
|
if (schema.properties[propName]) {
|
|
202
234
|
if (!extracted.properties) extracted.properties = {};
|
|
203
235
|
extracted.properties[propName] = {
|
|
204
|
-
enum:
|
|
236
|
+
enum:
|
|
237
|
+
schema.properties[propName].nts2Enum ||
|
|
238
|
+
schema.properties[propName].enum,
|
|
205
239
|
};
|
|
206
240
|
}
|
|
207
241
|
}
|
|
208
|
-
|
|
242
|
+
// Also preserve base enum values for all other properties in the extension
|
|
243
|
+
if (schema.properties) {
|
|
244
|
+
if (!extracted.properties) extracted.properties = {};
|
|
245
|
+
Object.keys(schema.properties).forEach((propName) => {
|
|
246
|
+
const prop = schema.properties[propName];
|
|
247
|
+
if (prop.enum) {
|
|
248
|
+
if (!extracted.properties[propName]) {
|
|
249
|
+
extracted.properties[propName] = {
|
|
250
|
+
enum: prop.enum,
|
|
251
|
+
};
|
|
252
|
+
} else if (!extracted.properties[propName].enum) {
|
|
253
|
+
// If the property exists but doesn't have enum, add it
|
|
254
|
+
extracted.properties[propName].enum = prop.enum;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return extracted;
|
|
260
|
+
} else {
|
|
261
|
+
// If this branch doesn't have nts2Ref, preserve the base discriminator enum
|
|
262
|
+
if (obj.discriminator && schema.properties) {
|
|
263
|
+
const propName = obj.discriminator.propertyName;
|
|
264
|
+
if (schema.properties[propName]) {
|
|
265
|
+
return {
|
|
266
|
+
properties: {
|
|
267
|
+
[propName]: {
|
|
268
|
+
enum: schema.properties[propName].enum,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// If no discriminator, return empty object to preserve structure
|
|
275
|
+
return {};
|
|
209
276
|
}
|
|
210
277
|
});
|
|
211
|
-
|
|
212
|
-
|
|
278
|
+
result.oneOf = processedOneOf;
|
|
279
|
+
|
|
280
|
+
// --- NEW: If this is an extension property inside a oneOf, promote it to the parent overlay ---
|
|
281
|
+
// If parentKey is set, and this object has extension metadata, return a stub for the parent
|
|
282
|
+
if (
|
|
283
|
+
parentKey &&
|
|
284
|
+
(result.ntsRef || result.required || result.discriminator)
|
|
285
|
+
) {
|
|
286
|
+
// This is an extension property inside a oneOf branch
|
|
287
|
+
// Return a stub for the parent property with metadata and oneOf structure
|
|
288
|
+
const parentStub = {};
|
|
289
|
+
Object.assign(parentStub, result);
|
|
290
|
+
return parentStub;
|
|
213
291
|
}
|
|
214
292
|
}
|
|
215
|
-
|
|
293
|
+
|
|
216
294
|
// Copy other JSON Schema keywords that might be present
|
|
217
|
-
[
|
|
295
|
+
[
|
|
296
|
+
"type",
|
|
297
|
+
"format",
|
|
298
|
+
"minimum",
|
|
299
|
+
"maximum",
|
|
300
|
+
"minLength",
|
|
301
|
+
"maxLength",
|
|
302
|
+
"minItems",
|
|
303
|
+
"maxItems",
|
|
304
|
+
].forEach((keyword) => {
|
|
218
305
|
if (obj[keyword] !== undefined) {
|
|
219
306
|
result[keyword] = obj[keyword];
|
|
220
307
|
}
|
|
221
308
|
});
|
|
222
|
-
|
|
309
|
+
|
|
223
310
|
return result;
|
|
224
311
|
}
|
|
225
312
|
|
|
226
313
|
// Add required array at parent levels if needed
|
|
227
314
|
function addRequiredArrays(overlay, schema) {
|
|
228
|
-
traverse(overlay).forEach(function(node) {
|
|
315
|
+
traverse(overlay).forEach(function (node) {
|
|
229
316
|
if (node && node.properties) {
|
|
230
317
|
const schemaPath = "/" + this.path.join("/");
|
|
231
318
|
try {
|
|
@@ -233,7 +320,9 @@ function addRequiredArrays(overlay, schema) {
|
|
|
233
320
|
if (schemaNode && schemaNode.nts2Required) {
|
|
234
321
|
// Filter to only include properties that exist in this overlay
|
|
235
322
|
const overlayProps = Object.keys(node.properties);
|
|
236
|
-
const requiredProps = schemaNode.nts2Required.filter(prop =>
|
|
323
|
+
const requiredProps = schemaNode.nts2Required.filter((prop) =>
|
|
324
|
+
overlayProps.includes(prop)
|
|
325
|
+
);
|
|
237
326
|
if (requiredProps.length > 0) {
|
|
238
327
|
node.required = requiredProps;
|
|
239
328
|
}
|
|
@@ -249,23 +338,25 @@ function addRequiredArrays(overlay, schema) {
|
|
|
249
338
|
// Generate extension overlays
|
|
250
339
|
Object.entries(extensionMappings).forEach(([code, config]) => {
|
|
251
340
|
console.log(`\nGenerating extension overlay: ${code} (${config.name})`);
|
|
252
|
-
|
|
341
|
+
|
|
253
342
|
// Extract the specific paths
|
|
254
343
|
let overlay = extractPathsWithNts2Ref(combinedSchema, config.paths);
|
|
255
|
-
|
|
344
|
+
|
|
256
345
|
// Add required arrays where needed
|
|
257
346
|
overlay = addRequiredArrays(overlay, combinedSchema);
|
|
258
|
-
|
|
347
|
+
|
|
259
348
|
// Add schema metadata
|
|
260
349
|
overlay.$schema = "http://json-schema.org/draft-07/schema#";
|
|
261
350
|
overlay.$id = `https://trust.propdata.org.uk/schemas/v3/overlays/extensions/${code}.json`;
|
|
262
351
|
overlay.$comment = config.description;
|
|
263
|
-
|
|
352
|
+
|
|
264
353
|
// Write the overlay file
|
|
265
|
-
const fileName = path.join(
|
|
354
|
+
const fileName = path.join(
|
|
355
|
+
__dirname,
|
|
356
|
+
`../schemas/v3/overlays/extensions/${code}.json`
|
|
357
|
+
);
|
|
266
358
|
fs.writeFileSync(fileName, JSON.stringify(overlay, null, 2));
|
|
267
359
|
console.log(`Extension overlay ${code} written to ${fileName}`);
|
|
268
360
|
});
|
|
269
361
|
|
|
270
|
-
|
|
271
|
-
console.log("\nExtension overlay extraction complete!");
|
|
362
|
+
console.log("\nExtension overlay extraction complete!");
|