@planning-inspectorate/data-model 1.20.0 → 1.22.0

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 (57) hide show
  1. package/docs/README.md +11 -1
  2. package/docs/appeal-has.md +30 -49
  3. package/docs/appeal-s78.md +241 -144
  4. package/docs/pins-inspector-defs-specialism.md +80 -0
  5. package/docs/pins-inspector-defs.md +3 -0
  6. package/docs/pins-inspector.md +311 -3
  7. package/package.json +1 -1
  8. package/pins_data_model/models/model_appeal_document.py +1 -1
  9. package/pins_data_model/models/model_appeal_event.py +1 -1
  10. package/pins_data_model/models/model_appeal_event_estimate.py +1 -1
  11. package/pins_data_model/models/model_appeal_has.py +72 -27
  12. package/pins_data_model/models/model_appeal_representation.py +1 -1
  13. package/pins_data_model/models/model_appeal_s78.py +95 -8
  14. package/pins_data_model/models/model_dart_response.py +1 -1
  15. package/pins_data_model/models/model_entraid.py +1 -1
  16. package/pins_data_model/models/model_folder.py +1 -1
  17. package/pins_data_model/models/model_gettimesheets_response.py +1 -1
  18. package/pins_data_model/models/model_listed_building.py +1 -1
  19. package/pins_data_model/models/model_listed_buildings.py +1 -1
  20. package/pins_data_model/models/model_nsip_document.py +1 -1
  21. package/pins_data_model/models/model_nsip_exam_timetable.py +1 -1
  22. package/pins_data_model/models/model_nsip_project.py +1 -1
  23. package/pins_data_model/models/model_nsip_project_update.py +1 -1
  24. package/pins_data_model/models/model_nsip_representation.py +1 -1
  25. package/pins_data_model/models/model_nsip_subscription.py +1 -1
  26. package/pins_data_model/models/model_pins_inspector.py +75 -6
  27. package/pins_data_model/models/model_pins_lpa.py +1 -1
  28. package/pins_data_model/models/model_s51_advice.py +1 -1
  29. package/pins_data_model/models/model_service_user.py +1 -1
  30. package/schemas/appeal-has.schema.json +52 -21
  31. package/schemas/appeal-s78.schema.json +62 -7
  32. package/schemas/commands/appeals-components/appeal-advert.submission.schema.json +53 -0
  33. package/schemas/commands/{appeal-common-submission.schema.json → appeals-components/appeal-common-submission.schema.json} +8 -8
  34. package/schemas/commands/{appeal-has-submission.schema.json → appeals-components/appeal-has-submission.schema.json} +2 -2
  35. package/schemas/commands/{appeal-s78-submission.schema.json → appeals-components/appeal-s78-submission.schema.json} +2 -2
  36. package/schemas/commands/appeals-components/appellant-procedure-preference.schema.json +37 -0
  37. package/schemas/commands/appeals-components/changed-listed-building-number.schema.json +23 -0
  38. package/schemas/commands/appeals-components/lpa-procedure-preference.schema.json +28 -0
  39. package/schemas/commands/{lpa-questionnaire-cas-advert.schema.json → appeals-components/lpa-questionnaire-advert.schema.json} +8 -33
  40. package/schemas/commands/{lpa-questionnaire-common.schema.json → appeals-components/lpa-questionnaire-common.schema.json} +1 -1
  41. package/schemas/commands/{lpa-questionnaire-has.schema.json → appeals-components/lpa-questionnaire-has.schema.json} +1 -1
  42. package/schemas/commands/{lpa-questionnaire-s78.schema.json → appeals-components/lpa-questionnaire-s78.schema.json} +2 -21
  43. package/schemas/commands/appellant-submission.schema.json +21 -10
  44. package/schemas/commands/lpa-questionnaire.schema.json +24 -10
  45. package/schemas/pins-inspector.schema.json +108 -3
  46. package/src/index.cjs +6 -2
  47. package/src/index.d.ts +4 -0
  48. package/src/index.js +5 -2
  49. package/src/schemas.d.ts +385 -802
  50. package/tests/appeals/appeal-has.test.js +30 -5
  51. package/tests/appeals/appeal-s78.test.js +17 -3
  52. package/tests/appeals/commands/adverts-questionnaire.test.js +1 -0
  53. package/tests/appeals/commands/adverts-submission.test.js +13 -13
  54. package/tests/appeals/commands/cas-adverts-submission.test.js +184 -0
  55. package/tests/appeals/commands/has-submission.test.js +2 -2
  56. package/tests/appeals/commands/s78-submission.test.js +2 -2
  57. package/schemas/commands/appeal-advert.submission.schema.json +0 -23
@@ -80,7 +80,6 @@ const appealHas = {
80
80
  affectsScheduledMonument: true,
81
81
  hasProtectedSpecies: true,
82
82
  isAonbNationalLandscape: null,
83
- isSiteOnHighwayLand: null,
84
83
  designatedSitesNames: null,
85
84
  hasInfrastructureLevy: false,
86
85
  isInfrastructureLevyFormallyAdopted: false,
@@ -164,11 +163,16 @@ const appealHas = {
164
163
  siteGridReferenceEasting: '357144',
165
164
  siteGridReferenceNorthing: '400534',
166
165
  hasLandownersPermission: null,
167
- isAdvertInPosition: false,
168
166
  isSiteInAreaOfSpecialControlAdverts: false,
169
167
  wasApplicationRefusedDueToHighwayOrTraffic: null,
170
168
  didAppellantSubmitCompletePhotosAndPlans: null,
171
- advertType: null
169
+ advertDetails: [
170
+ {
171
+ isAdvertInPosition: false,
172
+ isSiteOnHighwayLand: false,
173
+ advertType: 'Other'
174
+ }
175
+ ]
172
176
  };
173
177
 
174
178
  describe(schema, () => {
@@ -187,10 +191,10 @@ describe(schema, () => {
187
191
  it('should enforce eastings-northings pattern', () => {
188
192
  const invalidPatterns = [
189
193
  { easting: 'abc123', northing: '400534', description: 'easting with letters' },
190
- { easting: '1234567', northing: '400534', description: 'easting too long' },
194
+ { easting: '12345678', northing: '400534', description: 'easting too long' },
191
195
  { easting: '12345', northing: '400534', description: 'easting too short' },
192
196
  { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
193
- { easting: '357144', northing: '1234567', description: 'northing too long' },
197
+ { easting: '357144', northing: '12345678', description: 'northing too long' },
194
198
  { easting: '357144', northing: '12345', description: 'northing too short' },
195
199
  { easting: '', northing: '400534', description: 'empty easting' },
196
200
  { easting: '357144', northing: '', description: 'empty northing' }
@@ -240,4 +244,25 @@ describe(schema, () => {
240
244
  const validationResult = ajv.validate(schema, test);
241
245
  assert.strictEqual(validationResult, true);
242
246
  });
247
+
248
+ it('should not allow custom advertType', () => {
249
+ const test = structuredClone(appealHas);
250
+ test.advertDetails[0].advertType = 'something';
251
+ const validationResult = ajv.validate(schema, test);
252
+ assert.strictEqual(validationResult, false);
253
+ });
254
+
255
+ it('should not allow custom lpaProcedurePreference', () => {
256
+ const test = structuredClone(appealHas);
257
+ test.lpaProcedurePreference = 'something';
258
+ const validationResult = ajv.validate(schema, test);
259
+ assert.strictEqual(validationResult, false);
260
+ });
261
+
262
+ it('should enforce required props', () => {
263
+ const test = structuredClone(appealHas);
264
+ delete test.lpaCostsAppliedFor;
265
+ const validationResult = ajv.validate(schema, test);
266
+ assert.strictEqual(validationResult, false);
267
+ });
243
268
  });
@@ -153,7 +153,14 @@ const appealS78 = {
153
153
  typeOfPlanningApplication: 'full-appeal',
154
154
  developmentType: 'other-minor',
155
155
  statementOfCommonGroundDueDate: '1919-10-16T12:35:55.0Z',
156
- planningObligationDueDate: '1899-04-25T22:23:18.0Z'
156
+ planningObligationDueDate: '1899-04-25T22:23:18.0Z',
157
+ advertDetails: [
158
+ {
159
+ isAdvertInPosition: false,
160
+ isSiteOnHighwayLand: false,
161
+ advertType: 'Other'
162
+ }
163
+ ]
157
164
  };
158
165
 
159
166
  describe(schema, () => {
@@ -171,10 +178,10 @@ describe(schema, () => {
171
178
  it('should enforce eastings-northings pattern', () => {
172
179
  const invalidPatterns = [
173
180
  { easting: 'abc123', northing: '400534', description: 'easting with letters' },
174
- { easting: '1234567', northing: '400534', description: 'easting too long' },
181
+ { easting: '12345678', northing: '400534', description: 'easting too long' },
175
182
  { easting: '12345', northing: '400534', description: 'easting too short' },
176
183
  { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
177
- { easting: '357144', northing: '1234567', description: 'northing too long' },
184
+ { easting: '357144', northing: '12345678', description: 'northing too long' },
178
185
  { easting: '357144', northing: '12345', description: 'northing too short' },
179
186
  { easting: '', northing: '400534', description: 'empty easting' },
180
187
  { easting: '357144', northing: '', description: 'empty northing' }
@@ -224,4 +231,11 @@ describe(schema, () => {
224
231
  const validationResult = ajv.validate(schema, test);
225
232
  assert.strictEqual(validationResult, true);
226
233
  });
234
+
235
+ it('should not allow custom advertType', () => {
236
+ const test = structuredClone(appealS78);
237
+ test.advertDetails[0].advertType = 'something';
238
+ const validationResult = ajv.validate(schema, test);
239
+ assert.strictEqual(validationResult, false);
240
+ });
227
241
  });
@@ -49,6 +49,7 @@ const exampleAdvertQuestionnaireSchema = {
49
49
  'ullamco quis',
50
50
  'voluptate quis'
51
51
  ],
52
+ changedListedBuildingNumbers: ['occaecat id aliquip dolore'],
52
53
  lpaCostsAppliedFor: null,
53
54
  affectsScheduledMonument: null,
54
55
  hasProtectedSpecies: null,
@@ -69,8 +69,17 @@ const exampleAdvertSubmissionSchema = {
69
69
  neighbouringSiteAddresses: null,
70
70
  appellantCostsAppliedFor: true,
71
71
  hasLandownersPermission: true,
72
- isAdvertInPosition: true,
73
- isSiteOnHighwayLand: true
72
+ advertDetails: [
73
+ {
74
+ isAdvertInPosition: false,
75
+ isSiteOnHighwayLand: false,
76
+ advertType: 'Other'
77
+ }
78
+ ],
79
+ appellantProcedurePreference: 'hearing',
80
+ appellantProcedurePreferenceDetails: 'eiusmod ex exercitation',
81
+ appellantProcedurePreferenceDuration: 10816414.941069126,
82
+ appellantProcedurePreferenceWitnessCount: -91300569.36444478
74
83
  },
75
84
  documents: [document],
76
85
  users: [user]
@@ -87,15 +96,6 @@ describe('Advert submission command schema', () => {
87
96
  assert.strictEqual(validationResult, true);
88
97
  });
89
98
 
90
- it('should allow valid schema for CAS Advert', () => {
91
- const test = structuredClone(exampleAdvertSubmissionSchema);
92
- test.casedata.caseType = 'ZA';
93
-
94
- const validationResult = ajv.validate(schema, test);
95
-
96
- assert.strictEqual(validationResult, true);
97
- });
98
-
99
99
  it('should enforce advert specific props', () => {
100
100
  const test = structuredClone(exampleAdvertSubmissionSchema);
101
101
  delete test.casedata.hasLandownersPermission;
@@ -110,10 +110,10 @@ describe('Advert submission command schema', () => {
110
110
  it('should enforce eastings-northings pattern', () => {
111
111
  const invalidPatterns = [
112
112
  { easting: 'abc123', northing: '400534', description: 'easting with letters' },
113
- { easting: '1234567', northing: '400534', description: 'easting too long' },
113
+ { easting: '12345678', northing: '400534', description: 'easting too long' },
114
114
  { easting: '12345', northing: '400534', description: 'easting too short' },
115
115
  { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
116
- { easting: '357144', northing: '1234567', description: 'northing too long' },
116
+ { easting: '357144', northing: '12345678', description: 'northing too long' },
117
117
  { easting: '357144', northing: '12345', description: 'northing too short' },
118
118
  { easting: '', northing: '400534', description: 'empty easting' },
119
119
  { easting: '357144', northing: '', description: 'empty northing' }
@@ -0,0 +1,184 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import Ajv from 'ajv';
4
+ import addFormats from 'ajv-formats';
5
+ import { loadAllSchemasSync } from '../../../src/index.js';
6
+
7
+ const schema = 'appellant-submission.schema.json';
8
+
9
+ const user = {
10
+ salutation: 'nisi aute tempor Ut',
11
+ firstName: null,
12
+ lastName: null,
13
+ emailAddress: null,
14
+ telephoneNumber: 'irure',
15
+ organisation: null,
16
+ serviceUserType: 'Appellant'
17
+ };
18
+
19
+ const document = {
20
+ documentId: 'Lorem',
21
+ filename: 'sunt',
22
+ originalFilename: 'dolore fugiat',
23
+ size: 663667,
24
+ mime: 'consequat veniam ipsum ullamco sint',
25
+ documentURI: 'reprehenderit',
26
+ dateCreated: '1904-04-10T14:53:32.0Z',
27
+ documentType: 'appellantCaseWithdrawalLetter'
28
+ };
29
+
30
+ const exampleAdvertSubmissionSchema = {
31
+ casedata: {
32
+ submissionId: '072a9b08-0266-a291-ecd0-bb0ded9d7350',
33
+ caseType: 'ZA',
34
+ caseProcedure: 'written',
35
+ lpaCode: 'in laborum pariatur cupidatat',
36
+ caseSubmittedDate: '1920-12-06T17:37:12.0Z',
37
+ enforcementNotice: false,
38
+ applicationReference: 'voluptate esse Excepteur veniam sunt',
39
+ applicationDate: '1921-01-26T02:05:56.0Z',
40
+ applicationDecision: 'not_received',
41
+ applicationDecisionDate: '1890-12-26T17:09:36.0Z',
42
+ caseSubmissionDueDate: null,
43
+ siteAddressLine1: 'exercitation incididunt laborum',
44
+ siteAddressLine2: 'in Ut voluptate ea',
45
+ siteAddressTown: 'Lorem amet consectetur',
46
+ siteAddressCounty: null,
47
+ siteAddressPostcode: 'sunt nulla aliquip labore',
48
+ siteGridReferenceEasting: '357144',
49
+ siteGridReferenceNorthing: '400534',
50
+ siteAccessDetails: null,
51
+ siteSafetyDetails: ['Duis laboris ex', 'sint Lorem cupidatat commodo'],
52
+ isGreenBelt: null,
53
+ siteAreaSquareMetres: -11383801.18983768,
54
+ floorSpaceSquareMetres: -69227017.16474937,
55
+ ownsAllLand: null,
56
+ ownsSomeLand: null,
57
+ knowsOtherOwners: null,
58
+ knowsAllOwners: null,
59
+ advertisedAppeal: true,
60
+ ownersInformed: null,
61
+ originalDevelopmentDescription: null,
62
+ changedDevelopmentDescription: false,
63
+ nearbyCaseReferences: [
64
+ 'enim consectetur irure ad',
65
+ 'irure ut',
66
+ 'irure eiusmod minim Excepteur id',
67
+ 'aliqua pariatur consectetur eiusmod'
68
+ ],
69
+ neighbouringSiteAddresses: null,
70
+ appellantCostsAppliedFor: true,
71
+ hasLandownersPermission: true,
72
+ advertDetails: [
73
+ {
74
+ isAdvertInPosition: false,
75
+ isSiteOnHighwayLand: false,
76
+ advertType: 'Other'
77
+ }
78
+ ]
79
+ },
80
+ documents: [document],
81
+ users: [user]
82
+ };
83
+
84
+ describe('Advert submission command schema', () => {
85
+ const schemas = loadAllSchemasSync();
86
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
87
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
88
+ addFormats(ajv);
89
+
90
+ it('should allow valid schema for Advert', () => {
91
+ const validationResult = ajv.validate(schema, exampleAdvertSubmissionSchema);
92
+ assert.strictEqual(validationResult, true);
93
+ });
94
+
95
+ it('should enforce advert specific props', () => {
96
+ const test = structuredClone(exampleAdvertSubmissionSchema);
97
+ delete test.casedata.hasLandownersPermission;
98
+ delete test.casedata.isAdvertInPosition;
99
+ delete test.casedata.isSiteOnHighwayLand;
100
+
101
+ const validationResult = ajv.validate(schema, test);
102
+
103
+ assert.strictEqual(validationResult, false);
104
+ });
105
+
106
+ it('should enforce eastings-northings pattern', () => {
107
+ const invalidPatterns = [
108
+ { easting: 'abc123', northing: '400534', description: 'easting with letters' },
109
+ { easting: '12345678', northing: '400534', description: 'easting too long' },
110
+ { easting: '12345', northing: '400534', description: 'easting too short' },
111
+ { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
112
+ { easting: '357144', northing: '12345678', description: 'northing too long' },
113
+ { easting: '357144', northing: '12345', description: 'northing too short' },
114
+ { easting: '', northing: '400534', description: 'empty easting' },
115
+ { easting: '357144', northing: '', description: 'empty northing' }
116
+ ];
117
+
118
+ for (const pattern of invalidPatterns) {
119
+ const test = structuredClone(exampleAdvertSubmissionSchema);
120
+ test.casedata.siteGridReferenceEasting = pattern.easting;
121
+ test.casedata.siteGridReferenceNorthing = pattern.northing;
122
+
123
+ const validationResult = ajv.validate(schema, test);
124
+ assert.strictEqual(validationResult, false, `Expected validation to fail for ${pattern.description}`);
125
+ }
126
+ });
127
+
128
+ it('should allow only eastings-northings', () => {
129
+ const test = structuredClone(exampleAdvertSubmissionSchema);
130
+ test.casedata.siteAddressPostcode = undefined;
131
+ test.casedata.siteAddressLine1 = null;
132
+ test.casedata.siteAddressTown = null;
133
+ const validationResult = ajv.validate(schema, test);
134
+ assert.strictEqual(validationResult, true);
135
+ });
136
+
137
+ it('should allow only site address', () => {
138
+ const test = structuredClone(exampleAdvertSubmissionSchema);
139
+ test.casedata.siteGridReferenceEasting = undefined;
140
+ test.casedata.siteGridReferenceNorthing = null;
141
+ const validationResult = ajv.validate(schema, test);
142
+ assert.strictEqual(validationResult, true);
143
+ });
144
+
145
+ it('should enforce either site address or easting-northing', () => {
146
+ const test = structuredClone(exampleAdvertSubmissionSchema);
147
+ test.casedata.siteAddressPostcode = undefined;
148
+ test.casedata.siteAddressLine1 = null;
149
+ test.casedata.siteAddressTown = null;
150
+ test.casedata.siteGridReferenceEasting = undefined;
151
+ test.casedata.siteGridReferenceNorthing = null;
152
+ const validationResult = ajv.validate(schema, test);
153
+ assert.strictEqual(validationResult, false);
154
+ });
155
+
156
+ it('should reject missing root property', () => {
157
+ const test = structuredClone(exampleAdvertSubmissionSchema);
158
+ delete test.casedata.caseType;
159
+ const validationResult = ajv.validate(schema, test);
160
+ assert.strictEqual(validationResult, false);
161
+ assert.strictEqual(
162
+ ajv.errors.some((x) => x.message === "must have required property 'caseType'"),
163
+ true
164
+ );
165
+ });
166
+
167
+ it('should reject missing nested property', () => {
168
+ const test = structuredClone(exampleAdvertSubmissionSchema);
169
+ delete test.casedata.caseProcedure;
170
+ const validationResult = ajv.validate(schema, test);
171
+ assert.strictEqual(validationResult, false);
172
+ assert.strictEqual(
173
+ ajv.errors.some((x) => x.message === "must have required property 'caseProcedure'"),
174
+ true
175
+ );
176
+ });
177
+
178
+ it('should allow additional props', () => {
179
+ const test = structuredClone(exampleAdvertSubmissionSchema);
180
+ test.test = 1;
181
+ const validationResult = ajv.validate(schema, test);
182
+ assert.strictEqual(validationResult, true);
183
+ });
184
+ });
@@ -96,10 +96,10 @@ describe('HAS submission command schema', () => {
96
96
  it('should enforce eastings-northings pattern', () => {
97
97
  const invalidPatterns = [
98
98
  { easting: 'abc123', northing: '400534', description: 'easting with letters' },
99
- { easting: '1234567', northing: '400534', description: 'easting too long' },
99
+ { easting: '12345678', northing: '400534', description: 'easting too long' },
100
100
  { easting: '12345', northing: '400534', description: 'easting too short' },
101
101
  { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
102
- { easting: '357144', northing: '1234567', description: 'northing too long' },
102
+ { easting: '357144', northing: '12345678', description: 'northing too long' },
103
103
  { easting: '357144', northing: '12345', description: 'northing too short' },
104
104
  { easting: '', northing: '400534', description: 'empty easting' },
105
105
  { easting: '357144', northing: '', description: 'empty northing' }
@@ -100,10 +100,10 @@ describe('S78 submission command schema', () => {
100
100
  it('should enforce eastings-northings pattern', () => {
101
101
  const invalidPatterns = [
102
102
  { easting: 'abc123', northing: '400534', description: 'easting with letters' },
103
- { easting: '1234567', northing: '400534', description: 'easting too long' },
103
+ { easting: '1234567*', northing: '400534', description: 'easting too long' },
104
104
  { easting: '12345', northing: '400534', description: 'easting too short' },
105
105
  { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
106
- { easting: '357144', northing: '1234567', description: 'northing too long' },
106
+ { easting: '357144', northing: '12345678', description: 'northing too long' },
107
107
  { easting: '357144', northing: '12345', description: 'northing too short' },
108
108
  { easting: '', northing: '400534', description: 'empty easting' },
109
109
  { easting: '357144', northing: '', description: 'empty northing' }
@@ -1,23 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "commands/appeal-advert.submission.schema.json",
4
- "title": "Advert specific properties",
5
- "description": "Schema defining any properties for advertisement appeals",
6
- "type": "object",
7
- "additionalProperties": true,
8
- "required": ["isSiteOnHighwayLand", "isAdvertInPosition", "hasLandownersPermission"],
9
- "properties": {
10
- "hasLandownersPermission": {
11
- "type": ["boolean", "null"],
12
- "description": "Indicates if the appellant has land owners permission"
13
- },
14
- "isAdvertInPosition": {
15
- "type": ["boolean", "null"],
16
- "description": "Indicates if the advert is already in position"
17
- },
18
- "isSiteOnHighwayLand": {
19
- "type": ["boolean", "null"],
20
- "description": "Indicates if site is on highway land"
21
- }
22
- }
23
- }