@planning-inspectorate/data-model 1.16.1 → 1.17.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/src/schemas.d.ts CHANGED
@@ -301,9 +301,29 @@ export interface AppealEvent {
301
301
  }
302
302
 
303
303
  /**
304
- * Schema defining the metadata for an appeal
304
+ * Group A - Schema defining the metadata for an appeal
305
305
  */
306
- export interface AppealHASCase {
306
+ export type AppealHASCase = (
307
+ | {
308
+ siteGridReferenceEasting: {
309
+ [k: string]: unknown;
310
+ };
311
+ siteGridReferenceNorthing: {
312
+ [k: string]: unknown;
313
+ };
314
+ }
315
+ | {
316
+ siteAddressLine1: {
317
+ [k: string]: unknown;
318
+ };
319
+ siteAddressTown: {
320
+ [k: string]: unknown;
321
+ };
322
+ siteAddressPostcode: {
323
+ [k: string]: unknown;
324
+ };
325
+ }
326
+ ) & {
307
327
  /**
308
328
  * Internal case identifier
309
329
  */
@@ -504,23 +524,23 @@ export interface AppealHASCase {
504
524
  /**
505
525
  * First line of address for the appeal site
506
526
  */
507
- siteAddressLine1: string;
527
+ siteAddressLine1?: string | null;
508
528
  /**
509
529
  * Second line of address for the appeal site
510
530
  */
511
- siteAddressLine2: string | null;
531
+ siteAddressLine2?: string | null;
512
532
  /**
513
533
  * Town / City of the site address
514
534
  */
515
- siteAddressTown: string;
535
+ siteAddressTown?: string | null;
516
536
  /**
517
537
  * County of the site address
518
538
  */
519
- siteAddressCounty: string | null;
539
+ siteAddressCounty?: string | null;
520
540
  /**
521
541
  * Postal code of the site address
522
542
  */
523
- siteAddressPostcode: string;
543
+ siteAddressPostcode?: string | null;
524
544
  /**
525
545
  * Provided information on site accessibility
526
546
  */
@@ -549,6 +569,90 @@ export interface AppealHASCase {
549
569
  * Indicates if the site is in a conservation area
550
570
  */
551
571
  inConservationArea: boolean | null;
572
+ /**
573
+ * Indicates if the development affects a scheduled monument
574
+ */
575
+ affectsScheduledMonument?: boolean | null;
576
+ /**
577
+ * Indicates if the site hosts protected species
578
+ */
579
+ hasProtectedSpecies?: boolean | null;
580
+ /**
581
+ * Indicates an area of outstanding beauty (National Landscape)
582
+ */
583
+ isAonbNationalLandscape?: boolean | null;
584
+ /**
585
+ * Indicates if site is on highway land
586
+ */
587
+ isSiteOnHighwayLand?: boolean | null;
588
+ /**
589
+ * The designated site names
590
+ */
591
+ designatedSitesNames?: string[] | null;
592
+ /**
593
+ * Indicates the existence of an infrastructure levy
594
+ */
595
+ hasInfrastructureLevy?: boolean | null;
596
+ /**
597
+ * Indicates if the infrastructure levy is formally adopted
598
+ */
599
+ isInfrastructureLevyFormallyAdopted?: boolean | null;
600
+ /**
601
+ * The date of the infrastructure levy adoption
602
+ */
603
+ infrastructureLevyAdoptedDate?: string | null;
604
+ /**
605
+ * The expected date of the infrastructure levy
606
+ */
607
+ infrastructureLevyExpectedDate?: string | null;
608
+ /**
609
+ * The procedure preference indicated by the LPA
610
+ */
611
+ lpaProcedurePreference?: 'written' | 'hearing' | 'inquiry' | null;
612
+ /**
613
+ * The procedure details preference indicated by the LPA
614
+ */
615
+ lpaProcedurePreferenceDetails?: string | null;
616
+ /**
617
+ * The duration of enquiry indicated by the LPA
618
+ */
619
+ lpaProcedurePreferenceDuration?: number | null;
620
+ /**
621
+ * TBC
622
+ */
623
+ caseworkReason?: string | null;
624
+ /**
625
+ * Information relevant to case...
626
+ */
627
+ importantInformation?: string | null;
628
+ /**
629
+ * Under whose jurisdiction the decision falls
630
+ */
631
+ jurisdiction?: string | null;
632
+ /**
633
+ * Has the case been selected for redetermination
634
+ */
635
+ redeterminedIndicator?: boolean | null;
636
+ /**
637
+ * The date that the costs report was sent
638
+ */
639
+ dateCostsReportDespatched?: string | null;
640
+ /**
641
+ * The date that is TBC
642
+ */
643
+ dateNotRecoveredOrDerecovered?: string | null;
644
+ /**
645
+ * The date that case was recovered
646
+ */
647
+ dateRecovered?: string | null;
648
+ /**
649
+ * The original case decision date
650
+ */
651
+ originalCaseDecisionDate?: string | null;
652
+ /**
653
+ * The target date for case completion
654
+ */
655
+ targetDate?: string | null;
552
656
  /**
553
657
  * Indicates if the appellant has complete ownership of the site
554
658
  */
@@ -656,8 +760,31 @@ export interface AppealHASCase {
656
760
  | 'minor-commercial-development'
657
761
  | 'advertisement'
658
762
  | null;
763
+ siteGridReferenceEasting?: string | null;
764
+ siteGridReferenceNorthing?: string | null;
765
+ /**
766
+ * Indicates if the appellant has land owners permission
767
+ */
768
+ hasLandownersPermission?: boolean | null;
769
+ /**
770
+ * Indicates if the advert is already in position
771
+ */
772
+ isAdvertInPosition?: boolean | null;
773
+ /**
774
+ * Indicates if the site is in an area of special control for adverts
775
+ */
776
+ isSiteInAreaOfSpecialControlAdverts?: boolean | null;
777
+ /**
778
+ * Indicates if the application was refused because of highway or traffic public safety?
779
+ */
780
+ wasApplicationRefusedDueToHighwayOrTraffic?: boolean | null;
781
+ /**
782
+ * Indicates if the appellant submitted complete and accurate photographs and plans
783
+ */
784
+ didAppellantSubmitCompletePhotosAndPlans?: boolean | null;
785
+ advertType?: string | null;
659
786
  [k: string]: unknown;
660
- }
787
+ };
661
788
 
662
789
  /**
663
790
  * Schema defining the metadata for appeal representations, such as statements, third-party comments and final comments
@@ -746,9 +873,29 @@ export interface AppealRepresentation {
746
873
  }
747
874
 
748
875
  /**
749
- * Schema defining the metadata for an appeal (S78)
876
+ * Group B - Schema defining the metadata for an appeal
750
877
  */
751
- export interface AppealS78Case {
878
+ export type AppealS78Case = (
879
+ | {
880
+ siteGridReferenceEasting: {
881
+ [k: string]: unknown;
882
+ };
883
+ siteGridReferenceNorthing: {
884
+ [k: string]: unknown;
885
+ };
886
+ }
887
+ | {
888
+ siteAddressLine1: {
889
+ [k: string]: unknown;
890
+ };
891
+ siteAddressTown: {
892
+ [k: string]: unknown;
893
+ };
894
+ siteAddressPostcode: {
895
+ [k: string]: unknown;
896
+ };
897
+ }
898
+ ) & {
752
899
  /**
753
900
  * Internal case identifier
754
901
  */
@@ -953,23 +1100,23 @@ export interface AppealS78Case {
953
1100
  /**
954
1101
  * First line of address for the appeal site
955
1102
  */
956
- siteAddressLine1: string;
1103
+ siteAddressLine1?: string | null;
957
1104
  /**
958
1105
  * Second line of address for the appeal site
959
1106
  */
960
- siteAddressLine2: string | null;
1107
+ siteAddressLine2?: string | null;
961
1108
  /**
962
1109
  * Town / City of the site address
963
1110
  */
964
- siteAddressTown: string;
1111
+ siteAddressTown?: string | null;
965
1112
  /**
966
1113
  * County of the site address
967
1114
  */
968
- siteAddressCounty: string | null;
1115
+ siteAddressCounty?: string | null;
969
1116
  /**
970
1117
  * Postal code of the site address
971
1118
  */
972
- siteAddressPostcode: string;
1119
+ siteAddressPostcode?: string | null;
973
1120
  /**
974
1121
  * Provided information on site accessibility
975
1122
  */
@@ -1271,8 +1418,8 @@ export interface AppealS78Case {
1271
1418
  * The net gain in residences of the proposed development
1272
1419
  */
1273
1420
  numberOfResidencesNetChange: number | null;
1274
- siteGridReferenceEasting: string | null;
1275
- siteGridReferenceNorthing: string | null;
1421
+ siteGridReferenceEasting?: string | null;
1422
+ siteGridReferenceNorthing?: string | null;
1276
1423
  siteViewableFromRoad: boolean | null;
1277
1424
  siteWithinSSSI: boolean | null;
1278
1425
  typeOfPlanningApplication:
@@ -1306,7 +1453,7 @@ export interface AppealS78Case {
1306
1453
  statementOfCommonGroundDueDate?: string | null;
1307
1454
  planningObligationDueDate?: string | null;
1308
1455
  [k: string]: unknown;
1309
- }
1456
+ };
1310
1457
 
1311
1458
  /**
1312
1459
  * Schema defining the data returned by the DaRT API
@@ -0,0 +1,243 @@
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-has.schema.json';
8
+
9
+ const appealHas = {
10
+ caseId: null,
11
+ caseReference: 'mollit dolore dolor non culpa',
12
+ submissionId: null,
13
+ caseStatus: 'ready_to_start',
14
+ caseType: 'ZP',
15
+ caseProcedure: 'inquiry',
16
+ lpaCode: 'nulla',
17
+ caseOfficerId: null,
18
+ inspectorId: 'mollit in ullamco Excepteur commodo',
19
+ allocationLevel: 'D',
20
+ allocationBand: 1,
21
+ caseSpecialisms: ['et culpa elit labore', 'minim Lorem'],
22
+ caseSubmittedDate: '1954-03-03T08:10:46.0Z',
23
+ caseCreatedDate: '1945-04-11T07:49:09.0Z',
24
+ caseUpdatedDate: '1943-02-10T21:16:10.0Z',
25
+ caseValidDate: '1902-04-09T20:34:25.0Z',
26
+ caseValidationDate: null,
27
+ caseValidationOutcome: 'invalid',
28
+ caseValidationInvalidDetails: null,
29
+ caseValidationIncompleteDetails: [
30
+ 'occaecat dolor labore ut ipsum',
31
+ 'eiusmod ullamco pariatur aliqua esse',
32
+ 'eiusmod Ut officia',
33
+ 'aute',
34
+ 'do minim sint labore aliquip'
35
+ ],
36
+ caseExtensionDate: null,
37
+ caseStartedDate: null,
38
+ casePublishedDate: null,
39
+ linkedCaseStatus: null,
40
+ leadCaseReference: null,
41
+ lpaQuestionnaireDueDate: null,
42
+ lpaQuestionnaireSubmittedDate: null,
43
+ lpaQuestionnaireCreatedDate: '1916-11-15T14:22:37.0Z',
44
+ lpaQuestionnairePublishedDate: '1895-10-01T01:14:29.0Z',
45
+ lpaQuestionnaireValidationOutcome: 'incomplete',
46
+ lpaQuestionnaireValidationOutcomeDate: '1927-05-30T20:34:18.0Z',
47
+ lpaQuestionnaireValidationDetails: ['dolore cillum in anim adipisicing'],
48
+ lpaStatement: 'Ut ipsum et',
49
+ caseWithdrawnDate: '1926-02-11T13:40:53.0Z',
50
+ caseTransferredDate: '1923-01-28T02:09:32.0Z',
51
+ transferredCaseClosedDate: '1893-02-13T14:37:27.0Z',
52
+ caseDecisionOutcomeDate: '1920-07-08T09:20:22.0Z',
53
+ caseDecisionPublishedDate: null,
54
+ caseDecisionOutcome: 'dismissed',
55
+ caseCompletedDate: '1895-12-07T15:44:32.0Z',
56
+ enforcementNotice: null,
57
+ applicationReference: 'eu',
58
+ applicationDate: '1949-08-25T15:11:02.0Z',
59
+ applicationDecision: 'refused',
60
+ applicationDecisionDate: '1952-08-09T01:03:51.0Z',
61
+ caseSubmissionDueDate: '1942-02-09T21:03:07.0Z',
62
+ siteAddressLine1: 'ullamco',
63
+ siteAddressLine2: null,
64
+ siteAddressTown: 'dolor reprehenderit ut',
65
+ siteAddressCounty: 'dolore eu exercitation enim',
66
+ siteAddressPostcode: 'est',
67
+ siteAccessDetails: [
68
+ 'Ut',
69
+ 'eiusmod magna aliqua ipsum',
70
+ 'sit non ut amet aliqua',
71
+ 'id consectetur cupidatat',
72
+ 'incididunt ut Lorem eu'
73
+ ],
74
+ siteSafetyDetails: null,
75
+ siteAreaSquareMetres: 74995933.47978675,
76
+ floorSpaceSquareMetres: null,
77
+ isCorrectAppealType: null,
78
+ isGreenBelt: true,
79
+ inConservationArea: null,
80
+ affectsScheduledMonument: true,
81
+ hasProtectedSpecies: true,
82
+ isAonbNationalLandscape: null,
83
+ isSiteOnHighwayLand: null,
84
+ designatedSitesNames: null,
85
+ hasInfrastructureLevy: false,
86
+ isInfrastructureLevyFormallyAdopted: false,
87
+ infrastructureLevyAdoptedDate: null,
88
+ infrastructureLevyExpectedDate: null,
89
+ lpaProcedurePreference: null,
90
+ lpaProcedurePreferenceDetails: null,
91
+ lpaProcedurePreferenceDuration: -42778389.82228377,
92
+ caseworkReason: 'elit reprehenderit',
93
+ importantInformation: null,
94
+ jurisdiction: null,
95
+ redeterminedIndicator: null,
96
+ dateCostsReportDespatched: '1918-01-30T08:43:51.0Z',
97
+ dateNotRecoveredOrDerecovered: '1950-02-18T19:15:57.0Z',
98
+ dateRecovered: '1962-01-16T13:45:38.0Z',
99
+ originalCaseDecisionDate: null,
100
+ targetDate: null,
101
+ ownsAllLand: true,
102
+ ownsSomeLand: null,
103
+ knowsOtherOwners: 'Some',
104
+ knowsAllOwners: null,
105
+ advertisedAppeal: null,
106
+ notificationMethod: null,
107
+ ownersInformed: null,
108
+ originalDevelopmentDescription: null,
109
+ changedDevelopmentDescription: null,
110
+ newConditionDetails: 'tempor anim adipisicing',
111
+ nearbyCaseReferences: null,
112
+ neighbouringSiteAddresses: [
113
+ {
114
+ neighbouringSiteAddressLine1: 'eu veniam',
115
+ neighbouringSiteAddressLine2: null,
116
+ neighbouringSiteAddressTown: 'pariatur dolore',
117
+ neighbouringSiteAddressCounty: 'fugiat minim anim aliqua aliquip',
118
+ neighbouringSiteAddressPostcode: 'ex anim',
119
+ neighbouringSiteAccessDetails: null,
120
+ neighbouringSiteSafetyDetails: 'veniam in sunt'
121
+ },
122
+ {
123
+ neighbouringSiteAddressLine1: 'et adipisicing dolore',
124
+ neighbouringSiteAddressLine2: null,
125
+ neighbouringSiteAddressTown: 'cupidatat dolore',
126
+ neighbouringSiteAddressCounty: null,
127
+ neighbouringSiteAddressPostcode: 'cupidatat voluptate esse ad amet',
128
+ neighbouringSiteAccessDetails: 'labore',
129
+ neighbouringSiteSafetyDetails: 'deserunt fugiat'
130
+ },
131
+ {
132
+ neighbouringSiteAddressLine1: 'esse in pariatur mollit',
133
+ neighbouringSiteAddressLine2: 'ad aliquip in',
134
+ neighbouringSiteAddressTown: 'ullamco',
135
+ neighbouringSiteAddressCounty: 'ut aute',
136
+ neighbouringSiteAddressPostcode: 'Lorem nostrud ipsum aliquip',
137
+ neighbouringSiteAccessDetails: 'dolore irure nisi',
138
+ neighbouringSiteSafetyDetails: 'ex'
139
+ },
140
+ {
141
+ neighbouringSiteAddressLine1: 'est pariatur Lorem minim in',
142
+ neighbouringSiteAddressLine2: 'Ut',
143
+ neighbouringSiteAddressTown: 'eiusmod minim nostrud Excepteur nulla',
144
+ neighbouringSiteAddressCounty: 'officia',
145
+ neighbouringSiteAddressPostcode: 'non deserunt',
146
+ neighbouringSiteAccessDetails: null,
147
+ neighbouringSiteSafetyDetails: 'culpa occaecat'
148
+ },
149
+ {
150
+ neighbouringSiteAddressLine1: 'minim cillum eu tempor et',
151
+ neighbouringSiteAddressLine2: 'non adipisicing nisi in dolore',
152
+ neighbouringSiteAddressTown: 'irure in',
153
+ neighbouringSiteAddressCounty: null,
154
+ neighbouringSiteAddressPostcode: 'commodo in et Excepteur',
155
+ neighbouringSiteAccessDetails: null,
156
+ neighbouringSiteSafetyDetails: 'ex pariatur enim ipsum voluptate'
157
+ }
158
+ ],
159
+ reasonForNeighbourVisits: null,
160
+ affectedListedBuildingNumbers: ['Excepteur', 'in consequat sit', 'esse sunt velit culpa', 'voluptate esse'],
161
+ appellantCostsAppliedFor: null,
162
+ lpaCostsAppliedFor: null,
163
+ typeOfPlanningApplication: 'householder-planning',
164
+ siteGridReferenceEasting: '357144',
165
+ siteGridReferenceNorthing: '400534',
166
+ hasLandownersPermission: null,
167
+ isAdvertInPosition: false,
168
+ isSiteInAreaOfSpecialControlAdverts: false,
169
+ wasApplicationRefusedDueToHighwayOrTraffic: null,
170
+ didAppellantSubmitCompletePhotosAndPlans: null,
171
+ advertType: null
172
+ };
173
+
174
+ describe(schema, () => {
175
+ const schemas = loadAllSchemasSync();
176
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
177
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
178
+ addFormats(ajv);
179
+
180
+ it('should allow valid schema', () => {
181
+ const test = structuredClone(appealHas);
182
+
183
+ const validationResult = ajv.validate(schema, test);
184
+ assert.strictEqual(validationResult, true);
185
+ });
186
+
187
+ it('should enforce eastings-northings pattern', () => {
188
+ const invalidPatterns = [
189
+ { easting: 'abc123', northing: '400534', description: 'easting with letters' },
190
+ { easting: '1234567', northing: '400534', description: 'easting too long' },
191
+ { easting: '12345', northing: '400534', description: 'easting too short' },
192
+ { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
193
+ { easting: '357144', northing: '1234567', description: 'northing too long' },
194
+ { easting: '357144', northing: '12345', description: 'northing too short' },
195
+ { easting: '', northing: '400534', description: 'empty easting' },
196
+ { easting: '357144', northing: '', description: 'empty northing' }
197
+ ];
198
+
199
+ for (const pattern of invalidPatterns) {
200
+ const test = structuredClone(appealHas);
201
+ test.siteGridReferenceEasting = pattern.easting;
202
+ test.siteGridReferenceNorthing = pattern.northing;
203
+
204
+ const validationResult = ajv.validate(schema, test);
205
+ assert.strictEqual(validationResult, false, `Expected validation to fail for ${pattern.description}`);
206
+ }
207
+ });
208
+
209
+ it('should allow only eastings-northings', () => {
210
+ const test = structuredClone(appealHas);
211
+ test.siteAddressPostcode = undefined;
212
+ test.siteAddressLine1 = null;
213
+ test.siteAddressTown = null;
214
+ const validationResult = ajv.validate(schema, test);
215
+ assert.strictEqual(validationResult, true);
216
+ });
217
+
218
+ it('should allow only site address', () => {
219
+ const test = structuredClone(appealHas);
220
+ test.siteGridReferenceEasting = undefined;
221
+ test.siteGridReferenceNorthing = null;
222
+ const validationResult = ajv.validate(schema, test);
223
+ assert.strictEqual(validationResult, true);
224
+ });
225
+
226
+ it('should enforce either site address or easting-northing', () => {
227
+ const test = structuredClone(appealHas);
228
+ test.siteAddressPostcode = undefined;
229
+ test.siteAddressLine1 = null;
230
+ test.siteAddressTown = null;
231
+ test.siteGridReferenceEasting = undefined;
232
+ test.siteGridReferenceNorthing = null;
233
+ const validationResult = ajv.validate(schema, test);
234
+ assert.strictEqual(validationResult, false);
235
+ });
236
+
237
+ it('should allow additional props', () => {
238
+ const test = structuredClone(appealHas);
239
+ test.test = 1; // additional unknown prop allowed
240
+ const validationResult = ajv.validate(schema, test);
241
+ assert.strictEqual(validationResult, true);
242
+ });
243
+ });