@planning-inspectorate/data-model 2.10.2 → 2.11.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 (29) hide show
  1. package/docs/appeal-document.md +2 -0
  2. package/docs/appeal-has.md +1 -0
  3. package/docs/appeal-s78.md +118 -0
  4. package/package.json +1 -1
  5. package/pins_data_model/models/model_appeal_document.py +3 -1
  6. package/pins_data_model/models/model_appeal_has.py +2 -1
  7. package/pins_data_model/models/model_appeal_s78.py +38 -1
  8. package/schemas/appeal-document.schema.json +2 -0
  9. package/schemas/appeal-has.schema.json +1 -0
  10. package/schemas/appeal-s78.schema.json +20 -0
  11. package/schemas/commands/appeals-components/appeal-common-planning.schema.json +54 -0
  12. package/schemas/commands/appeals-components/appeal-common-submission.schema.json +7 -81
  13. package/schemas/commands/appeals-components/appeal-land-ownership.schema.json +48 -0
  14. package/schemas/commands/appeals-components/appeal-ldc.submission.schema.json +19 -0
  15. package/schemas/commands/appeals-components/appeal-site-area.schema.json +21 -0
  16. package/schemas/commands/appeals-components/lpa-questionnaire-common.schema.json +6 -1
  17. package/schemas/commands/appeals-components/lpa-questionnaire-has.schema.json +0 -5
  18. package/schemas/commands/appeals-components/lpa-questionnaire-ldc.schema.json +31 -0
  19. package/schemas/commands/appellant-submission.schema.json +20 -4
  20. package/schemas/commands/lpa-questionnaire.schema.json +13 -1
  21. package/src/enums.cjs +17 -0
  22. package/src/enums.d.ts +15 -0
  23. package/src/enums.js +15 -0
  24. package/src/index.cjs +17 -0
  25. package/src/schemas.d.ts +139 -89
  26. package/tests/appeals/appeal-s78.test.js +16 -1
  27. package/tests/appeals/commands/ldc-questionnaire.test.js +83 -0
  28. package/tests/appeals/commands/ldc-submission.test.js +172 -0
  29. package/schemas/commands/appeals-components/appeal-has-submission.schema.json +0 -81
@@ -161,7 +161,12 @@ const appealS78 = {
161
161
  advertType: 'Other'
162
162
  }
163
163
  ],
164
- padsSapId: null
164
+ padsSapId: null,
165
+ applicationMadeUnderActSection: 'existing-development',
166
+ siteUseAtTimeOfApplication: 'Site use at time of application',
167
+ appealUnderActSection: 'proposed-use-of-a-development',
168
+ lpaConsiderAppealInvalid: true,
169
+ lpaAppealInvalidReasons: 'Reason(s) why LPA consider the appeal invalid'
165
170
  };
166
171
 
167
172
  describe(schema, () => {
@@ -239,4 +244,14 @@ describe(schema, () => {
239
244
  const validationResult = ajv.validate(schema, test);
240
245
  assert.strictEqual(validationResult, false);
241
246
  });
247
+
248
+ const groupBAppealTypes = ['C', 'F', 'H', 'W', 'X', 'Y', 'Z'];
249
+ groupBAppealTypes.forEach((appealType) => {
250
+ it(`should support group B appeal type ${appealType}`, () => {
251
+ const test = structuredClone(appealS78);
252
+ test.caseType = appealType;
253
+ const validationResult = ajv.validate(schema, test);
254
+ assert.strictEqual(validationResult, true);
255
+ });
256
+ });
242
257
  });
@@ -0,0 +1,83 @@
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 = 'lpa-questionnaire.schema.json';
8
+
9
+ const document = {
10
+ documentId: 'deserunt sint aliqua adipisicing dolore',
11
+ filename: 'ut dolore',
12
+ originalFilename: 'Lorem amet fugiat adipisicing Excepteur',
13
+ size: -90476934,
14
+ mime: 'magna labore exercitation ut',
15
+ documentURI: 'ut reprehenderit',
16
+ dateCreated: '1933-04-03T03:24:38.0Z',
17
+ documentType: 'plansDrawings'
18
+ };
19
+
20
+ const exampleHasQuestionnaireSchema = {
21
+ casedata: {
22
+ caseType: 'X',
23
+ caseReference: 'nisi incididunt cillum',
24
+ lpaQuestionnaireSubmittedDate: '2026-04-03T03:24:38.0Z',
25
+ siteAccessDetails: ['test'],
26
+ siteSafetyDetails: ['test 2'],
27
+ isCorrectAppealType: true,
28
+ nearbyCaseReferences: ['1234567', '7654321'],
29
+ neighbouringSiteAddresses: [
30
+ {
31
+ neighbouringSiteAddressLine1: 'commodo dolore incididunt',
32
+ neighbouringSiteAddressLine2: 'est voluptate irure aliqua elit',
33
+ neighbouringSiteAddressTown: 'labore aliquip ex incididunt nulla',
34
+ neighbouringSiteAddressCounty: 'enim ut Duis ut ex',
35
+ neighbouringSiteAddressPostcode: 'pariatur',
36
+ neighbouringSiteAccessDetails: 'aliqua irure mollit consequat nisi',
37
+ neighbouringSiteSafetyDetails: null
38
+ }
39
+ ],
40
+ reasonForNeighbourVisits: 'adipisicing in voluptate',
41
+ hasInfrastructureLevy: true,
42
+ isInfrastructureLevyFormallyAdopted: true,
43
+ infrastructureLevyAdoptedDate: '1913-06-14T10:40:33.0Z',
44
+ infrastructureLevyExpectedDate: '1912-06-14T10:40:33.0Z',
45
+ lpaProcedurePreference: 'inquiry',
46
+ lpaProcedurePreferenceDetails: 'in reprehenderit',
47
+ lpaProcedurePreferenceDuration: -34480053.16630782,
48
+ appealUnderActSection: 'proposed-use-of-a-development',
49
+ lpaConsiderAppealInvalid: true,
50
+ lpaAppealInvalidReasons: 'Reason(s) why LPA consider the appeal invalid'
51
+ },
52
+ documents: [document]
53
+ };
54
+
55
+ describe('LDC questionnaire command schema', () => {
56
+ const schemas = loadAllSchemasSync();
57
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
58
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
59
+ addFormats(ajv);
60
+
61
+ it('should allow valid schema for LDC', () => {
62
+ const validationResult = ajv.validate(schema, exampleHasQuestionnaireSchema);
63
+ assert.strictEqual(validationResult, true);
64
+ });
65
+
66
+ it('should reject missing caseReference', () => {
67
+ const test = structuredClone(exampleHasQuestionnaireSchema);
68
+ delete test.casedata.caseReference;
69
+ const validationResult = ajv.validate(schema, test);
70
+ assert.strictEqual(validationResult, false);
71
+ assert.strictEqual(
72
+ ajv.errors.some((x) => x.message === "must have required property 'caseReference'"),
73
+ true
74
+ );
75
+ });
76
+
77
+ it('should allow additional props', () => {
78
+ const test = structuredClone(exampleHasQuestionnaireSchema);
79
+ test.test = 1;
80
+ const validationResult = ajv.validate(schema, test);
81
+ assert.strictEqual(validationResult, true);
82
+ });
83
+ });
@@ -0,0 +1,172 @@
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 exampleLDCSubmissionSchema = {
31
+ casedata: {
32
+ submissionId: '072a9b08-0266-a291-ecd0-bb0ded9d7350',
33
+ caseType: 'X',
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
+ originalDevelopmentDescription: null,
54
+ changedDevelopmentDescription: false,
55
+ nearbyCaseReferences: [
56
+ 'enim consectetur irure ad',
57
+ 'irure ut',
58
+ 'irure eiusmod minim Excepteur id',
59
+ 'aliqua pariatur consectetur eiusmod'
60
+ ],
61
+ appellantCostsAppliedFor: true,
62
+ appellantProcedurePreference: 'hearing',
63
+ appellantProcedurePreferenceDetails: 'eiusmod ex exercitation',
64
+ appellantProcedurePreferenceDuration: 10816414.941069126,
65
+ appellantProcedurePreferenceWitnessCount: -91300569.36444478,
66
+ siteUseAtTimeOfApplication: 'residential',
67
+ applicationMadeUnderActSection: 'existing-development'
68
+ },
69
+ documents: [document],
70
+ users: [user]
71
+ };
72
+
73
+ describe('LDC submission command schema', () => {
74
+ const schemas = loadAllSchemasSync();
75
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
76
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
77
+ addFormats(ajv);
78
+
79
+ it('should allow valid schema for LDC', () => {
80
+ const validationResult = ajv.validate(schema, exampleLDCSubmissionSchema);
81
+ assert.strictEqual(validationResult, true);
82
+ });
83
+
84
+ it('should enforce LDC specific props', () => {
85
+ const test = structuredClone(exampleLDCSubmissionSchema);
86
+ delete test.casedata.siteUseAtTimeOfApplication;
87
+ delete test.casedata.applicationMadeUnderActSection;
88
+
89
+ const validationResult = ajv.validate(schema, test);
90
+
91
+ assert.strictEqual(validationResult, false);
92
+ });
93
+
94
+ it('should enforce eastings-northings pattern', () => {
95
+ const invalidPatterns = [
96
+ { easting: 'abc123', northing: '400534', description: 'easting with letters' },
97
+ { easting: '12345678', northing: '400534', description: 'easting too long' },
98
+ { easting: '12345', northing: '400534', description: 'easting too short' },
99
+ { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
100
+ { easting: '357144', northing: '12345678', description: 'northing too long' },
101
+ { easting: '357144', northing: '12345', description: 'northing too short' },
102
+ { easting: '', northing: '400534', description: 'empty easting' },
103
+ { easting: '357144', northing: '', description: 'empty northing' }
104
+ ];
105
+
106
+ for (const pattern of invalidPatterns) {
107
+ const test = structuredClone(exampleLDCSubmissionSchema);
108
+ test.casedata.siteGridReferenceEasting = pattern.easting;
109
+ test.casedata.siteGridReferenceNorthing = pattern.northing;
110
+
111
+ const validationResult = ajv.validate(schema, test);
112
+ assert.strictEqual(validationResult, false, `Expected validation to fail for ${pattern.description}`);
113
+ }
114
+ });
115
+
116
+ it('should allow only eastings-northings', () => {
117
+ const test = structuredClone(exampleLDCSubmissionSchema);
118
+ test.casedata.siteAddressPostcode = undefined;
119
+ test.casedata.siteAddressLine1 = null;
120
+ test.casedata.siteAddressTown = null;
121
+ const validationResult = ajv.validate(schema, test);
122
+ assert.strictEqual(validationResult, true);
123
+ });
124
+
125
+ it('should allow only site address', () => {
126
+ const test = structuredClone(exampleLDCSubmissionSchema);
127
+ test.casedata.siteGridReferenceEasting = undefined;
128
+ test.casedata.siteGridReferenceNorthing = null;
129
+ const validationResult = ajv.validate(schema, test);
130
+ assert.strictEqual(validationResult, true);
131
+ });
132
+
133
+ it('should enforce either site address or easting-northing', () => {
134
+ const test = structuredClone(exampleLDCSubmissionSchema);
135
+ test.casedata.siteAddressPostcode = undefined;
136
+ test.casedata.siteAddressLine1 = null;
137
+ test.casedata.siteAddressTown = null;
138
+ test.casedata.siteGridReferenceEasting = undefined;
139
+ test.casedata.siteGridReferenceNorthing = null;
140
+ const validationResult = ajv.validate(schema, test);
141
+ assert.strictEqual(validationResult, false);
142
+ });
143
+
144
+ it('should reject missing root property', () => {
145
+ const test = structuredClone(exampleLDCSubmissionSchema);
146
+ delete test.casedata.caseType;
147
+ const validationResult = ajv.validate(schema, test);
148
+ assert.strictEqual(validationResult, false);
149
+ assert.strictEqual(
150
+ ajv.errors.some((x) => x.message === "must have required property 'caseType'"),
151
+ true
152
+ );
153
+ });
154
+
155
+ it('should reject missing nested property', () => {
156
+ const test = structuredClone(exampleLDCSubmissionSchema);
157
+ delete test.casedata.caseProcedure;
158
+ const validationResult = ajv.validate(schema, test);
159
+ assert.strictEqual(validationResult, false);
160
+ assert.strictEqual(
161
+ ajv.errors.some((x) => x.message === "must have required property 'caseProcedure'"),
162
+ true
163
+ );
164
+ });
165
+
166
+ it('should allow additional props', () => {
167
+ const test = structuredClone(exampleLDCSubmissionSchema);
168
+ test.test = 1;
169
+ const validationResult = ajv.validate(schema, test);
170
+ assert.strictEqual(validationResult, true);
171
+ });
172
+ });
@@ -1,81 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "commands/appeals-components/appeal-has-submission.schema.json",
4
- "title": "Appellant HAS submission properties",
5
- "description": "Schema defining any HAS specific properties for appeal submissions",
6
- "type": "object",
7
- "additionalProperties": true,
8
- "required": [
9
- "isGreenBelt",
10
- "siteAreaSquareMetres",
11
- "floorSpaceSquareMetres",
12
- "ownsAllLand",
13
- "ownsSomeLand",
14
- "knowsOtherOwners",
15
- "knowsAllOwners",
16
- "advertisedAppeal",
17
- "ownersInformed",
18
- "originalDevelopmentDescription",
19
- "changedDevelopmentDescription",
20
- "appellantCostsAppliedFor"
21
- ],
22
- "allOf": [
23
- {
24
- "properties": {
25
- "isGreenBelt": {
26
- "type": ["boolean", "null"],
27
- "description": "Indicates if the site is in a green belt"
28
- },
29
- "siteAreaSquareMetres": {
30
- "type": ["number", "null"],
31
- "examples": [45.7],
32
- "description": "The site area, in square meters"
33
- },
34
- "floorSpaceSquareMetres": {
35
- "type": ["number", "null"],
36
- "examples": [3.5],
37
- "description": "The floor space, in square meters"
38
- },
39
- "ownsAllLand": {
40
- "type": ["boolean", "null"],
41
- "description": "Indicates if the appellant has complete ownership of the site"
42
- },
43
- "ownsSomeLand": {
44
- "type": ["boolean", "null"],
45
- "description": "Indicates if the appellant has partial ownership of the site"
46
- },
47
- "knowsOtherOwners": {
48
- "type": ["string", "null"],
49
- "enum": ["Yes", "No", "Some", null],
50
- "description": "Indicates if the appellant knows other owners of the site"
51
- },
52
- "knowsAllOwners": {
53
- "type": ["string", "null"],
54
- "enum": ["Yes", "No", "Some", null],
55
- "description": "Indicates if the appellant knows all owners of the site"
56
- },
57
- "advertisedAppeal": {
58
- "type": ["boolean", "null"],
59
- "description": "Indicates if the appellant has advertised the appeal to the LPA decision"
60
- },
61
- "ownersInformed": {
62
- "type": ["boolean", "null"],
63
- "description": "Indicates if the appellant has informed other owners of the site"
64
- },
65
- "originalDevelopmentDescription": {
66
- "type": ["string", "null"],
67
- "examples": ["Replacement of an existing gate to access the property"],
68
- "description": "The original description of the development, as provided by the appellant"
69
- },
70
- "changedDevelopmentDescription": {
71
- "type": ["boolean", "null"],
72
- "description": "Indicates that the LPA has changed the development description"
73
- },
74
- "appellantCostsAppliedFor": {
75
- "type": ["boolean", "null"],
76
- "description": "Indicates if the appellant has applied for costs"
77
- }
78
- }
79
- }
80
- ]
81
- }