@pdtf/schemas 3.4.0-9 → 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.
Files changed (29) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/README.md +326 -6
  3. package/index.js +131 -0
  4. package/package.json +1 -1
  5. package/src/examples/v3/exampleTransaction.json +5 -1
  6. package/src/schemas/v3/combined.json +8 -21
  7. package/src/schemas/v3/overlays/README.md +299 -0
  8. package/src/schemas/v3/overlays/extensions/as.json +57 -0
  9. package/src/schemas/v3/overlays/extensions/dr.json +60 -0
  10. package/src/schemas/v3/overlays/extensions/er.json +71 -0
  11. package/src/schemas/v3/overlays/extensions/fd.json +63 -0
  12. package/src/schemas/v3/overlays/extensions/hi.json +44 -0
  13. package/src/schemas/v3/overlays/extensions/hs.json +56 -0
  14. package/src/schemas/v3/overlays/extensions/jk.json +55 -0
  15. package/src/schemas/v3/overlays/extensions/la.json +66 -0
  16. package/src/schemas/v3/overlays/extensions/ma.json +87 -0
  17. package/src/schemas/v3/overlays/extensions/mc.json +36 -0
  18. package/src/schemas/v3/overlays/extensions/oa.json +22 -0
  19. package/src/schemas/v3/overlays/extensions/oc.json +35 -0
  20. package/src/schemas/v3/overlays/extensions/sb.json +56 -0
  21. package/src/schemas/v3/overlays/extensions/sf.json +57 -0
  22. package/src/schemas/v3/overlays/extensions/sl.json +46 -0
  23. package/src/schemas/v3/overlays/extensions/tf.json +91 -0
  24. package/src/schemas/v3/overlays/nts2.json +6 -18
  25. package/src/schemas/v3/overlays/ntsl2.json +2 -8
  26. package/src/schemas/v3/overlays/ta6.json +1 -0
  27. package/src/schemas/v3/property-transaction-milestones.json +91 -0
  28. package/src/tests/v3/extensionOverlays.test.js +397 -0
  29. package/src/utils/extractExtensionOverlays.js +318 -0
@@ -0,0 +1,318 @@
1
+ const traverse = require("traverse");
2
+ const jp = require("jsonpointer");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const combinedSchema = require("../schemas/v3/combined.json");
7
+
8
+ // Define the extension overlay mappings
9
+ const extensionMappings = {
10
+ // Outside areas
11
+ oa: {
12
+ name: "outsideAreas",
13
+ description: "Outside areas extension for NTS",
14
+ paths: [
15
+ "/properties/propertyPack/properties/residentialPropertyFeatures/properties/outsideAreas",
16
+ ],
17
+ },
18
+
19
+ // Estate rentcharges for freehold
20
+ er: {
21
+ name: "estateRentcharges",
22
+ description: "Estate rentcharges for freehold properties",
23
+ paths: [
24
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/estateRentcharges",
25
+ ],
26
+ },
27
+
28
+ // Managing agent contact details for leasehold
29
+ ma: {
30
+ name: "managingAgent",
31
+ description: "Managing agent contact details for leasehold",
32
+ paths: [
33
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/contactDetails",
34
+ ],
35
+ },
36
+
37
+ // Transfer fees for leasehold
38
+ tf: {
39
+ name: "transferFees",
40
+ description: "Transfer fees for leasehold",
41
+ paths: [
42
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/serviceCharge/oneOf/1/properties/transferFees",
43
+ ],
44
+ },
45
+
46
+ // Main construction type if standard
47
+ mc: {
48
+ name: "mainConstruction",
49
+ description: "Main construction type if standard form",
50
+ paths: [
51
+ "/properties/propertyPack/properties/typeOfConstruction/properties/isStandardForm/oneOf/0/properties/constructionType",
52
+ ],
53
+ },
54
+
55
+ // Loft access and details
56
+ la: {
57
+ name: "loftAccess",
58
+ description: "Loft access and details",
59
+ paths: [
60
+ "/properties/propertyPack/properties/typeOfConstruction/properties/loft",
61
+ ],
62
+ },
63
+
64
+ // Spray foam insulation
65
+ sf: {
66
+ name: "sprayFoam",
67
+ description: "Spray foam insulation",
68
+ paths: [
69
+ "/properties/propertyPack/properties/typeOfConstruction/properties/sprayFoamInsulation",
70
+ ],
71
+ },
72
+
73
+ // Specialist issues - dry rot
74
+ dr: {
75
+ name: "dryRot",
76
+ description: "Dry rot treatment specialist issue",
77
+ paths: [
78
+ "/properties/propertyPack/properties/specialistIssues/properties/dryRotEtcTreatment",
79
+ ],
80
+ },
81
+
82
+ // Specialist issues - asbestos
83
+ as: {
84
+ name: "asbestos",
85
+ description: "Asbestos specialist issue",
86
+ paths: [
87
+ "/properties/propertyPack/properties/specialistIssues/properties/containsAsbestos",
88
+ ],
89
+ },
90
+
91
+ // Specialist issues - Japanese knotweed
92
+ jk: {
93
+ name: "japaneseKnotweed",
94
+ description: "Japanese knotweed specialist issue",
95
+ paths: [
96
+ "/properties/propertyPack/properties/specialistIssues/properties/japaneseKnotweed",
97
+ ],
98
+ },
99
+
100
+ // Specialist issues - subsidence
101
+ sb: {
102
+ name: "subsidence",
103
+ description: "Subsidence or structural fault specialist issue",
104
+ paths: [
105
+ "/properties/propertyPack/properties/specialistIssues/properties/subsidenceOrStructuralFault",
106
+ ],
107
+ },
108
+
109
+ // Specialist issues - health and safety
110
+ hs: {
111
+ name: "healthSafety",
112
+ description: "Health and safety specialist issue",
113
+ paths: [
114
+ "/properties/propertyPack/properties/specialistIssues/properties/ongoingHealthOrSafetyIssue",
115
+ ],
116
+ },
117
+
118
+ // Solar panels leased
119
+ sl: {
120
+ name: "solarPanelsLeased",
121
+ description: "Solar panels ownership details",
122
+ paths: [
123
+ "/properties/propertyPack/properties/electricity/properties/solarPanels/oneOf/1/properties/panelsOwnedOutright",
124
+ ],
125
+ },
126
+
127
+ // Heating installation date
128
+ hi: {
129
+ name: "heatingInstalled",
130
+ description: "Central heating installation date",
131
+ paths: [
132
+ "/properties/propertyPack/properties/heating/properties/heatingSystem/oneOf/1/properties/centralHeatingDetails/properties/centralHeatingInstalled",
133
+ ],
134
+ },
135
+
136
+ // Flood defences
137
+ fd: {
138
+ name: "floodDefences",
139
+ description: "Flood defence information",
140
+ paths: [
141
+ "/properties/propertyPack/properties/environmentalIssues/properties/flooding/properties/floodDefences",
142
+ ],
143
+ },
144
+
145
+ // Other property in chain
146
+ oc: {
147
+ name: "otherPropertyChain",
148
+ description: "Other property in chain dependency",
149
+ paths: [
150
+ "/properties/propertyPack/properties/completionAndMoving/properties/otherPropertyInChain",
151
+ ],
152
+ },
153
+ };
154
+
155
+ // Function to extract properties at specific paths with nts2Ref
156
+ function extractPathsWithNts2Ref(schema, paths) {
157
+ const result = {};
158
+
159
+ paths.forEach((path) => {
160
+ try {
161
+ const value = jp.get(schema, path);
162
+ if (value && hasNts2Ref(value)) {
163
+ // Set the value at the same path in result
164
+ jp.set(result, path, extractNts2Properties(value));
165
+ }
166
+ } catch (err) {
167
+ console.warn(`Path not found: ${path}`);
168
+ }
169
+ });
170
+
171
+ return result;
172
+ }
173
+
174
+ // Check if an object or its descendants have nts2Ref
175
+ function hasNts2Ref(obj) {
176
+ let found = false;
177
+ traverse(obj).forEach(function (element) {
178
+ if (element && element.nts2Ref) {
179
+ found = true;
180
+ this.stop();
181
+ }
182
+ });
183
+ return found;
184
+ }
185
+
186
+ // Extract only NTS2-specific properties (with nts2Ref)
187
+ function extractNts2Properties(obj) {
188
+ const result = {};
189
+
190
+ // Copy nts2-specific metadata at current level
191
+ if (obj.nts2Ref) result.ntsRef = obj.nts2Ref;
192
+ if (obj.nts2Required) result.required = obj.nts2Required;
193
+ if (obj.nts2Title) result.title = obj.nts2Title;
194
+ if (obj.nts2Description) result.description = obj.nts2Description;
195
+ if (obj.nts2Enum) result.enum = obj.nts2Enum;
196
+
197
+ // Handle discriminator
198
+ if (obj.discriminator) {
199
+ result.discriminator = obj.discriminator;
200
+ }
201
+
202
+ // Recursively process properties
203
+ if (obj.properties) {
204
+ result.properties = {};
205
+ Object.keys(obj.properties).forEach((key) => {
206
+ const prop = obj.properties[key];
207
+ if (hasNts2Ref(prop)) {
208
+ result.properties[key] = extractNts2Properties(prop);
209
+ }
210
+ });
211
+ // Only keep properties if we found some with nts2Ref
212
+ if (Object.keys(result.properties).length === 0) {
213
+ delete result.properties;
214
+ }
215
+ }
216
+
217
+ // Handle arrays
218
+ if (obj.items) {
219
+ if (hasNts2Ref(obj.items)) {
220
+ result.items = extractNts2Properties(obj.items);
221
+ }
222
+ }
223
+
224
+ // Handle oneOf
225
+ if (obj.oneOf) {
226
+ const processedOneOf = [];
227
+ obj.oneOf.forEach((schema, index) => {
228
+ if (hasNts2Ref(schema)) {
229
+ const extracted = extractNts2Properties(schema);
230
+ // For oneOf schemas, we need to preserve discriminator enum values
231
+ if (obj.discriminator && schema.properties) {
232
+ const propName = obj.discriminator.propertyName;
233
+ if (schema.properties[propName]) {
234
+ if (!extracted.properties) extracted.properties = {};
235
+ extracted.properties[propName] = {
236
+ enum:
237
+ schema.properties[propName].nts2Enum ||
238
+ schema.properties[propName].enum,
239
+ };
240
+ }
241
+ }
242
+ processedOneOf.push(extracted);
243
+ }
244
+ });
245
+ if (processedOneOf.length > 0) {
246
+ result.oneOf = processedOneOf;
247
+ }
248
+ }
249
+
250
+ // Copy other JSON Schema keywords that might be present
251
+ [
252
+ "type",
253
+ "format",
254
+ "minimum",
255
+ "maximum",
256
+ "minLength",
257
+ "maxLength",
258
+ "minItems",
259
+ "maxItems",
260
+ ].forEach((keyword) => {
261
+ if (obj[keyword] !== undefined) {
262
+ result[keyword] = obj[keyword];
263
+ }
264
+ });
265
+
266
+ return result;
267
+ }
268
+
269
+ // Add required array at parent levels if needed
270
+ function addRequiredArrays(overlay, schema) {
271
+ traverse(overlay).forEach(function (node) {
272
+ if (node && node.properties) {
273
+ const schemaPath = "/" + this.path.join("/");
274
+ try {
275
+ const schemaNode = jp.get(schema, schemaPath);
276
+ if (schemaNode && schemaNode.nts2Required) {
277
+ // Filter to only include properties that exist in this overlay
278
+ const overlayProps = Object.keys(node.properties);
279
+ const requiredProps = schemaNode.nts2Required.filter((prop) =>
280
+ overlayProps.includes(prop)
281
+ );
282
+ if (requiredProps.length > 0) {
283
+ node.required = requiredProps;
284
+ }
285
+ }
286
+ } catch (err) {
287
+ // Path doesn't exist in schema, skip
288
+ }
289
+ }
290
+ });
291
+ return overlay;
292
+ }
293
+
294
+ // Generate extension overlays
295
+ Object.entries(extensionMappings).forEach(([code, config]) => {
296
+ console.log(`\nGenerating extension overlay: ${code} (${config.name})`);
297
+
298
+ // Extract the specific paths
299
+ let overlay = extractPathsWithNts2Ref(combinedSchema, config.paths);
300
+
301
+ // Add required arrays where needed
302
+ overlay = addRequiredArrays(overlay, combinedSchema);
303
+
304
+ // Add schema metadata
305
+ overlay.$schema = "http://json-schema.org/draft-07/schema#";
306
+ overlay.$id = `https://trust.propdata.org.uk/schemas/v3/overlays/extensions/${code}.json`;
307
+ overlay.$comment = config.description;
308
+
309
+ // Write the overlay file
310
+ const fileName = path.join(
311
+ __dirname,
312
+ `../schemas/v3/overlays/extensions/${code}.json`
313
+ );
314
+ fs.writeFileSync(fileName, JSON.stringify(overlay, null, 2));
315
+ console.log(`Extension overlay ${code} written to ${fileName}`);
316
+ });
317
+
318
+ console.log("\nExtension overlay extraction complete!");