@planning-inspectorate/data-model 2.13.0 → 2.15.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 (36) hide show
  1. package/docs/nsip-document.md +9 -5
  2. package/package.json +1 -1
  3. package/pins_data_model/models/model_appeal_document.py +1 -1
  4. package/pins_data_model/models/model_appeal_event.py +1 -1
  5. package/pins_data_model/models/model_appeal_event_estimate.py +1 -1
  6. package/pins_data_model/models/model_appeal_has.py +1 -1
  7. package/pins_data_model/models/model_appeal_representation.py +1 -1
  8. package/pins_data_model/models/model_appeal_s78.py +1 -1
  9. package/pins_data_model/models/model_dart_response.py +1 -1
  10. package/pins_data_model/models/model_entraid.py +1 -1
  11. package/pins_data_model/models/model_folder.py +1 -1
  12. package/pins_data_model/models/model_gettimesheets_response.py +1 -1
  13. package/pins_data_model/models/model_listed_building.py +1 -1
  14. package/pins_data_model/models/model_listed_buildings.py +1 -1
  15. package/pins_data_model/models/model_nsip_document.py +5 -1
  16. package/pins_data_model/models/model_nsip_exam_timetable.py +1 -1
  17. package/pins_data_model/models/model_nsip_invoice.py +1 -1
  18. package/pins_data_model/models/model_nsip_meeting.py +1 -1
  19. package/pins_data_model/models/model_nsip_project.py +1 -1
  20. package/pins_data_model/models/model_nsip_project_update.py +1 -1
  21. package/pins_data_model/models/model_nsip_representation.py +1 -1
  22. package/pins_data_model/models/model_nsip_subscription.py +1 -1
  23. package/pins_data_model/models/model_pins_inspector.py +1 -1
  24. package/pins_data_model/models/model_pins_lpa.py +1 -1
  25. package/pins_data_model/models/model_s51_advice.py +1 -1
  26. package/pins_data_model/models/model_service_user.py +1 -1
  27. package/schemas/commands/appellant-submission.schema.json +8 -0
  28. package/schemas/commands/lpa-questionnaire.schema.json +2 -2
  29. package/schemas/nsip-document.schema.json +9 -1
  30. package/src/enums.cjs +4 -0
  31. package/src/enums.d.ts +4 -0
  32. package/src/enums.js +4 -0
  33. package/src/index.cjs +4 -0
  34. package/src/schemas.d.ts +14 -3
  35. package/tests/appeals/commands/enforcement-listed-questionnaire.test.js +109 -0
  36. package/tests/appeals/commands/enforcement-listed-submission.test.js +187 -0
@@ -516,11 +516,15 @@ The location of the published document. Only applicable to documents which are p
516
516
 
517
517
  **enum**: the value of this property must be equal to one of the following values:
518
518
 
519
- | Value | Explanation |
520
- | :--------------- | :---------- |
521
- | `"not_redacted"` | |
522
- | `"redacted"` | |
523
- | `null` | |
519
+ | Value | Explanation |
520
+ | :------------------------------- | :---------- |
521
+ | `"not_redacted"` | |
522
+ | `"redacted"` | |
523
+ | `"awaiting_ai_redaction"` | |
524
+ | `"ai_redaction_review_required"` | |
525
+ | `"ai_redaction_failed"` | |
526
+ | `"no_redaction_required"` | |
527
+ | `null` | |
524
528
 
525
529
  ## publishedStatus
526
530
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planning-inspectorate/data-model",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "JSON Schemas for the Planning Inspectorate's Data Model",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: appeal-document.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: appeal-event.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: appeal-event-estimate.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: appeal-has.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: appeal-representation.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: appeal-s78.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: dart-response.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: entraid.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: folder.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: gettimesheets-response.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: listed-building.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: listed-buildings.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: nsip-document.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -25,6 +25,10 @@ class CaseType(Enum):
25
25
  class RedactedStatus(Enum):
26
26
  not_redacted = "not_redacted"
27
27
  redacted = "redacted"
28
+ awaiting_ai_redaction = "awaiting_ai_redaction"
29
+ ai_redaction_review_required = "ai_redaction_review_required"
30
+ ai_redaction_failed = "ai_redaction_failed"
31
+ no_redaction_required = "no_redaction_required"
28
32
  NoneType_None = None
29
33
 
30
34
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: nsip-exam-timetable.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: nsip-invoice.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: nsip-meeting.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: nsip-project.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: nsip-project-update.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: nsip-representation.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: nsip-subscription.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: pins-inspector.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: pins-lpa.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: s51-advice.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: service-user.schema.json
3
- # timestamp: 2026-01-27T12:24:06+00:00
3
+ # timestamp: 2026-02-05T10:08:01+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -73,6 +73,14 @@
73
73
  { "$ref": "appeals-components/appellant-procedure-preference.schema.json" },
74
74
  { "$ref": "appeals-components/appeal-ldc.submission.schema.json" }
75
75
  ]
76
+ },
77
+ {
78
+ "$comment": "Enforcement listed building",
79
+ "allOf": [
80
+ { "properties": { "caseType": { "enum": ["F"] } } },
81
+ { "$ref": "appeals-components/appeal-enforcement-submission.schema.json" },
82
+ { "$ref": "appeals-components/appellant-procedure-preference.schema.json" }
83
+ ]
76
84
  }
77
85
  ]
78
86
  },
@@ -71,9 +71,9 @@
71
71
  ]
72
72
  },
73
73
  {
74
- "$comment": "Enforcement",
74
+ "$comment": "Enforcement + Enforcement listed building",
75
75
  "allOf": [
76
- { "properties": { "caseType": { "enum": ["C"] } } },
76
+ { "properties": { "caseType": { "enum": ["C", "F"] } } },
77
77
  { "$ref": "appeals-components/lpa-questionnaire-common.schema.json" },
78
78
  { "$ref": "appeals-components/lpa-questionnaire-has.schema.json" },
79
79
  { "$ref": "appeals-components/lpa-procedure-preference.schema.json" },
@@ -129,7 +129,15 @@
129
129
  },
130
130
  "redactedStatus": {
131
131
  "type": ["string", "null"],
132
- "enum": ["not_redacted", "redacted", null]
132
+ "enum": [
133
+ "not_redacted",
134
+ "redacted",
135
+ "awaiting_ai_redaction",
136
+ "ai_redaction_review_required",
137
+ "ai_redaction_failed",
138
+ "no_redaction_required",
139
+ null
140
+ ]
133
141
  },
134
142
  "publishedStatus": {
135
143
  "type": ["string", "null"],
package/src/enums.cjs CHANGED
@@ -590,6 +590,10 @@ const NSIP_RECOMMENDATION = Object.freeze({
590
590
  });
591
591
 
592
592
  const NSIP_REDACTED_STATUS = Object.freeze({
593
+ AI_REDACTION_FAILED: 'ai_redaction_failed',
594
+ AI_REDACTION_REVIEW_REQUIRED: 'ai_redaction_review_required',
595
+ AWAITING_AI_REDACTION: 'awaiting_ai_redaction',
596
+ NO_REDACTION_REQUIRED: 'no_redaction_required',
593
597
  NOT_REDACTED: 'not_redacted',
594
598
  REDACTED: 'redacted',
595
599
  });
package/src/enums.d.ts CHANGED
@@ -588,6 +588,10 @@ export const NSIP_RECOMMENDATION = {
588
588
  } as const;
589
589
 
590
590
  export const NSIP_REDACTED_STATUS = {
591
+ AI_REDACTION_FAILED: 'ai_redaction_failed',
592
+ AI_REDACTION_REVIEW_REQUIRED: 'ai_redaction_review_required',
593
+ AWAITING_AI_REDACTION: 'awaiting_ai_redaction',
594
+ NO_REDACTION_REQUIRED: 'no_redaction_required',
591
595
  NOT_REDACTED: 'not_redacted',
592
596
  REDACTED: 'redacted',
593
597
  } as const;
package/src/enums.js CHANGED
@@ -588,6 +588,10 @@ export const NSIP_RECOMMENDATION = Object.freeze({
588
588
  });
589
589
 
590
590
  export const NSIP_REDACTED_STATUS = Object.freeze({
591
+ AI_REDACTION_FAILED: 'ai_redaction_failed',
592
+ AI_REDACTION_REVIEW_REQUIRED: 'ai_redaction_review_required',
593
+ AWAITING_AI_REDACTION: 'awaiting_ai_redaction',
594
+ NO_REDACTION_REQUIRED: 'no_redaction_required',
591
595
  NOT_REDACTED: 'not_redacted',
592
596
  REDACTED: 'redacted',
593
597
  });
package/src/index.cjs CHANGED
@@ -671,6 +671,10 @@ const NSIP_RECOMMENDATION = Object.freeze({
671
671
  });
672
672
 
673
673
  const NSIP_REDACTED_STATUS = Object.freeze({
674
+ AI_REDACTION_FAILED: 'ai_redaction_failed',
675
+ AI_REDACTION_REVIEW_REQUIRED: 'ai_redaction_review_required',
676
+ AWAITING_AI_REDACTION: 'awaiting_ai_redaction',
677
+ NO_REDACTION_REQUIRED: 'no_redaction_required',
674
678
  NOT_REDACTED: 'not_redacted',
675
679
  REDACTED: 'redacted',
676
680
  });
package/src/schemas.d.ts CHANGED
@@ -2115,7 +2115,14 @@ export interface NSIPDocument {
2115
2115
  dateCreated: string;
2116
2116
  lastModified: string | null;
2117
2117
  caseType: 'nsip' | 'has' | null;
2118
- redactedStatus: 'not_redacted' | 'redacted' | null;
2118
+ redactedStatus:
2119
+ | 'not_redacted'
2120
+ | 'redacted'
2121
+ | 'awaiting_ai_redaction'
2122
+ | 'ai_redaction_review_required'
2123
+ | 'ai_redaction_failed'
2124
+ | 'no_redaction_required'
2125
+ | null;
2119
2126
  publishedStatus:
2120
2127
  | 'not_checked'
2121
2128
  | 'checked'
@@ -3986,7 +3993,11 @@ export interface AppellantSubmissionCommand {
3986
3993
  } & AppellantCommonSubmissionProperties &
3987
3994
  AppellantCommonPlanningProperties &
3988
3995
  AppellantProcedurePreferenceProperties &
3989
- LDCSpecificProperties);
3996
+ LDCSpecificProperties)
3997
+ | ({
3998
+ caseType?: 'F';
3999
+ } & AppellantEnforcementSubmissionProperties &
4000
+ AppellantProcedurePreferenceProperties);
3990
4001
  documents: {
3991
4002
  /**
3992
4003
  * The unique identifier for the document
@@ -4590,7 +4601,7 @@ export interface LPAQuestionnaireCommand {
4590
4601
  LPAProcedurePreferenceProperties &
4591
4602
  ChangedListedBuildingNumbersProperties)
4592
4603
  | ({
4593
- caseType?: 'C';
4604
+ caseType?: 'C' | 'F';
4594
4605
  } & LPAQCommonSubmissionProperties &
4595
4606
  LPAQHASSubmissionProperties &
4596
4607
  LPAProcedurePreferenceProperties &
@@ -0,0 +1,109 @@
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 exampleEnforcementListedQuestionnaireSchema = {
21
+ casedata: {
22
+ caseType: 'F',
23
+ caseReference: 'est ea elit in nulla',
24
+ lpaQuestionnaireSubmittedDate: null,
25
+ siteAccessDetails: ['velit adipisicing fugiat dolor ullamco'],
26
+ siteSafetyDetails: ['ut', 'dolore ut sint Duis', 'magna consequat quis laboris officia'],
27
+ nearbyCaseReferences: ['nulla', 'incididunt eu nostrud eiusmod', 'exercitation', 'Ut nostrud', 'eiusmod cillum eu'],
28
+ neighbouringSiteAddresses: null,
29
+ isCorrectAppealType: true,
30
+ affectedListedBuildingNumbers: null,
31
+ isGreenBelt: null,
32
+ inConservationArea: null,
33
+ newConditionDetails: null,
34
+ notificationMethod: ['irure', 'cillum tempor incididunt labore in'],
35
+ lpaCostsAppliedFor: true,
36
+ lpaStatement: 'ex laborum cupidatat',
37
+ changedListedBuildingNumbers: null,
38
+ affectsScheduledMonument: true,
39
+ hasProtectedSpecies: false,
40
+ isAonbNationalLandscape: true,
41
+ designatedSitesNames: ['quis nulla eu officia exercitation'],
42
+ hasTreePreservationOrder: null,
43
+ isGypsyOrTravellerSite: true,
44
+ isPublicRightOfWay: null,
45
+ eiaEnvironmentalImpactSchedule: 'schedule-2',
46
+ eiaDevelopmentDescription: 'production-processing-of-metals',
47
+ eiaSensitiveAreaDetails: null,
48
+ eiaColumnTwoThreshold: false,
49
+ eiaScreeningOpinion: false,
50
+ eiaRequiresEnvironmentalStatement: null,
51
+ eiaCompletedEnvironmentalStatement: true,
52
+ hasStatutoryConsultees: true,
53
+ consultedBodiesDetails: 'in in labore irure nostrud',
54
+ hasConsultationResponses: null,
55
+ hasEmergingPlan: null,
56
+ hasSupplementaryPlanningDocs: null,
57
+ hasInfrastructureLevy: true,
58
+ isInfrastructureLevyFormallyAdopted: false,
59
+ infrastructureLevyAdoptedDate: '1913-06-14T10:40:33.0Z',
60
+ infrastructureLevyExpectedDate: '1912-06-14T10:40:33.0Z',
61
+ lpaProcedurePreference: null,
62
+ lpaProcedurePreferenceDetails: 'in reprehenderit',
63
+ lpaProcedurePreferenceDuration: -34480053.16630782,
64
+ noticeRelatesToBuildingEngineeringMiningOther: null,
65
+ siteAreaSquareMetres: null,
66
+ hasAllegedBreachArea: null,
67
+ doesAllegedBreachCreateFloorSpace: null,
68
+ changeOfUseRefuseOrWaste: null,
69
+ changeOfUseMineralExtraction: null,
70
+ changeOfUseMineralStorage: null,
71
+ relatesToErectionOfBuildingOrBuildings: null,
72
+ relatesToBuildingWithAgriculturalPurpose: null,
73
+ relatesToBuildingSingleDwellingHouse: null,
74
+ affectedTrunkRoadName: null,
75
+ isSiteOnCrownLand: null,
76
+ article4AffectedDevelopmentRights: null
77
+ },
78
+ documents: [document]
79
+ };
80
+
81
+ describe('Enforcement listed building questionnaire command schema', () => {
82
+ const schemas = loadAllSchemasSync();
83
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
84
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
85
+ addFormats(ajv);
86
+
87
+ it('should allow valid schema for Enforcement listed building', () => {
88
+ const validationResult = ajv.validate(schema, exampleEnforcementListedQuestionnaireSchema);
89
+ assert.strictEqual(validationResult, true);
90
+ });
91
+
92
+ it('should reject missing caseReference', () => {
93
+ const test = structuredClone(exampleEnforcementListedQuestionnaireSchema);
94
+ delete test.casedata.caseReference;
95
+ const validationResult = ajv.validate(schema, test);
96
+ assert.strictEqual(validationResult, false);
97
+ assert.strictEqual(
98
+ ajv.errors.some((x) => x.message === "must have required property 'caseReference'"),
99
+ true
100
+ );
101
+ });
102
+
103
+ it('should allow additional props', () => {
104
+ const test = structuredClone(exampleEnforcementListedQuestionnaireSchema);
105
+ test.test = 1;
106
+ const validationResult = ajv.validate(schema, test);
107
+ assert.strictEqual(validationResult, true);
108
+ });
109
+ });
@@ -0,0 +1,187 @@
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: 'priorCorrespondenceWithPINS'
28
+ };
29
+
30
+ const exampleEnforcementListedSubmissionSchema = {
31
+ casedata: {
32
+ caseType: 'F',
33
+ submissionId: '4b78fc58-fcb0-70d6-5dea-b9b774f802d6',
34
+ caseProcedure: 'written',
35
+ lpaCode: 'cupidatat',
36
+ caseSubmittedDate: '1890-12-28T22:49:47.0Z',
37
+ enforcementNotice: true,
38
+ enforcementReference: '12345',
39
+ enforcementIssueDate: '1890-12-28T22:49:47.0Z',
40
+ enforcementEffectiveDate: '1890-12-28T22:49:47.0Z',
41
+ contactPlanningInspectorateDate: null,
42
+ interestInLand: 'owner',
43
+ writtenOrVerbalPermission: null,
44
+ descriptionOfAllegedBreach: 'a new chimney',
45
+ applicationMadeAndFeePaid: true,
46
+ applicationDevelopmentAllOrPart: 'all-of-the-development',
47
+ originalDevelopmentDescription: 'in amet',
48
+ changedDevelopmentDescription: true,
49
+ applicationReference: '43010/APP/1/290572',
50
+ applicationDate: '1890-12-28T22:49:47.0Z',
51
+ applicationDecision: 'granted',
52
+ applicationDecisionAppealed: false,
53
+ applicationDecisionDate: '1890-12-28T22:49:47.0Z',
54
+ appealDecisionDate: null,
55
+ caseSubmissionDueDate: null,
56
+ siteAddressLine1: 'occaecat aliquip non pariatur consequat',
57
+ siteAddressLine2: null,
58
+ siteAddressTown: 'et',
59
+ siteAddressCounty: 'velit',
60
+ siteAddressPostcode: 'nisi minim laboris sed',
61
+ siteGridReferenceEasting: '357144',
62
+ siteGridReferenceNorthing: '400534',
63
+ contactAddressLine1: null,
64
+ contactAddressLine2: null,
65
+ contactAddressTown: null,
66
+ contactAddressCounty: null,
67
+ contactAddressPostcode: null,
68
+ contactGridReferenceEasting: null,
69
+ contactGridReferenceNorthing: null,
70
+ siteAccessDetails: ['laborum', 'aute velit quis', 'dolor'],
71
+ siteSafetyDetails: ['velit aliqua', 'qui', 'tempor Duis ut dolore', 'anim Ut in ea', 'eiusmod laborum'],
72
+ appellantCostsAppliedFor: false,
73
+ appellantProcedurePreference: null,
74
+ appellantProcedurePreferenceDetails: 'eiusmod ex exercitation',
75
+ appellantProcedurePreferenceDuration: 10816414.941069126,
76
+ appellantProcedurePreferenceWitnessCount: -91300569.36444478,
77
+ statusPlanningObligation: 'proident aute',
78
+ nearbyCaseReferences: ['incididunt'],
79
+ namedIndividuals: [
80
+ {
81
+ firstName: 'Bob',
82
+ lastName: 'Bobberson',
83
+ interestInLand: 'owner',
84
+ writtenOrVerbalPermission: 'yes'
85
+ }
86
+ ],
87
+ appealGrounds: [
88
+ {
89
+ groundRef: 'a',
90
+ factsForGround: 'I like Christmas'
91
+ }
92
+ ]
93
+ },
94
+ documents: [document],
95
+ users: [user]
96
+ };
97
+
98
+ describe(' listed building submission command schema', () => {
99
+ const schemas = loadAllSchemasSync();
100
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
101
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
102
+ addFormats(ajv);
103
+
104
+ it('should allow valid schema for enforcement listed building', () => {
105
+ const validationResult = ajv.validate(schema, exampleEnforcementListedSubmissionSchema);
106
+ assert.strictEqual(validationResult, true);
107
+ });
108
+
109
+ it('should enforce eastings-northings pattern', () => {
110
+ const invalidPatterns = [
111
+ { easting: 'abc123', northing: '400534', description: 'easting with letters' },
112
+ { easting: '1234567*', northing: '400534', description: 'easting too long' },
113
+ { easting: '12345', northing: '400534', description: 'easting too short' },
114
+ { easting: '357144', northing: 'xyz123', description: 'northing with letters' },
115
+ { easting: '357144', northing: '12345678', description: 'northing too long' },
116
+ { easting: '357144', northing: '12345', description: 'northing too short' },
117
+ { easting: '', northing: '400534', description: 'empty easting' },
118
+ { easting: '357144', northing: '', description: 'empty northing' }
119
+ ];
120
+
121
+ for (const pattern of invalidPatterns) {
122
+ const test = structuredClone(exampleEnforcementListedSubmissionSchema);
123
+ test.casedata.siteGridReferenceEasting = pattern.easting;
124
+ test.casedata.siteGridReferenceNorthing = pattern.northing;
125
+
126
+ const validationResult = ajv.validate(schema, test);
127
+ assert.strictEqual(validationResult, false, `Expected validation to fail for ${pattern.description}`);
128
+ }
129
+ });
130
+
131
+ it('should allow only eastings-northings', () => {
132
+ const test = structuredClone(exampleEnforcementListedSubmissionSchema);
133
+ test.casedata.siteAddressPostcode = undefined;
134
+ test.casedata.siteAddressLine1 = null;
135
+ test.casedata.siteAddressTown = null;
136
+ const validationResult = ajv.validate(schema, test);
137
+ assert.strictEqual(validationResult, true);
138
+ });
139
+
140
+ it('should allow only site address', () => {
141
+ const test = structuredClone(exampleEnforcementListedSubmissionSchema);
142
+ test.casedata.siteGridReferenceEasting = undefined;
143
+ test.casedata.siteGridReferenceNorthing = null;
144
+ const validationResult = ajv.validate(schema, test);
145
+ assert.strictEqual(validationResult, true);
146
+ });
147
+
148
+ it('should enforce either site address or easting-northing', () => {
149
+ const test = structuredClone(exampleEnforcementListedSubmissionSchema);
150
+ test.casedata.siteAddressPostcode = undefined;
151
+ test.casedata.siteAddressLine1 = null;
152
+ test.casedata.siteAddressTown = null;
153
+ test.casedata.siteGridReferenceEasting = undefined;
154
+ test.casedata.siteGridReferenceNorthing = null;
155
+ const validationResult = ajv.validate(schema, test);
156
+ assert.strictEqual(validationResult, false);
157
+ });
158
+
159
+ it('should reject missing root property', () => {
160
+ const test = structuredClone(exampleEnforcementListedSubmissionSchema);
161
+ delete test.casedata.caseType;
162
+ const validationResult = ajv.validate(schema, test);
163
+ assert.strictEqual(validationResult, false);
164
+ assert.strictEqual(
165
+ ajv.errors.some((x) => x.message === "must have required property 'caseType'"),
166
+ true
167
+ );
168
+ });
169
+
170
+ it('should reject missing nested property', () => {
171
+ const test = structuredClone(exampleEnforcementListedSubmissionSchema);
172
+ delete test.casedata.caseProcedure;
173
+ const validationResult = ajv.validate(schema, test);
174
+ assert.strictEqual(validationResult, false);
175
+ assert.strictEqual(
176
+ ajv.errors.some((x) => x.message === "must have required property 'caseProcedure'"),
177
+ true
178
+ );
179
+ });
180
+
181
+ it('should allow additional props', () => {
182
+ const test = structuredClone(exampleEnforcementListedSubmissionSchema);
183
+ test.test = 1;
184
+ const validationResult = ajv.validate(schema, test);
185
+ assert.strictEqual(validationResult, true);
186
+ });
187
+ });