@pdtf/schemas 2.0.0-3 → 2.0.0-31

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.
@@ -110,12 +110,14 @@ test("correctly gets a subschema through a dependency", () => {
110
110
  "/propertyPack/materialFacts/delayFactors/hasDelayFactors/details",
111
111
  schemaId
112
112
  );
113
- expect(subschema.title).toBe("Details");
113
+ expect(subschema.title).toBe(
114
+ "Provide details and likely timescale for delay"
115
+ );
114
116
  });
115
117
 
116
118
  test("correctly gets another subschema through a dependency", () => {
117
119
  const subschema = getSubschema(
118
- "/propertyPack/materialFacts/ownership/ownershipsToBeTransferred/0/leaseholdInformation/general/leaseTerm/lengthOfLeaseInYears",
120
+ "/propertyPack/materialFacts/ownership/ownershipsToBeTransferred/0/leaseholdInformation/leaseTerm/lengthOfLeaseInYears",
119
121
  schemaId
120
122
  );
121
123
  expect(subschema.title).toBe("Length of lease (years)");
@@ -133,12 +135,12 @@ test("correctly gets yet, yet another subschema through a dependency", () => {
133
135
  "/propertyPack/materialFacts/listingAndConservation/isConservationArea/yesNo"
134
136
  );
135
137
  expect(subschema.type).toBe("string");
136
- expect(subschema.enum).toStrictEqual(["Yes", "No"]);
138
+ expect(subschema.enum).toStrictEqual(["Yes", "No", "Not known"]);
137
139
  });
138
140
 
139
141
  test("correctly gets yet, yet, yet another subschema through a dependency", () => {
140
142
  const subschema = getSubschema(
141
- "/propertyPack/materialFacts/environmentalIssues/flooding/typeOfFlooding/groundWater/yesNo"
143
+ "/propertyPack/materialFacts/environmentalIssues/flooding/typeOfFlooding/other/yesNo"
142
144
  );
143
145
  expect(subschema.type).toBe("string");
144
146
  expect(subschema.enum).toStrictEqual(["Yes", "No"]);
@@ -165,6 +167,16 @@ test("correctly gets yes another subschema but through a non-baspi oneOf structu
165
167
  expect(subschema).toEqual({ type: "string" });
166
168
  });
167
169
 
170
+ test("correctly gets a subschema with multiple overlays", () => {
171
+ const subschema = getSubschema(
172
+ "/propertyPack/materialFacts/parking/parkingArrangements",
173
+ schemaId,
174
+ ["baspiV4", "ta6ed4"]
175
+ );
176
+ expect(subschema.baspiRef).toBe("A1.6.0");
177
+ expect(subschema.ta6Ref).toBe("9.1");
178
+ });
179
+
168
180
  test("correctly gets a subschema validator which is already cached", () => {
169
181
  const validator = getSubschemaValidator(
170
182
  "/propertyPack/materialFacts/energyEfficiency/certificate"
@@ -176,6 +188,73 @@ test("correctly gets a subschema validator which is already cached", () => {
176
188
  expect(anotherValidator).not.toBeNull();
177
189
  });
178
190
 
191
+ test("correctly gets a subschema validator for a TA6 overlay", () => {
192
+ const path = "/propertyPack";
193
+ const data = jp.get(exampleTransaction, path);
194
+ const validator = getSubschemaValidator(path, exampleTransaction.$schema, [
195
+ "ta6ed4",
196
+ ]);
197
+ let isValid = validator(data);
198
+ // console.log(validator.errors);
199
+ expect(isValid).toBe(true);
200
+ });
201
+
202
+ test("correctly gets a subschema validator for a TA6 overlay which validates", () => {
203
+ const path =
204
+ "/propertyPack/additionalLegalInfo/guaranteesWarrantiesAndIndemnityInsurances/subsidenceWork";
205
+ const data = { yesNo: "Yes" };
206
+ const validator = getSubschemaValidator(path, exampleTransaction.$schema, [
207
+ "ta6ed4",
208
+ ]);
209
+ let isValid = validator(data);
210
+ expect(isValid).toBe(false);
211
+ expect(validator.errors[0].message).toBe(
212
+ "must have required property 'attachments'"
213
+ );
214
+ });
215
+
216
+ test("correctly gets a subschema validator for a TA6 overlay which validates with non-TA6 field missing", () => {
217
+ const path = "/propertyPack";
218
+ const clonedExampleTransaction = JSON.parse(
219
+ JSON.stringify(exampleTransaction)
220
+ );
221
+ const data = jp.get(exampleTransaction, path);
222
+ data.materialFacts.uprn = undefined;
223
+ const validator = getSubschemaValidator(path, exampleTransaction.$schema, [
224
+ "ta6ed4",
225
+ ]);
226
+ let isValid = validator(data);
227
+ expect(isValid).toBe(true);
228
+ });
229
+
230
+ test("correctly gets a subschema validator for a TA6 overlay which validates with non-TA6 section missing", () => {
231
+ const path = "/propertyPack";
232
+ const clonedExampleTransaction = JSON.parse(
233
+ JSON.stringify(exampleTransaction)
234
+ );
235
+ const data = jp.get(clonedExampleTransaction, path);
236
+ data.additionalLegalInfo.smartHomeSystems = undefined;
237
+ const validator = getSubschemaValidator(path, exampleTransaction.$schema, [
238
+ "ta6ed4",
239
+ ]);
240
+ let isValid = validator(data);
241
+ expect(isValid).toBe(true);
242
+ });
243
+
244
+ test("correctly gets a subschema validator for a TA6 overlay which fails to validate with BASPI/TA6 section missing", () => {
245
+ const path = "/propertyPack";
246
+ const clonedExampleTransaction = JSON.parse(
247
+ JSON.stringify(exampleTransaction)
248
+ );
249
+ const data = jp.get(clonedExampleTransaction, path);
250
+ data.materialFacts.waterAndDrainage = {};
251
+ const validator = getSubschemaValidator(path, exampleTransaction.$schema, [
252
+ "ta6ed4",
253
+ ]);
254
+ let isValid = validator(data);
255
+ expect(isValid).toBe(false);
256
+ });
257
+
179
258
  test("correctly gets a subschema validator which validates", () => {
180
259
  const path = "/propertyPack/materialFacts/notices";
181
260
  const validator = getSubschemaValidator(path);
@@ -198,7 +277,7 @@ test("correctly gets titles across schemas, arrays and non-existient title prope
198
277
  expect(
199
278
  getTitleAtPath(
200
279
  v2TransactionSchema,
201
- "/propertyPack/materialFacts/ownership/ownershipsToBeTransferred/0/leaseholdInformation/general/leaseTerm/lengthOfLeaseInYears"
280
+ "/propertyPack/materialFacts/ownership/ownershipsToBeTransferred/0/leaseholdInformation/leaseTerm/lengthOfLeaseInYears"
202
281
  )
203
282
  ).toBe("Length of lease (years)");
204
283
  expect(
@@ -0,0 +1,128 @@
1
+ const { validateVerifiedClaims } = require("../../../index.js");
2
+
3
+ const exampleVouch = require("../../examples/v1/exampleVouch.json");
4
+ const exampleDocumentedVouch = require("../../examples/v1/exampleDocumentedVouch.json");
5
+ const v2SchemaId =
6
+ "https://trust.propdata.org.uk/schemas/v2/pdtf-transaction.json";
7
+
8
+ test("returns an empty array for a valid claim", () => {
9
+ expect(validateVerifiedClaims([exampleVouch])).toEqual([]);
10
+ });
11
+
12
+ test("returns an empty array for a valid array of claims", () => {
13
+ const claimsArray = [exampleVouch, exampleDocumentedVouch];
14
+ expect(validateVerifiedClaims(claimsArray)).toEqual([]);
15
+ });
16
+
17
+ test("returns an array with an error stating if path is incorrect", () => {
18
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
19
+ const originalPath = Object.keys(clonedVouch.claims)[0];
20
+ const data = clonedVouch.claims[originalPath];
21
+ clonedVouch.claims = {
22
+ "/propertyPack/INVALID/materialFacts/councilTax": data,
23
+ };
24
+ // v2 schema, no overlay
25
+ expect(validateVerifiedClaims([clonedVouch], v2SchemaId, null)).toEqual([
26
+ "Path /propertyPack/INVALID/materialFacts/councilTax is not a valid PDTF schema path",
27
+ ]);
28
+ });
29
+
30
+ test("returns errors if BASPI requirements are not met and BASPI overlay is specified (default)", () => {
31
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
32
+ clonedVouch.claims = {
33
+ "/propertyPack/materialFacts/delayFactors": {
34
+ hasDelayFactors: { yesNo: "Yes" },
35
+ },
36
+ };
37
+ const errors = validateVerifiedClaims([clonedVouch], v2SchemaId);
38
+ expect(errors).toEqual([
39
+ {
40
+ instancePath: "/hasDelayFactors",
41
+ schemaPath: "#/properties/hasDelayFactors/oneOf/1/required",
42
+ keyword: "required",
43
+ params: { missingProperty: "details" },
44
+ message: "must have required property 'details'",
45
+ },
46
+ {
47
+ instancePath: "/hasDelayFactors",
48
+ schemaPath: "#/properties/hasDelayFactors/oneOf/1/required",
49
+ keyword: "required",
50
+ params: { missingProperty: "attachments" },
51
+ message: "must have required property 'attachments'",
52
+ },
53
+ ]);
54
+ });
55
+
56
+ test("returns no errors if BASPI requirements are not met and null overlay is specified", () => {
57
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
58
+ clonedVouch.claims = {
59
+ "/propertyPack/materialFacts/delayFactors": {
60
+ hasDelayFactors: { yesNo: "Yes" },
61
+ },
62
+ };
63
+ const errors = validateVerifiedClaims([clonedVouch], v2SchemaId, null);
64
+ expect(errors).toEqual([]);
65
+ });
66
+
67
+ test("returns errors for invalid fields even if null overlay is specified", () => {
68
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
69
+ clonedVouch.claims = {
70
+ "/propertyPack/materialFacts/delayFactors": {
71
+ hasDelayFactors: { yesNo: "Maybe" },
72
+ },
73
+ };
74
+ const errors = validateVerifiedClaims([clonedVouch], v2SchemaId, null);
75
+ expect(errors).toHaveLength(4);
76
+ });
77
+
78
+ test("returns an array of errors for verified claim with multiple paths", () => {
79
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
80
+ clonedVouch.claims = {
81
+ "/propertyPack/materialFacts/cousncilTaxBad": {
82
+ councilTaxBand: "D",
83
+ councilTaxAffectingAlterations: {
84
+ yesNo: "Yes",
85
+ details:
86
+ "Extension added in 2005 to add bedroom with ensuite shower room. Certificate of Compliance issued 17th Feb 2006 and council tax updated",
87
+ },
88
+ },
89
+ "/propertyPack/materialFacts/councilTaxBadTwo": {
90
+ councilTaxBand: "D",
91
+ councilTaxAffectingAlterations: {
92
+ yesNo: "Yes",
93
+ details:
94
+ "Extension added in 2005 to add bedroom with ensuite shower room. Certificate of Compliance issued 17th Feb 2006 and council tax updated",
95
+ },
96
+ },
97
+ };
98
+ expect(validateVerifiedClaims([clonedVouch], v2SchemaId, null)).toEqual([
99
+ "Path /propertyPack/materialFacts/cousncilTaxBad is not a valid PDTF schema path",
100
+ "Path /propertyPack/materialFacts/councilTaxBadTwo is not a valid PDTF schema path",
101
+ ]);
102
+ });
103
+
104
+ test("returns an empty array of errors for verified claim with multiple valid paths", () => {
105
+ const clonedVouch = JSON.parse(
106
+ JSON.stringify(exampleVouch),
107
+ v2SchemaId,
108
+ null
109
+ );
110
+ clonedVouch.claims = {
111
+ "/propertyPack/materialFacts/councilTax": {
112
+ councilTaxBand: "D",
113
+ councilTaxAffectingAlterations: {
114
+ yesNo: "Yes",
115
+ details:
116
+ "Extension added in 2005 to add bedroom with ensuite shower room. Certificate of Compliance issued 17th Feb 2006 and council tax updated",
117
+ },
118
+ },
119
+ "/propertyPack/materialFacts/address": {
120
+ line1: "property.line1",
121
+ line2: "property.line2",
122
+ town: "property.city",
123
+ county: "property.province",
124
+ postcode: "property.postcode",
125
+ },
126
+ };
127
+ expect(validateVerifiedClaims([clonedVouch], v2SchemaId, null)).toEqual([]);
128
+ });
@@ -43,9 +43,34 @@ const combineMerge = (target, source, options) => {
43
43
  return destination;
44
44
  };
45
45
 
46
+ const flattenSkeleton = (schema) => {
47
+ if (!schema) return undefined;
48
+ // console.log(schema.properties);
49
+ let returnStructure = {};
50
+ if (schema.properties) {
51
+ Object.keys(schema.properties).forEach((key) => {
52
+ returnStructure[key] = flattenSkeleton(schema.properties[key]);
53
+ });
54
+ }
55
+ if (schema.oneOf) {
56
+ schema.oneOf.forEach((aOneOf) => {
57
+ // console.log(aOneOf);
58
+ if (aOneOf.properties) {
59
+ Object.entries(aOneOf.properties).forEach(([key, value]) => {
60
+ // console.log(aProperty);
61
+ returnStructure[key] = flattenSkeleton(value);
62
+ });
63
+ }
64
+ });
65
+ }
66
+ if (schema.items) {
67
+ returnStructure = flattenSkeleton(schema.items);
68
+ }
69
+ return returnStructure;
70
+ };
71
+
46
72
  const extractOverlay = (sourceSchema, ref) => {
47
73
  const refName = `${ref}Ref`;
48
- const refRequired = `${ref}Required`;
49
74
  const returnSchema = {
50
75
  $schema: "http://json-schema.org/draft-07/schema#",
51
76
  $id: `https://trust.propdata.org.uk/schemas/v2/overlays/${ref}.json`,
@@ -74,6 +99,7 @@ const extractOverlay = (sourceSchema, ref) => {
74
99
  });
75
100
  }
76
101
  }
102
+ const refRequired = `${ref}Required`;
77
103
  if (element[refRequired]) {
78
104
  jp.set(returnSchema, `${path}/required`, element[refRequired]);
79
105
  }
@@ -112,17 +138,29 @@ const coreSchema = deleteProperties(combinedSchema, [
112
138
  ]);
113
139
 
114
140
  fs.writeFileSync(
115
- "../schemas/v2/core.json",
141
+ "../schemas/v2/pdtf-transaction.json",
116
142
  JSON.stringify(coreSchema, null, 2)
117
143
  );
118
- console.log("Core schema written to ../schemas/v2/core.json");
119
- console.log(overlays.baspi);
120
- const mergedSchema = merge(overlays.baspi, coreSchema, {
121
- arrayMerge: combineMerge,
122
- });
144
+ console.log("Core schema written to ../schemas/v2/pdtf-transaction.json");
145
+
146
+ const skeletonSchema = deleteProperties(coreSchema, [
147
+ "$schema",
148
+ "$id",
149
+ "title",
150
+ "description",
151
+ "required",
152
+ "enum",
153
+ "minItems",
154
+ "minLength",
155
+ "format",
156
+ "minimum",
157
+ "maximum",
158
+ ]);
159
+
160
+ const skeletonSchemaFlattened = flattenSkeleton(skeletonSchema);
123
161
 
124
162
  fs.writeFileSync(
125
- "../schemas/v2/merged.json",
126
- JSON.stringify(mergedSchema, null, 2)
163
+ "../schemas/v2/skeleton.json",
164
+ JSON.stringify(skeletonSchemaFlattened, null, 2)
127
165
  );
128
- console.log("Merged schema written to ../schemas/v2/merged.json");
166
+ console.log("Flat Skeleton schema written to ../schemas/v2/skeleton.json");
@@ -0,0 +1,44 @@
1
+ const { dereference } = require("@jdw/jst");
2
+ const traverse = require("traverse");
3
+ const jp = require("jsonpointer");
4
+ const fs = require("fs");
5
+ const merge = require("deepmerge");
6
+
7
+ const combinedSchema = require("../schemas/v2/combined.json");
8
+
9
+ const overlay = "baspiUI";
10
+ const keys = [
11
+ "isDependent",
12
+ "component",
13
+ "htmlClass",
14
+ "addButtonLabelFirst",
15
+ "addButtonLabelSubsequent",
16
+ ];
17
+
18
+ const returnSchema = {
19
+ $schema: "http://json-schema.org/draft-07/schema#",
20
+ $id: `https://trust.propdata.org.uk/schemas/v2/overlays/${overlay}.json`,
21
+ };
22
+
23
+ traverse(combinedSchema).forEach(function (element) {
24
+ let path = "/" + this.path.join("/");
25
+ if (path === "/") path = "";
26
+ if (keys.includes(this.key)) {
27
+ jp.set(returnSchema, path, element);
28
+ }
29
+ });
30
+
31
+ const fileName = `../schemas/v2/overlays/${overlay}.json`;
32
+ fs.writeFileSync(fileName, JSON.stringify(returnSchema, null, 2));
33
+ console.log(`Overlay ${overlay} written to ${overlay}`);
34
+
35
+ traverse(combinedSchema).forEach(function (element) {
36
+ if (keys.includes(this.key)) {
37
+ this.delete(true); // true = stop here
38
+ }
39
+ });
40
+
41
+ fs.writeFileSync(
42
+ "../schemas/v2/combined.json",
43
+ JSON.stringify(combinedSchema, null, 2)
44
+ );
@@ -0,0 +1,275 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema#",
3
+ "$id": "http://www.landregistry.gov.uk/LLC/LLCSearchResponseV2_0#",
4
+ "type": "object",
5
+ "properties": {
6
+ "LocalLandCharge": {
7
+ "type": "array",
8
+ "items": {
9
+ "type": "object",
10
+ "required": [
11
+ "HMLRReference",
12
+ "RegistrationDate",
13
+ "Category",
14
+ "Location",
15
+ "LocationDominantBuilding"
16
+ ],
17
+ "properties": {
18
+ "HMLRReference": {
19
+ "type": "string"
20
+ },
21
+ "OriginatingAuthority": {
22
+ "type": "string"
23
+ },
24
+ "AuthorityReference": {
25
+ "type": "string"
26
+ },
27
+ "CreationDate": {
28
+ "type": "object",
29
+ "properties": {
30
+ "Value": {
31
+ "type": "string",
32
+ "format": "date"
33
+ }
34
+ }
35
+ },
36
+ "RegistrationDate": {
37
+ "type": "object",
38
+ "properties": {
39
+ "Value": {
40
+ "type": "string",
41
+ "format": "date"
42
+ }
43
+ }
44
+ },
45
+ "Category": {
46
+ "type": "string"
47
+ },
48
+ "ChargeSubCategory": {
49
+ "type": "string"
50
+ },
51
+ "Law": {
52
+ "type": "string"
53
+ },
54
+ "LegalDocument": {
55
+ "type": "string"
56
+ },
57
+ "Location": {
58
+ "type": "object",
59
+ "required": ["AddressLine"],
60
+ "properties": {
61
+ "PostcodeZone": {
62
+ "type": "object",
63
+ "required": ["Postcode"],
64
+ "properties": {
65
+ "Postcode": {
66
+ "type": "string"
67
+ }
68
+ }
69
+ },
70
+ "AddressLine": {
71
+ "type": "object",
72
+ "properties": {
73
+ "IndexNumeric": {
74
+ "type": "array",
75
+ "items": {
76
+ "type": "object",
77
+ "properties": {
78
+ "Value": {
79
+ "type": "number"
80
+ }
81
+ }
82
+ },
83
+ "minItems": 0
84
+ },
85
+ "Line": {
86
+ "type": "array",
87
+ "items": {
88
+ "type": "string"
89
+ },
90
+ "minItems": 0
91
+ }
92
+ }
93
+ }
94
+ }
95
+ },
96
+ "LocationDominantBuilding": {
97
+ "type": "object",
98
+ "required": ["AddressLine"],
99
+ "properties": {
100
+ "PostcodeZone": {
101
+ "type": "object",
102
+ "required": ["Postcode"],
103
+ "properties": {
104
+ "Postcode": {
105
+ "type": "string"
106
+ }
107
+ }
108
+ },
109
+ "AddressLine": {
110
+ "type": "object",
111
+ "properties": {
112
+ "IndexNumeric": {
113
+ "type": "array",
114
+ "items": {
115
+ "type": "number"
116
+ },
117
+ "minItems": 0
118
+ },
119
+ "Line": {
120
+ "type": "array",
121
+ "items": {
122
+ "type": "string"
123
+ },
124
+ "minItems": 0
125
+ }
126
+ }
127
+ }
128
+ }
129
+ },
130
+ "Description": {
131
+ "type": "string"
132
+ },
133
+ "SourceOfInformation": {
134
+ "type": "string"
135
+ },
136
+ "AvailableDocument": {
137
+ "type": "array",
138
+ "items": {
139
+ "type": "object",
140
+ "required": ["Document"],
141
+ "properties": {
142
+ "Document": {
143
+ "type": "string"
144
+ },
145
+ "StartDate": {
146
+ "type": "string",
147
+ "format": "date"
148
+ },
149
+ "EndDate": {
150
+ "type": "string",
151
+ "format": "date"
152
+ }
153
+ }
154
+ },
155
+ "minItems": 0
156
+ },
157
+ "InterestInLand": {
158
+ "type": "string"
159
+ },
160
+ "ApplicantName": {
161
+ "type": "string"
162
+ },
163
+ "ApplicantAddress": {
164
+ "type": "object",
165
+ "required": ["AddressLine"],
166
+ "properties": {
167
+ "PostcodeZone": {
168
+ "type": "object",
169
+ "required": ["Postcode"],
170
+ "properties": {
171
+ "Postcode": {
172
+ "type": "string"
173
+ }
174
+ }
175
+ },
176
+ "AddressLine": {
177
+ "type": "object",
178
+ "properties": {
179
+ "IndexNumeric": {
180
+ "type": "array",
181
+ "items": {
182
+ "type": "object",
183
+ "properties": {
184
+ "Value": {
185
+ "type": "number"
186
+ }
187
+ }
188
+ },
189
+ "minItems": 0
190
+ },
191
+ "Line": {
192
+ "type": "array",
193
+ "items": {
194
+ "type": "object",
195
+ "properties": {
196
+ "Value": {
197
+ "type": "string"
198
+ }
199
+ }
200
+ },
201
+ "minItems": 0
202
+ }
203
+ }
204
+ }
205
+ }
206
+ },
207
+ "ServientLandDevelopment": {
208
+ "type": "object",
209
+ "required": ["Height", "CoversAllOrPartOfExtent"],
210
+ "properties": {
211
+ "Height": {
212
+ "type": "string"
213
+ },
214
+ "CoversAllOrPartOfExtent": {
215
+ "type": "string"
216
+ }
217
+ }
218
+ },
219
+ "Amount": {
220
+ "type": "string"
221
+ },
222
+ "InterestRate": {
223
+ "type": "string"
224
+ },
225
+ "LandSold": {
226
+ "type": "string"
227
+ },
228
+ "WorksDone": {
229
+ "type": "string"
230
+ },
231
+ "AdvancePayment": {
232
+ "type": "string"
233
+ },
234
+ "TotalCompensation": {
235
+ "type": "string"
236
+ },
237
+ "AgreedOrEstimated": {
238
+ "type": "string"
239
+ },
240
+ "Adjoining": {
241
+ "type": "boolean"
242
+ },
243
+ "OtherData": {
244
+ "type": "array",
245
+ "items": {
246
+ "type": "object",
247
+ "required": ["DataLabel", "Data"],
248
+ "properties": {
249
+ "DataLabel": {
250
+ "type": "string"
251
+ },
252
+ "Data": {
253
+ "type": "string"
254
+ }
255
+ }
256
+ },
257
+ "minItems": 0
258
+ },
259
+ "ChargeExtent": {
260
+ "type": "object",
261
+ "properties": {
262
+ "Value": {
263
+ "type": "string"
264
+ },
265
+ "format": {
266
+ "type": "string"
267
+ }
268
+ }
269
+ }
270
+ }
271
+ },
272
+ "minItems": 0
273
+ }
274
+ }
275
+ }