@pdtf/schemas 3.4.1-1 → 3.4.1-2
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
CHANGED
|
@@ -22,23 +22,23 @@ const extensionOverlays = {
|
|
|
22
22
|
jk: require("./src/schemas/v3/overlays/extensions/jk.json"),
|
|
23
23
|
sb: require("./src/schemas/v3/overlays/extensions/sb.json"),
|
|
24
24
|
hs: require("./src/schemas/v3/overlays/extensions/hs.json"),
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
// Property Features Extensions
|
|
27
27
|
oa: require("./src/schemas/v3/overlays/extensions/oa.json"),
|
|
28
28
|
la: require("./src/schemas/v3/overlays/extensions/la.json"),
|
|
29
29
|
sf: require("./src/schemas/v3/overlays/extensions/sf.json"),
|
|
30
30
|
mc: require("./src/schemas/v3/overlays/extensions/mc.json"),
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
// Ownership & Financial Extensions
|
|
33
33
|
er: require("./src/schemas/v3/overlays/extensions/er.json"),
|
|
34
34
|
ma: require("./src/schemas/v3/overlays/extensions/ma.json"),
|
|
35
35
|
tf: require("./src/schemas/v3/overlays/extensions/tf.json"),
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
// Utilities & Services Extensions
|
|
38
38
|
sl: require("./src/schemas/v3/overlays/extensions/sl.json"),
|
|
39
39
|
hi: require("./src/schemas/v3/overlays/extensions/hi.json"),
|
|
40
40
|
fd: require("./src/schemas/v3/overlays/extensions/fd.json"),
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
// Transaction Extensions
|
|
43
43
|
oc: require("./src/schemas/v3/overlays/extensions/oc.json"),
|
|
44
44
|
};
|
|
@@ -165,10 +165,109 @@ const getTransactionSchema = (
|
|
|
165
165
|
},
|
|
166
166
|
arrayMerge: combineMerge,
|
|
167
167
|
});
|
|
168
|
+
|
|
169
|
+
// Post-merge: Ensure overlay metadata is preserved
|
|
170
|
+
mergedSchema = preserveOverlayMetadata(mergedSchema, overlaySchema);
|
|
168
171
|
});
|
|
169
172
|
return mergedSchema;
|
|
170
173
|
};
|
|
171
174
|
|
|
175
|
+
// Function to ensure overlay metadata properties are preserved after merging
|
|
176
|
+
const preserveOverlayMetadata = (mergedSchema, overlaySchema) => {
|
|
177
|
+
const metadataKeys = [
|
|
178
|
+
"ntsRef",
|
|
179
|
+
"nts2Ref",
|
|
180
|
+
"baspi4Ref",
|
|
181
|
+
"baspi5Ref",
|
|
182
|
+
"ta6Ref",
|
|
183
|
+
"ta7Ref",
|
|
184
|
+
"ta10Ref",
|
|
185
|
+
"lpe1Ref",
|
|
186
|
+
"fme1Ref",
|
|
187
|
+
"con29RRef",
|
|
188
|
+
"con29DWRef",
|
|
189
|
+
"llc1Ref",
|
|
190
|
+
"rdsRef",
|
|
191
|
+
"oc1Ref",
|
|
192
|
+
"piqRef",
|
|
193
|
+
"sr24Ref",
|
|
194
|
+
"discriminator",
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
// Helper to get a unique key signature for a schema branch (for matching oneOf branches)
|
|
198
|
+
const getBranchSignature = (obj) => {
|
|
199
|
+
if (!obj || typeof obj !== "object") return "";
|
|
200
|
+
if (obj.properties) {
|
|
201
|
+
return Object.keys(obj.properties).sort().join(",");
|
|
202
|
+
}
|
|
203
|
+
return Object.keys(obj).sort().join(",");
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const traverseAndPreserve = (merged, overlay, path = "") => {
|
|
207
|
+
if (!overlay || typeof overlay !== "object") return;
|
|
208
|
+
|
|
209
|
+
// If both are arrays, try to match by signature, else fallback to index
|
|
210
|
+
if (Array.isArray(overlay) && Array.isArray(merged)) {
|
|
211
|
+
// Build a map of overlay signatures
|
|
212
|
+
const overlaySignatures = overlay.map(getBranchSignature);
|
|
213
|
+
const mergedSignatures = merged.map(getBranchSignature);
|
|
214
|
+
for (let i = 0; i < overlay.length; i++) {
|
|
215
|
+
const oSig = overlaySignatures[i];
|
|
216
|
+
// Try to find a matching branch in merged by signature
|
|
217
|
+
let mIdx = mergedSignatures.indexOf(oSig);
|
|
218
|
+
if (mIdx === -1) mIdx = i; // fallback to index if not found
|
|
219
|
+
if (merged[mIdx] !== undefined) {
|
|
220
|
+
traverseAndPreserve(merged[mIdx], overlay[i], path + `[${mIdx}]`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
Object.keys(overlay).forEach((key) => {
|
|
227
|
+
const overlayValue = overlay[key];
|
|
228
|
+
const mergedValue = merged[key];
|
|
229
|
+
|
|
230
|
+
if (metadataKeys.includes(key) && overlayValue !== undefined) {
|
|
231
|
+
// For metadata keys, preserve the overlay value
|
|
232
|
+
merged[key] = overlayValue;
|
|
233
|
+
} else if (
|
|
234
|
+
key === "required" &&
|
|
235
|
+
Array.isArray(overlayValue) &&
|
|
236
|
+
Array.isArray(mergedValue)
|
|
237
|
+
) {
|
|
238
|
+
// For required arrays, merge them uniquely (don't overwrite)
|
|
239
|
+
merged[key] = [...new Set([...mergedValue, ...overlayValue])];
|
|
240
|
+
} else if (
|
|
241
|
+
key === "required" &&
|
|
242
|
+
Array.isArray(overlayValue) &&
|
|
243
|
+
!Array.isArray(mergedValue)
|
|
244
|
+
) {
|
|
245
|
+
// If merged doesn't have required array but overlay does, use overlay's
|
|
246
|
+
merged[key] = overlayValue;
|
|
247
|
+
} else if (
|
|
248
|
+
overlayValue &&
|
|
249
|
+
typeof overlayValue === "object" &&
|
|
250
|
+
!Array.isArray(overlayValue) &&
|
|
251
|
+
mergedValue &&
|
|
252
|
+
typeof mergedValue === "object" &&
|
|
253
|
+
!Array.isArray(mergedValue)
|
|
254
|
+
) {
|
|
255
|
+
traverseAndPreserve(mergedValue, overlayValue, path + "/" + key);
|
|
256
|
+
} else if (
|
|
257
|
+
Array.isArray(overlayValue) &&
|
|
258
|
+
Array.isArray(mergedValue) &&
|
|
259
|
+
["oneOf", "anyOf", "allOf"].includes(key)
|
|
260
|
+
) {
|
|
261
|
+
// Recursively handle schema composition arrays (handled above)
|
|
262
|
+
traverseAndPreserve(mergedValue, overlayValue, path + `/${key}`);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
traverseAndPreserve(mergedSchema, overlaySchema);
|
|
268
|
+
return mergedSchema;
|
|
269
|
+
};
|
|
270
|
+
|
|
172
271
|
// Add helper function to generate cache key
|
|
173
272
|
const generateOverlayKey = (overlays) => {
|
|
174
273
|
if (!overlays) return "";
|
package/package.json
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
"countryCode": "GBR"
|
|
19
19
|
},
|
|
20
20
|
"role": "Seller",
|
|
21
|
-
"sellersCapacity": {
|
|
21
|
+
"sellersCapacity": {
|
|
22
|
+
"capacity": "Legal Owner",
|
|
23
|
+
"sellersCapacityDetails": "Selling as the legal owner of the property",
|
|
24
|
+
"attachments": "To follow"
|
|
25
|
+
},
|
|
22
26
|
"externalIds": { "Moverly": "123434" }
|
|
23
27
|
},
|
|
24
28
|
{
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Transaction Milestones",
|
|
4
|
+
"description": "Schema for tracking transaction milestones throughout the conveyancing process",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"listed": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"completed": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"format": "date-time"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"legalForms": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"completed": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"format": "date-time"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"soldSubjectToContract": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"completed": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"format": "date-time"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"searches": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"ordered": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"format": "date-time"
|
|
40
|
+
},
|
|
41
|
+
"expected": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"format": "date-time"
|
|
44
|
+
},
|
|
45
|
+
"completed": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"format": "date-time"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"enquiries": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"completed": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"format": "date-time"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"exchangeOfContracts": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {
|
|
63
|
+
"expected": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"format": "date-time"
|
|
66
|
+
},
|
|
67
|
+
"completed": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"format": "date-time"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"completion": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"properties": {
|
|
76
|
+
"started": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"format": "date-time"
|
|
79
|
+
},
|
|
80
|
+
"expected": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"format": "date-time"
|
|
83
|
+
},
|
|
84
|
+
"completed": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"format": "date-time"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -4,35 +4,60 @@ const {
|
|
|
4
4
|
getValidator,
|
|
5
5
|
} = require("../../../index.js");
|
|
6
6
|
|
|
7
|
-
const schemaId =
|
|
7
|
+
const schemaId =
|
|
8
|
+
"https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
|
|
8
9
|
|
|
9
10
|
describe("Extension Overlays", () => {
|
|
10
|
-
|
|
11
11
|
describe("String key support", () => {
|
|
12
12
|
test("should load extension overlays using string keys", () => {
|
|
13
13
|
const schema = getTransactionSchema(schemaId, ["nts2023", "jk"]);
|
|
14
|
-
const jkProp =
|
|
15
|
-
|
|
14
|
+
const jkProp =
|
|
15
|
+
schema.properties?.propertyPack?.properties?.specialistIssues
|
|
16
|
+
?.properties?.japaneseKnotweed;
|
|
17
|
+
|
|
16
18
|
expect(jkProp).toBeDefined();
|
|
17
19
|
expect(jkProp.ntsRef).toBe("A5.3");
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
test("should work with multiple extension string keys", () => {
|
|
21
|
-
const schema = getTransactionSchema(schemaId, [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const schema = getTransactionSchema(schemaId, [
|
|
24
|
+
"nts2023",
|
|
25
|
+
"jk",
|
|
26
|
+
"tf",
|
|
27
|
+
"ma",
|
|
28
|
+
"er",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const jkProp =
|
|
32
|
+
schema.properties?.propertyPack?.properties?.specialistIssues
|
|
33
|
+
?.properties?.japaneseKnotweed;
|
|
34
|
+
const tfProp =
|
|
35
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
36
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
|
|
37
|
+
?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]
|
|
38
|
+
?.properties?.transferFees;
|
|
39
|
+
const maProp =
|
|
40
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
41
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
|
|
42
|
+
?.leaseholdInformation?.properties?.contactDetails;
|
|
43
|
+
const erProp =
|
|
44
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
45
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[0]?.properties
|
|
46
|
+
?.estateRentcharges;
|
|
47
|
+
|
|
27
48
|
expect(jkProp?.ntsRef).toBe("A5.3");
|
|
28
49
|
expect(tfProp?.ntsRef).toBe("A3.5.1.1");
|
|
29
50
|
expect(maProp?.ntsRef).toBe("A1.5.4");
|
|
51
|
+
expect(erProp?.ntsRef).toBe("A3.4");
|
|
30
52
|
});
|
|
31
53
|
|
|
32
54
|
test("should be equivalent to direct object access", () => {
|
|
33
55
|
const schemaString = getTransactionSchema(schemaId, ["nts2023", "jk"]);
|
|
34
|
-
const schemaDirect = getTransactionSchema(schemaId, [
|
|
35
|
-
|
|
56
|
+
const schemaDirect = getTransactionSchema(schemaId, [
|
|
57
|
+
"nts2023",
|
|
58
|
+
extensionOverlays.jk,
|
|
59
|
+
]);
|
|
60
|
+
|
|
36
61
|
expect(schemaString).toEqual(schemaDirect);
|
|
37
62
|
});
|
|
38
63
|
});
|
|
@@ -40,8 +65,10 @@ describe("Extension Overlays", () => {
|
|
|
40
65
|
describe("Individual extension functionality", () => {
|
|
41
66
|
test("should add Japanese Knotweed extension correctly", () => {
|
|
42
67
|
const schema = getTransactionSchema(schemaId, ["nts2023", "jk"]);
|
|
43
|
-
const jkProp =
|
|
44
|
-
|
|
68
|
+
const jkProp =
|
|
69
|
+
schema.properties?.propertyPack?.properties?.specialistIssues
|
|
70
|
+
?.properties?.japaneseKnotweed;
|
|
71
|
+
|
|
45
72
|
expect(jkProp).toBeDefined();
|
|
46
73
|
expect(jkProp.ntsRef).toBe("A5.3");
|
|
47
74
|
expect(jkProp.required).toContain("yesNo");
|
|
@@ -50,17 +77,26 @@ describe("Extension Overlays", () => {
|
|
|
50
77
|
|
|
51
78
|
test("should add Transfer Fees extension correctly", () => {
|
|
52
79
|
const schema = getTransactionSchema(schemaId, ["nts2023", "tf"]);
|
|
53
|
-
const tfProp =
|
|
54
|
-
|
|
80
|
+
const tfProp =
|
|
81
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
82
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
|
|
83
|
+
?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]
|
|
84
|
+
?.properties?.transferFees;
|
|
85
|
+
|
|
55
86
|
expect(tfProp).toBeDefined();
|
|
56
87
|
expect(tfProp.ntsRef).toBe("A3.5.1.1");
|
|
57
|
-
expect(tfProp.title).toBe(
|
|
88
|
+
expect(tfProp.title).toBe(
|
|
89
|
+
"Are there any additional fees payable on sale or letting?"
|
|
90
|
+
);
|
|
58
91
|
});
|
|
59
92
|
|
|
60
93
|
test("should add Managing Agent extension correctly", () => {
|
|
61
94
|
const schema = getTransactionSchema(schemaId, ["nts2023", "ma"]);
|
|
62
|
-
const maProp =
|
|
63
|
-
|
|
95
|
+
const maProp =
|
|
96
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
97
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
|
|
98
|
+
?.leaseholdInformation?.properties?.contactDetails;
|
|
99
|
+
|
|
64
100
|
expect(maProp).toBeDefined();
|
|
65
101
|
expect(maProp.ntsRef).toBe("A1.5.4");
|
|
66
102
|
expect(maProp.required).toContain("contacts");
|
|
@@ -68,32 +104,76 @@ describe("Extension Overlays", () => {
|
|
|
68
104
|
|
|
69
105
|
test("should add Solar Panels extension correctly", () => {
|
|
70
106
|
const schema = getTransactionSchema(schemaId, ["nts2023", "sl"]);
|
|
71
|
-
const slProp =
|
|
72
|
-
|
|
107
|
+
const slProp =
|
|
108
|
+
schema.properties?.propertyPack?.properties?.electricity?.properties
|
|
109
|
+
?.solarPanels?.oneOf?.[1]?.properties?.panelsOwnedOutright;
|
|
110
|
+
|
|
73
111
|
expect(slProp).toBeDefined();
|
|
74
112
|
expect(slProp.ntsRef).toBe("B3.7.1");
|
|
75
113
|
});
|
|
76
114
|
|
|
77
115
|
test("should add Heating Installation extension correctly", () => {
|
|
78
116
|
const schema = getTransactionSchema(schemaId, ["nts2023", "hi"]);
|
|
79
|
-
const hiProp =
|
|
80
|
-
|
|
117
|
+
const hiProp =
|
|
118
|
+
schema.properties?.propertyPack?.properties?.heating?.properties
|
|
119
|
+
?.heatingSystem?.oneOf?.[1]?.properties?.centralHeatingDetails
|
|
120
|
+
?.properties?.centralHeatingInstalled;
|
|
121
|
+
|
|
81
122
|
expect(hiProp).toBeDefined();
|
|
82
123
|
expect(hiProp.ntsRef).toBe("B3.4.3.2");
|
|
83
124
|
});
|
|
125
|
+
|
|
126
|
+
test("should add Estate Rentcharges extension correctly", () => {
|
|
127
|
+
const schema = getTransactionSchema(schemaId, ["nts2023", "er"]);
|
|
128
|
+
const erProp =
|
|
129
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
130
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[0]?.properties
|
|
131
|
+
?.estateRentcharges;
|
|
132
|
+
|
|
133
|
+
expect(erProp).toBeDefined();
|
|
134
|
+
expect(erProp.ntsRef).toBe("A3.4");
|
|
135
|
+
expect(erProp.required).toContain("yesNo");
|
|
136
|
+
expect(erProp.discriminator?.propertyName).toBe("yesNo");
|
|
137
|
+
expect(erProp.oneOf).toBeDefined();
|
|
138
|
+
expect(Array.isArray(erProp.oneOf)).toBe(true);
|
|
139
|
+
expect(erProp.oneOf.length).toBeGreaterThan(0);
|
|
140
|
+
|
|
141
|
+
// Check the conditional branch for "Yes" response
|
|
142
|
+
const yesBranch = erProp.oneOf.find(
|
|
143
|
+
(branch) =>
|
|
144
|
+
branch.required?.includes("details") &&
|
|
145
|
+
branch.required?.includes("amount")
|
|
146
|
+
);
|
|
147
|
+
expect(yesBranch).toBeDefined();
|
|
148
|
+
expect(yesBranch.properties?.details?.ntsRef).toBe("A3.4.2");
|
|
149
|
+
expect(yesBranch.properties?.amount?.ntsRef).toBe("A3.4.3");
|
|
150
|
+
});
|
|
84
151
|
});
|
|
85
152
|
|
|
86
153
|
describe("Extension overlay availability", () => {
|
|
87
154
|
test("should export all extension overlays", () => {
|
|
88
155
|
const expectedKeys = [
|
|
89
|
-
"as",
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
156
|
+
"as",
|
|
157
|
+
"dr",
|
|
158
|
+
"jk",
|
|
159
|
+
"sb",
|
|
160
|
+
"hs", // Specialist Issues
|
|
161
|
+
"oa",
|
|
162
|
+
"la",
|
|
163
|
+
"sf",
|
|
164
|
+
"mc", // Property Features
|
|
165
|
+
"er",
|
|
166
|
+
"ma",
|
|
167
|
+
"tf", // Ownership & Financial
|
|
168
|
+
"sl",
|
|
169
|
+
"hi",
|
|
170
|
+
"fd", // Utilities & Services
|
|
171
|
+
"oc", // Transaction
|
|
94
172
|
];
|
|
95
|
-
|
|
96
|
-
expect(Object.keys(extensionOverlays).sort()).toEqual(
|
|
173
|
+
|
|
174
|
+
expect(Object.keys(extensionOverlays).sort()).toEqual(
|
|
175
|
+
expectedKeys.sort()
|
|
176
|
+
);
|
|
97
177
|
});
|
|
98
178
|
|
|
99
179
|
test("should have valid JSON structure for all extensions", () => {
|
|
@@ -110,78 +190,131 @@ describe("Extension Overlays", () => {
|
|
|
110
190
|
test("should produce equivalent result to nts2 when all relevant extensions are combined", () => {
|
|
111
191
|
// Get the full NTS2 schema
|
|
112
192
|
const nts2Schema = getTransactionSchema(schemaId, ["nts2025"]);
|
|
113
|
-
|
|
193
|
+
|
|
114
194
|
// Get NTS base with all relevant extensions
|
|
115
195
|
const allExtensions = [
|
|
116
196
|
"nts2023",
|
|
117
197
|
// Specialist issues extensions
|
|
118
|
-
"as",
|
|
119
|
-
|
|
120
|
-
"
|
|
198
|
+
"as",
|
|
199
|
+
"dr",
|
|
200
|
+
"jk",
|
|
201
|
+
"sb",
|
|
202
|
+
"hs",
|
|
203
|
+
// Property features extensions
|
|
204
|
+
"oa",
|
|
205
|
+
"la",
|
|
206
|
+
"sf",
|
|
207
|
+
"mc",
|
|
121
208
|
// Ownership & financial extensions
|
|
122
|
-
"er",
|
|
209
|
+
"er",
|
|
210
|
+
"ma",
|
|
211
|
+
"tf",
|
|
123
212
|
// Utilities & services extensions
|
|
124
|
-
"sl",
|
|
213
|
+
"sl",
|
|
214
|
+
"hi",
|
|
215
|
+
"fd",
|
|
125
216
|
// Transaction extensions
|
|
126
|
-
"oc"
|
|
217
|
+
"oc",
|
|
127
218
|
];
|
|
128
|
-
|
|
129
|
-
const ntsWithAllExtensions = getTransactionSchema(
|
|
130
|
-
|
|
219
|
+
|
|
220
|
+
const ntsWithAllExtensions = getTransactionSchema(
|
|
221
|
+
schemaId,
|
|
222
|
+
allExtensions
|
|
223
|
+
);
|
|
224
|
+
|
|
131
225
|
// Compare key structural elements
|
|
132
226
|
const nts2PropPack = nts2Schema.properties?.propertyPack;
|
|
133
227
|
const extPropPack = ntsWithAllExtensions.properties?.propertyPack;
|
|
134
|
-
|
|
228
|
+
|
|
135
229
|
// Check that specialist issues section exists in both
|
|
136
230
|
expect(nts2PropPack?.properties?.specialistIssues).toBeDefined();
|
|
137
231
|
expect(extPropPack?.properties?.specialistIssues).toBeDefined();
|
|
138
|
-
|
|
232
|
+
|
|
139
233
|
// Check key specialist issues properties
|
|
140
234
|
const nts2SI = nts2PropPack.properties.specialistIssues.properties;
|
|
141
235
|
const extSI = extPropPack.properties.specialistIssues.properties;
|
|
142
|
-
|
|
236
|
+
|
|
143
237
|
expect(extSI?.japaneseKnotweed).toBeDefined();
|
|
144
238
|
expect(extSI?.dryRotEtcTreatment).toBeDefined();
|
|
145
239
|
expect(extSI?.containsAsbestos).toBeDefined();
|
|
146
240
|
expect(extSI?.subsidenceOrStructuralFault).toBeDefined();
|
|
147
241
|
expect(extSI?.ongoingHealthOrSafetyIssue).toBeDefined();
|
|
148
|
-
|
|
242
|
+
|
|
149
243
|
// Check that references match (extensions use ntsRef, NTS2 uses nts2Ref)
|
|
150
244
|
if (nts2SI?.japaneseKnotweed && extSI?.japaneseKnotweed) {
|
|
151
245
|
expect(extSI.japaneseKnotweed.ntsRef).toBe("A5.3");
|
|
152
246
|
// NTS2 might have nts2Ref instead - this is expected difference
|
|
153
247
|
}
|
|
154
|
-
|
|
248
|
+
|
|
155
249
|
// Check outside areas
|
|
156
|
-
expect(
|
|
157
|
-
|
|
250
|
+
expect(
|
|
251
|
+
extPropPack?.properties?.residentialPropertyFeatures?.properties
|
|
252
|
+
?.outsideAreas
|
|
253
|
+
).toBeDefined();
|
|
254
|
+
|
|
158
255
|
// Check leasehold extensions
|
|
159
|
-
const leaseholdInfo =
|
|
256
|
+
const leaseholdInfo =
|
|
257
|
+
extPropPack?.properties?.ownership?.properties
|
|
258
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
|
|
259
|
+
?.leaseholdInformation;
|
|
160
260
|
expect(leaseholdInfo?.properties?.contactDetails).toBeDefined(); // Managing agent
|
|
161
|
-
expect(
|
|
162
|
-
|
|
261
|
+
expect(
|
|
262
|
+
leaseholdInfo?.properties?.serviceCharge?.oneOf?.[1]?.properties
|
|
263
|
+
?.transferFees
|
|
264
|
+
).toBeDefined(); // Transfer fees
|
|
265
|
+
|
|
163
266
|
// Check utility extensions
|
|
164
|
-
expect(
|
|
165
|
-
|
|
166
|
-
|
|
267
|
+
expect(
|
|
268
|
+
extPropPack?.properties?.electricity?.properties?.solarPanels
|
|
269
|
+
?.oneOf?.[1]?.properties?.panelsOwnedOutright
|
|
270
|
+
).toBeDefined();
|
|
271
|
+
expect(
|
|
272
|
+
extPropPack?.properties?.heating?.properties?.heatingSystem?.oneOf?.[1]
|
|
273
|
+
?.properties?.centralHeatingDetails?.properties
|
|
274
|
+
?.centralHeatingInstalled
|
|
275
|
+
).toBeDefined();
|
|
276
|
+
|
|
167
277
|
// Check construction extensions
|
|
168
|
-
expect(
|
|
169
|
-
|
|
170
|
-
|
|
278
|
+
expect(
|
|
279
|
+
extPropPack?.properties?.typeOfConstruction?.properties?.loft
|
|
280
|
+
).toBeDefined();
|
|
281
|
+
expect(
|
|
282
|
+
extPropPack?.properties?.typeOfConstruction?.properties
|
|
283
|
+
?.sprayFoamInsulation
|
|
284
|
+
).toBeDefined();
|
|
285
|
+
|
|
171
286
|
// Check completion and moving
|
|
172
|
-
expect(
|
|
287
|
+
expect(
|
|
288
|
+
extPropPack?.properties?.completionAndMoving?.properties
|
|
289
|
+
?.otherPropertyInChain
|
|
290
|
+
).toBeDefined();
|
|
173
291
|
});
|
|
174
292
|
|
|
175
293
|
test("should validate data successfully with all extensions", () => {
|
|
176
294
|
const allExtensions = [
|
|
177
|
-
"nts2023",
|
|
178
|
-
"
|
|
295
|
+
"nts2023",
|
|
296
|
+
"as",
|
|
297
|
+
"dr",
|
|
298
|
+
"jk",
|
|
299
|
+
"sb",
|
|
300
|
+
"hs",
|
|
301
|
+
"oa",
|
|
302
|
+
"la",
|
|
303
|
+
"sf",
|
|
304
|
+
"mc",
|
|
305
|
+
"er",
|
|
306
|
+
"ma",
|
|
307
|
+
"tf",
|
|
308
|
+
"sl",
|
|
309
|
+
"hi",
|
|
310
|
+
"fd",
|
|
311
|
+
"oc",
|
|
179
312
|
];
|
|
180
|
-
|
|
313
|
+
|
|
181
314
|
const validator = getValidator(schemaId, allExtensions);
|
|
182
315
|
expect(validator).toBeDefined();
|
|
183
316
|
expect(typeof validator).toBe("function");
|
|
184
|
-
|
|
317
|
+
|
|
185
318
|
// Test with minimal valid data structure
|
|
186
319
|
const testData = {
|
|
187
320
|
propertyPack: {
|
|
@@ -205,11 +338,11 @@ describe("Extension Overlays", () => {
|
|
|
205
338
|
dryRotEtcTreatment: { yesNo: "No" },
|
|
206
339
|
containsAsbestos: { yesNo: "No" },
|
|
207
340
|
subsidenceOrStructuralFault: { yesNo: "No" },
|
|
208
|
-
ongoingHealthOrSafetyIssue: { yesNo: "No" }
|
|
209
|
-
}
|
|
210
|
-
}
|
|
341
|
+
ongoingHealthOrSafetyIssue: { yesNo: "No" },
|
|
342
|
+
},
|
|
343
|
+
},
|
|
211
344
|
};
|
|
212
|
-
|
|
345
|
+
|
|
213
346
|
// This might not validate due to missing required fields, but validator should not throw
|
|
214
347
|
expect(() => validator(testData)).not.toThrow();
|
|
215
348
|
});
|
|
@@ -218,8 +351,9 @@ describe("Extension Overlays", () => {
|
|
|
218
351
|
describe("Extension merging behavior", () => {
|
|
219
352
|
test("should preserve required arrays when merging extensions", () => {
|
|
220
353
|
const schema = getTransactionSchema(schemaId, ["nts2023", "jk", "as"]);
|
|
221
|
-
const siSection =
|
|
222
|
-
|
|
354
|
+
const siSection =
|
|
355
|
+
schema.properties?.propertyPack?.properties?.specialistIssues;
|
|
356
|
+
|
|
223
357
|
expect(siSection?.required).toBeDefined();
|
|
224
358
|
expect(siSection.required).toContain("japaneseKnotweed");
|
|
225
359
|
expect(siSection.required).toContain("containsAsbestos");
|
|
@@ -227,8 +361,10 @@ describe("Extension Overlays", () => {
|
|
|
227
361
|
|
|
228
362
|
test("should handle discriminator patterns correctly", () => {
|
|
229
363
|
const schema = getTransactionSchema(schemaId, ["nts2023", "jk"]);
|
|
230
|
-
const jkProp =
|
|
231
|
-
|
|
364
|
+
const jkProp =
|
|
365
|
+
schema.properties?.propertyPack?.properties?.specialistIssues
|
|
366
|
+
?.properties?.japaneseKnotweed;
|
|
367
|
+
|
|
232
368
|
expect(jkProp?.discriminator).toBeDefined();
|
|
233
369
|
expect(jkProp.discriminator.propertyName).toBe("yesNo");
|
|
234
370
|
expect(jkProp.oneOf).toBeDefined();
|
|
@@ -237,17 +373,25 @@ describe("Extension Overlays", () => {
|
|
|
237
373
|
|
|
238
374
|
test("should merge oneOf schemas correctly", () => {
|
|
239
375
|
const schema = getTransactionSchema(schemaId, ["nts2023", "tf"]);
|
|
240
|
-
const tfProp =
|
|
241
|
-
|
|
376
|
+
const tfProp =
|
|
377
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
378
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[2]?.properties
|
|
379
|
+
?.leaseholdInformation?.properties?.serviceCharge?.oneOf?.[1]
|
|
380
|
+
?.properties?.transferFees;
|
|
381
|
+
|
|
242
382
|
expect(tfProp?.oneOf).toBeDefined();
|
|
243
383
|
expect(tfProp.oneOf.length).toBeGreaterThan(0);
|
|
244
|
-
|
|
384
|
+
|
|
245
385
|
// Check the structure - details should be in the conditional oneOf branch
|
|
246
|
-
const conditionalBranch = tfProp.oneOf.find(
|
|
247
|
-
branch
|
|
386
|
+
const conditionalBranch = tfProp.oneOf.find(
|
|
387
|
+
(branch) =>
|
|
388
|
+
branch.properties?.details || branch.required?.includes("details")
|
|
248
389
|
);
|
|
249
390
|
expect(conditionalBranch).toBeDefined();
|
|
250
|
-
expect(
|
|
391
|
+
expect(
|
|
392
|
+
conditionalBranch.properties?.details ||
|
|
393
|
+
conditionalBranch.required?.includes("details")
|
|
394
|
+
).toBeTruthy();
|
|
251
395
|
});
|
|
252
396
|
});
|
|
253
|
-
});
|
|
397
|
+
});
|
|
@@ -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();
|
|
@@ -154,23 +186,23 @@ function hasNts2Ref(obj) {
|
|
|
154
186
|
// Extract only NTS2-specific properties (with nts2Ref)
|
|
155
187
|
function extractNts2Properties(obj) {
|
|
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
208
|
result.properties[key] = extractNts2Properties(prop);
|
|
@@ -181,14 +213,14 @@ 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
226
|
const processedOneOf = [];
|
|
@@ -201,7 +233,9 @@ 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
|
}
|
|
@@ -212,20 +246,29 @@ function extractNts2Properties(obj) {
|
|
|
212
246
|
result.oneOf = processedOneOf;
|
|
213
247
|
}
|
|
214
248
|
}
|
|
215
|
-
|
|
249
|
+
|
|
216
250
|
// Copy other JSON Schema keywords that might be present
|
|
217
|
-
[
|
|
251
|
+
[
|
|
252
|
+
"type",
|
|
253
|
+
"format",
|
|
254
|
+
"minimum",
|
|
255
|
+
"maximum",
|
|
256
|
+
"minLength",
|
|
257
|
+
"maxLength",
|
|
258
|
+
"minItems",
|
|
259
|
+
"maxItems",
|
|
260
|
+
].forEach((keyword) => {
|
|
218
261
|
if (obj[keyword] !== undefined) {
|
|
219
262
|
result[keyword] = obj[keyword];
|
|
220
263
|
}
|
|
221
264
|
});
|
|
222
|
-
|
|
265
|
+
|
|
223
266
|
return result;
|
|
224
267
|
}
|
|
225
268
|
|
|
226
269
|
// Add required array at parent levels if needed
|
|
227
270
|
function addRequiredArrays(overlay, schema) {
|
|
228
|
-
traverse(overlay).forEach(function(node) {
|
|
271
|
+
traverse(overlay).forEach(function (node) {
|
|
229
272
|
if (node && node.properties) {
|
|
230
273
|
const schemaPath = "/" + this.path.join("/");
|
|
231
274
|
try {
|
|
@@ -233,7 +276,9 @@ function addRequiredArrays(overlay, schema) {
|
|
|
233
276
|
if (schemaNode && schemaNode.nts2Required) {
|
|
234
277
|
// Filter to only include properties that exist in this overlay
|
|
235
278
|
const overlayProps = Object.keys(node.properties);
|
|
236
|
-
const requiredProps = schemaNode.nts2Required.filter(prop =>
|
|
279
|
+
const requiredProps = schemaNode.nts2Required.filter((prop) =>
|
|
280
|
+
overlayProps.includes(prop)
|
|
281
|
+
);
|
|
237
282
|
if (requiredProps.length > 0) {
|
|
238
283
|
node.required = requiredProps;
|
|
239
284
|
}
|
|
@@ -249,23 +294,25 @@ function addRequiredArrays(overlay, schema) {
|
|
|
249
294
|
// Generate extension overlays
|
|
250
295
|
Object.entries(extensionMappings).forEach(([code, config]) => {
|
|
251
296
|
console.log(`\nGenerating extension overlay: ${code} (${config.name})`);
|
|
252
|
-
|
|
297
|
+
|
|
253
298
|
// Extract the specific paths
|
|
254
299
|
let overlay = extractPathsWithNts2Ref(combinedSchema, config.paths);
|
|
255
|
-
|
|
300
|
+
|
|
256
301
|
// Add required arrays where needed
|
|
257
302
|
overlay = addRequiredArrays(overlay, combinedSchema);
|
|
258
|
-
|
|
303
|
+
|
|
259
304
|
// Add schema metadata
|
|
260
305
|
overlay.$schema = "http://json-schema.org/draft-07/schema#";
|
|
261
306
|
overlay.$id = `https://trust.propdata.org.uk/schemas/v3/overlays/extensions/${code}.json`;
|
|
262
307
|
overlay.$comment = config.description;
|
|
263
|
-
|
|
308
|
+
|
|
264
309
|
// Write the overlay file
|
|
265
|
-
const fileName = path.join(
|
|
310
|
+
const fileName = path.join(
|
|
311
|
+
__dirname,
|
|
312
|
+
`../schemas/v3/overlays/extensions/${code}.json`
|
|
313
|
+
);
|
|
266
314
|
fs.writeFileSync(fileName, JSON.stringify(overlay, null, 2));
|
|
267
315
|
console.log(`Extension overlay ${code} written to ${fileName}`);
|
|
268
316
|
});
|
|
269
317
|
|
|
270
|
-
|
|
271
|
-
console.log("\nExtension overlay extraction complete!");
|
|
318
|
+
console.log("\nExtension overlay extraction complete!");
|