@planning-inspectorate/data-model 2.30.0 → 2.31.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 (39) hide show
  1. package/docs/README.md +4 -0
  2. package/docs/appeal-document.md +96 -94
  3. package/docs/appeal-s78-definitions-significantchangesaffectingapplication.md +14 -0
  4. package/docs/appeal-s78-definitions.md +3 -0
  5. package/docs/appeal-s78.md +292 -198
  6. package/package.json +1 -1
  7. package/pins_data_model/models/model_appeal_document.py +3 -1
  8. package/pins_data_model/models/model_appeal_event.py +1 -1
  9. package/pins_data_model/models/model_appeal_event_estimate.py +1 -1
  10. package/pins_data_model/models/model_appeal_has.py +1 -1
  11. package/pins_data_model/models/model_appeal_representation.py +1 -1
  12. package/pins_data_model/models/model_appeal_s78.py +128 -70
  13. package/pins_data_model/models/model_dart_response.py +1 -1
  14. package/pins_data_model/models/model_entraid.py +1 -1
  15. package/pins_data_model/models/model_folder.py +1 -1
  16. package/pins_data_model/models/model_gettimesheets_response.py +1 -1
  17. package/pins_data_model/models/model_listed_building.py +1 -1
  18. package/pins_data_model/models/model_listed_buildings.py +1 -1
  19. package/pins_data_model/models/model_nsip_document.py +1 -1
  20. package/pins_data_model/models/model_nsip_exam_timetable.py +1 -1
  21. package/pins_data_model/models/model_nsip_invoice.py +1 -1
  22. package/pins_data_model/models/model_nsip_meeting.py +1 -1
  23. package/pins_data_model/models/model_nsip_project.py +1 -1
  24. package/pins_data_model/models/model_nsip_project_update.py +1 -1
  25. package/pins_data_model/models/model_nsip_representation.py +1 -1
  26. package/pins_data_model/models/model_nsip_subscription.py +1 -1
  27. package/pins_data_model/models/model_pins_inspector.py +1 -1
  28. package/pins_data_model/models/model_pins_lpa.py +1 -1
  29. package/pins_data_model/models/model_s51_advice.py +1 -1
  30. package/pins_data_model/models/model_service_user.py +1 -1
  31. package/schemas/appeal-document.schema.json +2 -0
  32. package/schemas/appeal-s78.schema.json +48 -5
  33. package/schemas/commands/appellant-submission.schema.json +2 -1
  34. package/src/enums.cjs +3 -0
  35. package/src/enums.d.ts +3 -0
  36. package/src/enums.js +3 -0
  37. package/src/index.cjs +3 -0
  38. package/src/schemas.d.ts +40 -6
  39. package/tests/appeals/expedited-appeals.test.js +238 -0
package/src/enums.js CHANGED
@@ -96,6 +96,7 @@ export const APPEAL_CASE_PROCEDURE = Object.freeze({
96
96
  INQUIRY: 'inquiry',
97
97
  WRITTEN: 'written',
98
98
  WRITTEN_PART_1: 'writtenPart1',
99
+ WRITTEN_PART_2: 'writtenPart2',
99
100
  });
100
101
 
101
102
  export const APPEAL_CASE_STAGE = Object.freeze({
@@ -208,6 +209,7 @@ export const APPEAL_DOCUMENT_TYPE = Object.freeze({
208
209
  DEVELOPMENT_PLAN_POLICIES: 'developmentPlanPolicies',
209
210
  DISCONTINUANCE_NOTICE: 'discontinuanceNotice',
210
211
  EIA_ENVIRONMENTAL_STATEMENT: 'eiaEnvironmentalStatement',
212
+ EIA_ENVIRONMENTAL_STATEMENT_APPELLANT: 'eiaEnvironmentalStatementAppellant',
211
213
  EIA_SCOPING_OPINION: 'eiaScopingOpinion',
212
214
  EIA_SCREENING_DIRECTION: 'eiaScreeningDirection',
213
215
  EIA_SCREENING_OPINION: 'eiaScreeningOpinion',
@@ -251,6 +253,7 @@ export const APPEAL_DOCUMENT_TYPE = Object.freeze({
251
253
  OTHER_PARTY_REPRESENTATIONS: 'otherPartyRepresentations',
252
254
  OTHER_RELEVANT_MATTERS: 'otherRelevantMatters',
253
255
  OTHER_RELEVANT_POLICIES: 'otherRelevantPolicies',
256
+ OTHER_SUPPORTING_INFORMATION_APPELLANT: 'otherSupportingInformationAppellant',
254
257
  OWNERSHIP_CERTIFICATE: 'ownershipCertificate',
255
258
  PLAN_SHOWING_EXTENT_OF_ORDER: 'planShowingExtentOfOrder',
256
259
  PLANNING_CONTRAVENTION_NOTICE: 'planningContraventionNotice',
package/src/index.cjs CHANGED
@@ -179,6 +179,7 @@ const APPEAL_CASE_PROCEDURE = Object.freeze({
179
179
  INQUIRY: 'inquiry',
180
180
  WRITTEN: 'written',
181
181
  WRITTEN_PART_1: 'writtenPart1',
182
+ WRITTEN_PART_2: 'writtenPart2',
182
183
  });
183
184
 
184
185
  const APPEAL_CASE_STAGE = Object.freeze({
@@ -291,6 +292,7 @@ const APPEAL_DOCUMENT_TYPE = Object.freeze({
291
292
  DEVELOPMENT_PLAN_POLICIES: 'developmentPlanPolicies',
292
293
  DISCONTINUANCE_NOTICE: 'discontinuanceNotice',
293
294
  EIA_ENVIRONMENTAL_STATEMENT: 'eiaEnvironmentalStatement',
295
+ EIA_ENVIRONMENTAL_STATEMENT_APPELLANT: 'eiaEnvironmentalStatementAppellant',
294
296
  EIA_SCOPING_OPINION: 'eiaScopingOpinion',
295
297
  EIA_SCREENING_DIRECTION: 'eiaScreeningDirection',
296
298
  EIA_SCREENING_OPINION: 'eiaScreeningOpinion',
@@ -334,6 +336,7 @@ const APPEAL_DOCUMENT_TYPE = Object.freeze({
334
336
  OTHER_PARTY_REPRESENTATIONS: 'otherPartyRepresentations',
335
337
  OTHER_RELEVANT_MATTERS: 'otherRelevantMatters',
336
338
  OTHER_RELEVANT_POLICIES: 'otherRelevantPolicies',
339
+ OTHER_SUPPORTING_INFORMATION_APPELLANT: 'otherSupportingInformationAppellant',
337
340
  OWNERSHIP_CERTIFICATE: 'ownershipCertificate',
338
341
  PLAN_SHOWING_EXTENT_OF_ORDER: 'planShowingExtentOfOrder',
339
342
  PLANNING_CONTRAVENTION_NOTICE: 'planningContraventionNotice',
package/src/schemas.d.ts CHANGED
@@ -176,6 +176,8 @@ export interface AppealDocument {
176
176
  | 'groundJSupporting'
177
177
  | 'groundKSupporting'
178
178
  | 'discontinuanceNotice'
179
+ | 'otherSupportingInformationAppellant'
180
+ | 'eiaEnvironmentalStatementAppellant'
179
181
  | null;
180
182
  /**
181
183
  * The system mastering the metadata for the current document
@@ -973,7 +975,7 @@ export type AppealS78Case = (GridReference | SiteAddress) & {
973
975
  /**
974
976
  * The type of procedure for the appeal
975
977
  */
976
- caseProcedure: 'written' | 'hearing' | 'inquiry' | 'writtenPart1' | null;
978
+ caseProcedure: 'written' | 'hearing' | 'inquiry' | 'writtenPart1' | 'writtenPart2' | null;
977
979
  /**
978
980
  * A unique identifier for the Local Planning Authority
979
981
  */
@@ -1461,14 +1463,14 @@ export type AppealS78Case = (GridReference | SiteAddress) & {
1461
1463
  appellantStatementDueDate?: string | null;
1462
1464
  appellantStatementSubmittedDate: string | null;
1463
1465
  appellantProofsSubmittedDate: string | null;
1464
- finalCommentsDueDate: string | null;
1465
- interestedPartyRepsDueDate: string | null;
1466
+ finalCommentsDueDate?: string | null;
1467
+ interestedPartyRepsDueDate?: string | null;
1466
1468
  lpaCommentsSubmittedDate: string | null;
1467
1469
  lpaProofsSubmittedDate: string | null;
1468
1470
  lpaStatementSubmittedDate: string | null;
1469
- proofsOfEvidenceDueDate: string | null;
1471
+ proofsOfEvidenceDueDate?: string | null;
1470
1472
  siteNoticesSentDate: string | null;
1471
- statementDueDate: string | null;
1473
+ statementDueDate?: string | null;
1472
1474
  /**
1473
1475
  * The date of the Case Management Conference (CMC)
1474
1476
  */
@@ -1703,8 +1705,39 @@ export type AppealS78Case = (GridReference | SiteAddress) & {
1703
1705
  */
1704
1706
  discontinuanceNoticeServedOtherReason?: string | null;
1705
1707
  wasRetrospectivePlanningApplicationMade?: boolean | null;
1708
+ /**
1709
+ * Reason for appeal as provided by the appellant (Expedited Appeals)
1710
+ */
1711
+ reasonForAppealAppellant?: string | null;
1712
+ /**
1713
+ * Indicates if the screening opinion specifies that an EIA is required (Expedited Appeals)
1714
+ */
1715
+ screeningOpinionIndicatesEiaRequired?: boolean | null;
1716
+ /**
1717
+ * Significant changes affecting the application as reported by the appellant (Expedited Appeals)
1718
+ */
1719
+ significantChangesAffectingApplicationAppellant?:
1720
+ | {
1721
+ value: SignificantChangesAffectingApplication;
1722
+ comment?: string | null;
1723
+ }[]
1724
+ | null;
1725
+ /**
1726
+ * Significant changes affecting the application as reported by the LPA (Expedited Appeals)
1727
+ */
1728
+ significantChangesAffectingApplicationLpa?:
1729
+ | {
1730
+ value: SignificantChangesAffectingApplication;
1731
+ comment?: string | null;
1732
+ }[]
1733
+ | null;
1706
1734
  [k: string]: unknown;
1707
1735
  };
1736
+ export type SignificantChangesAffectingApplication =
1737
+ | 'adopted-a-new-local-plan'
1738
+ | 'national-policy-change'
1739
+ | 'court-judgement'
1740
+ | 'other';
1708
1741
 
1709
1742
  export interface GridReference {
1710
1743
  siteGridReferenceEasting: string;
@@ -3889,7 +3922,8 @@ export interface AppellantSubmissionCommand {
3889
3922
  | 'groundKSupporting'
3890
3923
  | 'groundAFeeReceipt'
3891
3924
  | null
3892
- | 'environmentalAssessment';
3925
+ | 'environmentalAssessment'
3926
+ | 'eiaEnvironmentalStatement';
3893
3927
  [k: string]: unknown;
3894
3928
  }[];
3895
3929
  /**
@@ -0,0 +1,238 @@
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 = 'appeal-s78.schema.json';
8
+
9
+ const expeditedAppealPayload = {
10
+ caseId: 1001,
11
+ caseReference: '7000001',
12
+ submissionId: '7f45748c-2342-4b59-b934-ad6183e8d834',
13
+ caseStatus: 'validation',
14
+ caseType: 'S',
15
+ caseProcedure: 'writtenPart1', // New procedure type
16
+ lpaCode: 'Q9999',
17
+ caseOfficerId: 'officer-1',
18
+ inspectorId: 'inspector-1',
19
+ allocationLevel: 'A',
20
+ allocationBand: 1,
21
+ caseSpecialisms: ['Access'],
22
+ caseSubmittedDate: '2024-01-01T12:00:00.000Z',
23
+ caseCreatedDate: '2024-01-01T12:00:00.000Z',
24
+ caseUpdatedDate: '2024-01-01T12:00:00.000Z',
25
+ caseValidDate: '2024-01-02T12:00:00.000Z',
26
+ caseValidationDate: '2024-01-02T12:00:00.000Z',
27
+ caseValidationOutcome: 'valid',
28
+ caseValidationInvalidDetails: null,
29
+ caseValidationIncompleteDetails: null,
30
+ caseExtensionDate: null,
31
+ caseStartedDate: '2024-01-03T12:00:00.000Z',
32
+ casePublishedDate: null,
33
+ linkedCaseStatus: null,
34
+ leadCaseReference: null,
35
+ lpaQuestionnaireDueDate: '2024-01-10T12:00:00.000Z',
36
+ lpaQuestionnaireSubmittedDate: '2024-01-09T12:00:00.000Z',
37
+ lpaQuestionnaireCreatedDate: '2024-01-09T12:00:00.000Z',
38
+ lpaQuestionnairePublishedDate: '2024-01-10T12:00:00.000Z',
39
+ lpaQuestionnaireValidationOutcome: 'complete',
40
+ lpaQuestionnaireValidationOutcomeDate: '2024-01-10T12:00:00.000Z',
41
+ lpaQuestionnaireValidationDetails: null,
42
+ lpaStatement: 'LPA Statement content',
43
+ caseWithdrawnDate: null,
44
+ caseTransferredDate: null,
45
+ transferredCaseClosedDate: null,
46
+ caseDecisionOutcomeDate: null,
47
+ caseDecisionPublishedDate: null,
48
+ caseDecisionOutcome: null,
49
+ caseCompletedDate: null,
50
+ enforcementNotice: false,
51
+ applicationReference: 'APP/2024/0001',
52
+ applicationDate: '2023-12-01T12:00:00.000Z',
53
+ applicationDecision: 'refused',
54
+ applicationDecisionDate: '2023-12-20T12:00:00.000Z',
55
+ caseSubmissionDueDate: '2024-06-20T12:00:00.000Z',
56
+ siteAddressLine1: '123 Test Street',
57
+ siteAddressTown: 'Test Town',
58
+ siteAddressPostcode: 'TE1 1ST',
59
+ siteAccessDetails: ['Standard access'],
60
+ siteSafetyDetails: null,
61
+ siteAreaSquareMetres: 100,
62
+ floorSpaceSquareMetres: 50,
63
+ isCorrectAppealType: true,
64
+ isGreenBelt: false,
65
+ inConservationArea: false,
66
+ ownsAllLand: true,
67
+ ownsSomeLand: false,
68
+ knowsOtherOwners: 'No',
69
+ knowsAllOwners: 'Yes',
70
+ advertisedAppeal: false,
71
+ notificationMethod: ['notice'],
72
+ ownersInformed: true,
73
+ originalDevelopmentDescription: 'Description of development',
74
+ changedDevelopmentDescription: false,
75
+ newConditionDetails: null,
76
+ nearbyCaseReferences: [],
77
+ neighbouringSiteAddresses: [],
78
+ affectedListedBuildingNumbers: [],
79
+ changedListedBuildingNumbers: [],
80
+ appellantCostsAppliedFor: false,
81
+ lpaCostsAppliedFor: false,
82
+ agriculturalHolding: false,
83
+ tenantAgriculturalHolding: false,
84
+ otherTenantsAgriculturalHolding: false,
85
+ informedTenantsAgriculturalHolding: false,
86
+ appellantProcedurePreference: 'written',
87
+ appellantProcedurePreferenceDetails: null,
88
+ appellantProcedurePreferenceDuration: null,
89
+ appellantProcedurePreferenceWitnessCount: null,
90
+ statusPlanningObligation: 'Finalised',
91
+ affectsScheduledMonument: false,
92
+ hasProtectedSpecies: false,
93
+ isAonbNationalLandscape: false,
94
+ designatedSitesNames: [],
95
+ isGypsyOrTravellerSite: false,
96
+ isPublicRightOfWay: false,
97
+ eiaEnvironmentalImpactSchedule: 'schedule-2',
98
+ eiaDevelopmentDescription: 'infrastructure-projects',
99
+ eiaSensitiveAreaDetails: null,
100
+ eiaColumnTwoThreshold: false,
101
+ eiaScreeningOpinion: false,
102
+ eiaRequiresEnvironmentalStatement: false,
103
+ eiaCompletedEnvironmentalStatement: false,
104
+ hasStatutoryConsultees: false,
105
+ consultedBodiesDetails: null,
106
+ hasInfrastructureLevy: false,
107
+ isInfrastructureLevyFormallyAdopted: false,
108
+ infrastructureLevyAdoptedDate: null,
109
+ infrastructureLevyExpectedDate: null,
110
+ lpaProcedurePreference: 'written',
111
+ lpaProcedurePreferenceDetails: null,
112
+ lpaProcedurePreferenceDuration: null,
113
+ caseworkReason: null,
114
+ developmentType: 'minor-dwellings',
115
+ importantInformation: null,
116
+ jurisdiction: 'England',
117
+ redeterminedIndicator: 'No',
118
+ dateCostsReportDespatched: null,
119
+ dateNotRecoveredOrDerecovered: null,
120
+ dateRecovered: null,
121
+ originalCaseDecisionDate: null,
122
+ targetDate: '2024-04-01T12:00:00.000Z',
123
+ reasonForAppealAppellant: 'This is an expedited appeal because...', // New attribute
124
+ screeningOpinionIndicatesEiaRequired: false, // New attribute
125
+ significantChangesAffectingApplicationAppellant: [
126
+ // New multi-select
127
+ { value: 'adopted-a-new-local-plan', comment: 'Plan adopted last month' },
128
+ { value: 'other', comment: 'General policy shift' }
129
+ ],
130
+ significantChangesAffectingApplicationLpa: null, // Optional
131
+ appellantCommentsSubmittedDate: null,
132
+ appellantStatementSubmittedDate: null,
133
+ appellantProofsSubmittedDate: null,
134
+ lpaCommentsSubmittedDate: '2024-01-15T12:00:00.000Z',
135
+ lpaProofsSubmittedDate: '2024-01-15T12:00:00.000Z',
136
+ lpaStatementSubmittedDate: '2024-01-15T12:00:00.000Z',
137
+ siteNoticesSentDate: '2024-01-05T12:00:00.000Z',
138
+ reasonForNeighbourVisits: 'To check impact on privacy',
139
+ numberOfResidencesNetChange: 1,
140
+ siteGridReferenceEasting: '357144',
141
+ siteGridReferenceNorthing: '400534',
142
+ siteViewableFromRoad: true,
143
+ siteWithinSSSI: false,
144
+ typeOfPlanningApplication: 'full-appeal'
145
+ // statementDueDate, interestedPartyRepsDueDate, finalCommentsDueDate, proofsOfEvidenceDueDate omitted (now optional)
146
+ };
147
+
148
+ describe('Expedited S78 Appeals', () => {
149
+ const schemas = loadAllSchemasSync();
150
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
151
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
152
+ addFormats(ajv);
153
+
154
+ it('should allow valid expedited appeal payload with new attributes and missing optional due dates', () => {
155
+ const test = structuredClone(expeditedAppealPayload);
156
+ const validationResult = ajv.validate(schema, test);
157
+ if (!validationResult) {
158
+ console.error(ajv.errors);
159
+ }
160
+ assert.strictEqual(validationResult, true);
161
+ });
162
+
163
+ it('should allow writtenPart2 procedure type', () => {
164
+ const test = structuredClone(expeditedAppealPayload);
165
+ test.caseProcedure = 'writtenPart2';
166
+ const validationResult = ajv.validate(schema, test);
167
+ if (!validationResult) {
168
+ console.error(ajv.errors);
169
+ }
170
+ assert.strictEqual(validationResult, true);
171
+ });
172
+
173
+ it('should allow null for new attributes', () => {
174
+ const test = structuredClone(expeditedAppealPayload);
175
+ test.reasonForAppealAppellant = null;
176
+ test.screeningOpinionIndicatesEiaRequired = null;
177
+ test.significantChangesAffectingApplicationAppellant = null;
178
+ test.significantChangesAffectingApplicationLpa = null;
179
+ const validationResult = ajv.validate(schema, test);
180
+ if (!validationResult) {
181
+ console.error(ajv.errors);
182
+ }
183
+ assert.strictEqual(validationResult, true);
184
+ });
185
+
186
+ it('should enforce maxLength for reasonForAppealAppellant', () => {
187
+ const test = structuredClone(expeditedAppealPayload);
188
+ test.reasonForAppealAppellant = 'a'.repeat(251);
189
+ const validationResult = ajv.validate(schema, test);
190
+ assert.strictEqual(validationResult, false);
191
+ });
192
+
193
+ it('should enforce enum for significantChangesAffectingApplication', () => {
194
+ const test = structuredClone(expeditedAppealPayload);
195
+ test.significantChangesAffectingApplicationAppellant = [{ value: 'invalid-enum-value', comment: 'some comment' }];
196
+ const validationResult = ajv.validate(schema, test);
197
+ assert.strictEqual(validationResult, false);
198
+ });
199
+
200
+ it('should allow new document types in appeal-document schema', () => {
201
+ const docSchema = 'appeal-document.schema.json';
202
+ const testDoc = {
203
+ documentId: 'doc-1',
204
+ caseId: 1001,
205
+ caseReference: '7000001',
206
+ version: 1,
207
+ filename: 'test.pdf',
208
+ originalFilename: 'test.pdf',
209
+ size: 1024,
210
+ mime: 'application/pdf',
211
+ documentURI: 'http://example.com/test.pdf',
212
+ publishedDocumentURI: null,
213
+ virusCheckStatus: 'scanned',
214
+ fileMD5: 'hash',
215
+ dateCreated: '2024-01-01T12:00:00.000Z',
216
+ dateReceived: '2024-01-01T12:00:00.000Z',
217
+ datePublished: null,
218
+ lastModified: '2024-01-01T12:00:00.000Z',
219
+ caseType: 'S',
220
+ redactedStatus: 'not_redacted',
221
+ documentType: 'otherSupportingInformationAppellant', // New type
222
+ sourceSystem: 'back-office-appeals',
223
+ origin: 'citizen',
224
+ owner: 'appellant',
225
+ author: 'appellant',
226
+ description: 'Other supporting info',
227
+ caseStage: 'appellant-case',
228
+ horizonFolderId: null
229
+ };
230
+
231
+ const validationResult1 = ajv.validate(docSchema, testDoc);
232
+ assert.strictEqual(validationResult1, true);
233
+
234
+ testDoc.documentType = 'eiaEnvironmentalStatementAppellant'; // New type
235
+ const validationResult2 = ajv.validate(docSchema, testDoc);
236
+ assert.strictEqual(validationResult2, true);
237
+ });
238
+ });