@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.
- package/docs/appeal-document.md +2 -0
- package/docs/appeal-has.md +1 -0
- package/docs/appeal-s78.md +118 -0
- package/package.json +1 -1
- package/pins_data_model/models/model_appeal_document.py +3 -1
- package/pins_data_model/models/model_appeal_has.py +2 -1
- package/pins_data_model/models/model_appeal_s78.py +38 -1
- package/schemas/appeal-document.schema.json +2 -0
- package/schemas/appeal-has.schema.json +1 -0
- package/schemas/appeal-s78.schema.json +20 -0
- package/schemas/commands/appeals-components/appeal-common-planning.schema.json +54 -0
- package/schemas/commands/appeals-components/appeal-common-submission.schema.json +7 -81
- package/schemas/commands/appeals-components/appeal-land-ownership.schema.json +48 -0
- package/schemas/commands/appeals-components/appeal-ldc.submission.schema.json +19 -0
- package/schemas/commands/appeals-components/appeal-site-area.schema.json +21 -0
- package/schemas/commands/appeals-components/lpa-questionnaire-common.schema.json +6 -1
- package/schemas/commands/appeals-components/lpa-questionnaire-has.schema.json +0 -5
- package/schemas/commands/appeals-components/lpa-questionnaire-ldc.schema.json +31 -0
- package/schemas/commands/appellant-submission.schema.json +20 -4
- package/schemas/commands/lpa-questionnaire.schema.json +13 -1
- package/src/enums.cjs +17 -0
- package/src/enums.d.ts +15 -0
- package/src/enums.js +15 -0
- package/src/index.cjs +17 -0
- package/src/schemas.d.ts +139 -89
- package/tests/appeals/appeal-s78.test.js +16 -1
- package/tests/appeals/commands/ldc-questionnaire.test.js +83 -0
- package/tests/appeals/commands/ldc-submission.test.js +172 -0
- 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
|
-
}
|