@planning-inspectorate/data-model 2.23.0 → 2.25.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 (43) hide show
  1. package/docs/appeal-has.md +29 -0
  2. package/docs/appeal-s78.md +60 -0
  3. package/package.json +1 -1
  4. package/pins_data_model/models/model_appeal_document.py +1 -1
  5. package/pins_data_model/models/model_appeal_event.py +1 -1
  6. package/pins_data_model/models/model_appeal_event_estimate.py +1 -1
  7. package/pins_data_model/models/model_appeal_has.py +14 -1
  8. package/pins_data_model/models/model_appeal_representation.py +1 -1
  9. package/pins_data_model/models/model_appeal_s78.py +26 -1
  10. package/pins_data_model/models/model_dart_response.py +1 -1
  11. package/pins_data_model/models/model_entraid.py +1 -1
  12. package/pins_data_model/models/model_folder.py +1 -1
  13. package/pins_data_model/models/model_gettimesheets_response.py +1 -1
  14. package/pins_data_model/models/model_listed_building.py +1 -1
  15. package/pins_data_model/models/model_listed_buildings.py +1 -1
  16. package/pins_data_model/models/model_nsip_document.py +1 -1
  17. package/pins_data_model/models/model_nsip_exam_timetable.py +1 -1
  18. package/pins_data_model/models/model_nsip_invoice.py +1 -1
  19. package/pins_data_model/models/model_nsip_meeting.py +1 -1
  20. package/pins_data_model/models/model_nsip_project.py +1 -1
  21. package/pins_data_model/models/model_nsip_project_update.py +1 -1
  22. package/pins_data_model/models/model_nsip_representation.py +1 -1
  23. package/pins_data_model/models/model_nsip_subscription.py +1 -1
  24. package/pins_data_model/models/model_pins_inspector.py +1 -1
  25. package/pins_data_model/models/model_pins_lpa.py +1 -1
  26. package/pins_data_model/models/model_s51_advice.py +1 -1
  27. package/pins_data_model/models/model_service_user.py +1 -1
  28. package/schemas/appeal-has.schema.json +7 -1
  29. package/schemas/appeal-s78.schema.json +14 -1
  30. package/schemas/commands/appeals-components/lpa-questionnaire-enforcement-common.schema.json +60 -0
  31. package/schemas/commands/appeals-components/lpa-questionnaire-enforcement.schema.json +2 -50
  32. package/schemas/commands/lpa-questionnaire.schema.json +18 -2
  33. package/src/enums.cjs +42 -0
  34. package/src/enums.d.ts +38 -0
  35. package/src/enums.js +38 -0
  36. package/src/index.cjs +42 -0
  37. package/src/lib/gen-enum-impl.js +196 -0
  38. package/src/lib/gen-enum-impl.test.js +183 -0
  39. package/src/lib/gen-enums.js +11 -87
  40. package/src/schemas.d.ts +35 -6
  41. package/tests/appeals/appeal-has.test.js +1 -0
  42. package/tests/appeals/appeal-s78.test.js +2 -0
  43. package/tests/appeals/commands/enforcement-listed-questionnaire.test.js +2 -3
@@ -0,0 +1,183 @@
1
+ import { describe, it } from 'node:test';
2
+ import { collectEnumProps, generateConstants, generateEnumMap } from './gen-enum-impl.js';
3
+ import assert from 'node:assert';
4
+
5
+ describe('gen-enum-impl', () => {
6
+ describe('generateConstants', () => {
7
+ it('should generate constant definitions', () => {
8
+ const output = generateConstants('some_EnumValue', ['value-1', 'other-value', 'a-third-value']);
9
+ assert.strictEqual(
10
+ output,
11
+ `export const SOME_ENUM_VALUE = Object.freeze({\n A_THIRD_VALUE: 'a-third-value',\n OTHER_VALUE: 'other-value',\n VALUE_1: 'value-1',\n});`
12
+ );
13
+ });
14
+ it('should generate constant definitions as typescript', () => {
15
+ const output = generateConstants('some_EnumValue', ['value-1', 'other-value', 'a-third-value'], { ts: true });
16
+ assert.strictEqual(
17
+ output,
18
+ `export const SOME_ENUM_VALUE = {\n A_THIRD_VALUE: 'a-third-value',\n OTHER_VALUE: 'other-value',\n VALUE_1: 'value-1',\n} as const;`
19
+ );
20
+ });
21
+ });
22
+
23
+ describe('generateEnumMap', () => {
24
+ it('should add all enums to a map by key with prefix', () => {
25
+ const enums = [
26
+ {
27
+ schemaName: 'schema-1',
28
+ key: 'key3',
29
+ values: ['enum-1', 'enum-2']
30
+ },
31
+ {
32
+ schemaName: 'appeal-1.schema.json',
33
+ key: 'key5',
34
+ values: ['enum-4', 'enum-5']
35
+ },
36
+ {
37
+ schemaName: 'nsip-1.schema.json',
38
+ key: 'key5',
39
+ values: ['enum-6', 'enum-7']
40
+ }
41
+ ];
42
+ const map = generateEnumMap(enums);
43
+ assert.deepStrictEqual(map, {
44
+ appeal_key5: ['enum-4', 'enum-5'],
45
+ nsip_key5: ['enum-6', 'enum-7'],
46
+ key3: ['enum-1', 'enum-2']
47
+ });
48
+ });
49
+ it('should ignore duplicates if the enums match', () => {
50
+ const enums = [
51
+ {
52
+ schemaName: 'schema-1',
53
+ key: 'key3',
54
+ values: ['enum-1', 'enum-2']
55
+ },
56
+ {
57
+ schemaName: 'appeal-1.schema.json',
58
+ key: 'key5',
59
+ values: ['enum-4', 'enum-5']
60
+ },
61
+ {
62
+ schemaName: 'appeal-2.schema.json',
63
+ key: 'key5',
64
+ values: ['enum-4', 'enum-5']
65
+ }
66
+ ];
67
+ const map = generateEnumMap(enums);
68
+ assert.deepStrictEqual(map, {
69
+ appeal_key5: ['enum-4', 'enum-5'],
70
+ key3: ['enum-1', 'enum-2']
71
+ });
72
+ });
73
+ it('should add a more specific prefix if there is a duplicate key', () => {
74
+ const enums = [
75
+ {
76
+ schemaName: 'schema-1',
77
+ key: 'key3',
78
+ values: ['enum-1', 'enum-2']
79
+ },
80
+ {
81
+ schemaName: 'appeal-1.schema.json',
82
+ key: 'key5',
83
+ values: ['enum-4', 'enum-5']
84
+ },
85
+ {
86
+ schemaName: 'appeal-2.schema.json',
87
+ key: 'key5',
88
+ values: ['enum-4', 'enum-5', 'enum-6']
89
+ }
90
+ ];
91
+ const map = generateEnumMap(enums);
92
+ assert.deepStrictEqual(map, {
93
+ appeal_key5: ['enum-4', 'enum-5', 'enum-6'],
94
+ 'appeal-1_key5': ['enum-4', 'enum-5'],
95
+ key3: ['enum-1', 'enum-2']
96
+ });
97
+ });
98
+ it('should error if duplicate key enums are not a superset', () => {
99
+ const enums = [
100
+ {
101
+ schemaName: 'schema-1',
102
+ key: 'key3',
103
+ values: ['enum-1', 'enum-2']
104
+ },
105
+ {
106
+ schemaName: 'appeal-1.schema.json',
107
+ key: 'key5',
108
+ values: ['enum-4', 'enum-5']
109
+ },
110
+ {
111
+ schemaName: 'appeal-2.schema.json',
112
+ key: 'key5',
113
+ values: ['enum-4', 'enum-6']
114
+ }
115
+ ];
116
+ assert.throws(() => generateEnumMap(enums), { message: /not a superset of the other/ });
117
+ });
118
+ it('should error if duplicate keys are present more than twice', () => {
119
+ const enums = [
120
+ {
121
+ schemaName: 'schema-1',
122
+ key: 'key3',
123
+ values: ['enum-1', 'enum-2']
124
+ },
125
+ {
126
+ schemaName: 'appeal-1.schema.json',
127
+ key: 'key5',
128
+ values: ['enum-4', 'enum-5']
129
+ },
130
+ {
131
+ schemaName: 'appeal-2.schema.json',
132
+ key: 'key5',
133
+ values: ['enum-4', 'enum-5', 'enum-6']
134
+ },
135
+ {
136
+ schemaName: 'appeal-2.schema.json',
137
+ key: 'key5',
138
+ values: ['enum-4', 'enum-5', 'enum-6', 'enum-7']
139
+ }
140
+ ];
141
+ assert.throws(() => generateEnumMap(enums), { message: /used by multiple schemas/ });
142
+ });
143
+ });
144
+
145
+ describe('collectEnumProps', () => {
146
+ it('should collect all enums', () => {
147
+ const enums = [];
148
+ collectEnumProps(
149
+ enums,
150
+ {
151
+ key1: 'value-1',
152
+ key2: 'other-value',
153
+ key3: {
154
+ type: 'string',
155
+ enum: ['enum-1', 'enum-2']
156
+ }
157
+ },
158
+ 'schema-1'
159
+ );
160
+ collectEnumProps(
161
+ enums,
162
+ {
163
+ key5: {
164
+ type: 'string',
165
+ enum: ['enum-4', 'enum-5']
166
+ }
167
+ },
168
+ 'appeal-1.schema.json'
169
+ );
170
+ assert.strictEqual(enums.length, 2);
171
+ assert.deepStrictEqual(enums[0], {
172
+ schemaName: 'schema-1',
173
+ key: 'key3',
174
+ values: ['enum-1', 'enum-2']
175
+ });
176
+ assert.deepStrictEqual(enums[1], {
177
+ schemaName: 'appeal-1.schema.json',
178
+ key: 'key5',
179
+ values: ['enum-4', 'enum-5']
180
+ });
181
+ });
182
+ });
183
+ });
@@ -1,16 +1,14 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- import url from 'url';
4
3
  import { loadAllSchemas } from '../index.js';
5
- import snakeCase from 'lodash.snakecase';
6
4
  import { loadSchema } from './load.js';
5
+ import { collectEnumProps, generateConstants, generateEnumMap } from './gen-enum-impl.js';
7
6
 
8
- const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
7
+ const __dirname = import.meta.dirname;
9
8
 
10
9
  export const constPath = path.join(__dirname, '..', 'enums.js');
11
10
  export const constTsPath = path.join(__dirname, '..', 'enums.d.ts');
12
11
 
13
- const SPACING = ' ';
14
12
  const NEW_LINE = '\n';
15
13
 
16
14
  /**
@@ -19,16 +17,17 @@ const NEW_LINE = '\n';
19
17
  async function run() {
20
18
  const s = await loadAllSchemas();
21
19
 
22
- /** @type {Object<string, string[]>} */
23
- const enumProps = {};
20
+ const allEnums = [];
24
21
 
25
22
  for (const schemaName of Object.keys(s.schemas).sort()) {
26
23
  const schema = s.schemas[schemaName];
27
- collectEnumProps(enumProps, schema.properties, schemaPrefix(schemaName));
24
+ collectEnumProps(allEnums, schema.properties, schemaName);
28
25
  }
29
26
 
30
27
  const staticEnums = await loadSchema(path.join(__dirname, 'enums-static.schema.json'));
31
- collectEnumProps(enumProps, staticEnums.properties, 'message');
28
+ collectEnumProps(allEnums, staticEnums.properties, 'message');
29
+
30
+ const enumProps = generateEnumMap(allEnums);
32
31
 
33
32
  let output = '';
34
33
  let tsOutput = '';
@@ -42,82 +41,7 @@ async function run() {
42
41
  await fs.writeFile(constTsPath, tsOutput);
43
42
  }
44
43
 
45
- /**
46
- * Generate JS code to define exported constants for the given enum values
47
- *
48
- * @param {string} name
49
- * @param {string[]} values
50
- * @param {Object} opts
51
- * @param {boolean} [opts.ts] - whether to generate the TS type or JS code
52
- * @returns {string}
53
- */
54
- function generateConstants(name, values, { ts = false } = {}) {
55
- const freezeStart = ts ? '' : 'Object.freeze(';
56
- const freezeEnd = ts ? ' as const' : ')';
57
- const exportPrefix = 'export ';
58
- const lines = [`${exportPrefix}const ${formatName(name)} = ${freezeStart}{`];
59
- /**
60
- * @param {string} line
61
- * @param {number} spacing
62
- */
63
- const addWithSpace = (line, spacing = 0) => lines.push(SPACING.repeat(spacing) + line);
64
-
65
- for (const value of values.sort()) {
66
- addWithSpace(`${formatName(value)}: '${value}',`, 1);
67
- }
68
-
69
- lines.push(`}${freezeEnd};`);
70
-
71
- return lines.join(NEW_LINE);
72
- }
73
-
74
- /**
75
- * Format a name for use as a constant value: UPPER_SNAKE_CASE
76
- *
77
- * @param {string} name
78
- * @returns {string}
79
- */
80
- function formatName(name) {
81
- return snakeCase(name).toUpperCase();
82
- }
83
-
84
- /**
85
- *
86
- * @param {Object<string, string[]>} map
87
- * @param {Object<string, {type?: string|string[], enum?: string[]}>} properties
88
- * @param {string} [prefix]
89
- */
90
- function collectEnumProps(map, properties = {}, prefix) {
91
- for (const [key, prop] of Object.entries(properties)) {
92
- const name = prefix ? prefix + '_' + key : key;
93
-
94
- const hasStringEnum = (p) =>
95
- p && p.enum && Array.isArray(p.enum) && p.type && (p.type === 'string' || p.type.includes('string'));
96
- const getEnumValues = (p) => p.filter((e) => e !== null);
97
- const collectEnumFromProp = (p) => {
98
- if (hasStringEnum(p)) {
99
- map[name] = getEnumValues(p.enum);
100
- }
101
- };
102
-
103
- collectEnumFromProp(prop);
104
- collectEnumFromProp(prop.items);
105
- }
106
- }
107
-
108
- /**
109
- * Generate a prefix based on the schema name
110
- *
111
- * @param {string} schemaName
112
- * @returns {string|undefined}
113
- */
114
- function schemaPrefix(schemaName) {
115
- if (schemaName.toLowerCase().includes('appeal')) {
116
- return 'appeal';
117
- }
118
- if (schemaName.toLowerCase().includes('nsip')) {
119
- return 'nsip';
120
- }
121
- }
122
-
123
- run().catch(console.error);
44
+ run().catch((err) => {
45
+ console.error(err);
46
+ process.exit(1);
47
+ });
package/src/schemas.d.ts CHANGED
@@ -526,6 +526,10 @@ export type AppealHASCase = (GridReference | SiteAddress) & {
526
526
  * The date of the original LPA decision
527
527
  */
528
528
  applicationDecisionDate: string | null;
529
+ /**
530
+ * The date that an application decision is due
531
+ */
532
+ applicationDecisionDueDate?: string | null;
529
533
  /**
530
534
  * The statutory deadline for submitting an appeal from the original LPA decision date
531
535
  */
@@ -685,7 +689,7 @@ export type AppealHASCase = (GridReference | SiteAddress) & {
685
689
  /**
686
690
  * The methods used to notify relevant parties
687
691
  */
688
- notificationMethod: ('notice' | 'letter' | 'advert')[] | null;
692
+ notificationMethod: ('notice' | 'letter' | 'advert' | 'not_notified')[] | null;
689
693
  /**
690
694
  * Indicates if the appellant has informed other owners of the site
691
695
  */
@@ -1132,6 +1136,10 @@ export type AppealS78Case = (GridReference | SiteAddress) & {
1132
1136
  * The date of the original LPA decision
1133
1137
  */
1134
1138
  applicationDecisionDate: string | null;
1139
+ /**
1140
+ * The date that an application decision is due
1141
+ */
1142
+ applicationDecisionDueDate?: string | null;
1135
1143
  /**
1136
1144
  * The statutory deadline for submitting an appeal from the original LPA decision date
1137
1145
  */
@@ -1207,7 +1215,7 @@ export type AppealS78Case = (GridReference | SiteAddress) & {
1207
1215
  /**
1208
1216
  * The methods used to notify relevant parties
1209
1217
  */
1210
- notificationMethod: ('notice' | 'letter' | 'advert')[] | null;
1218
+ notificationMethod: ('notice' | 'letter' | 'advert' | 'not_notified')[] | null;
1211
1219
  /**
1212
1220
  * Indicates if the appellant has informed other owners of the site
1213
1221
  */
@@ -1458,6 +1466,10 @@ export type AppealS78Case = (GridReference | SiteAddress) & {
1458
1466
  proofsOfEvidenceDueDate: string | null;
1459
1467
  siteNoticesSentDate: string | null;
1460
1468
  statementDueDate: string | null;
1469
+ /**
1470
+ * The date of the Case Management Conference (CMC)
1471
+ */
1472
+ caseManagementConferenceDate?: string | null;
1461
1473
  /**
1462
1474
  * The net gain in residences of the proposed development
1463
1475
  */
@@ -4601,7 +4613,7 @@ export type LPAQCasAdvertSubmissionProperties = {
4601
4613
  didAppellantSubmitCompletePhotosAndPlans?: boolean | null;
4602
4614
  };
4603
4615
  /**
4604
- * Schema defining any enfrocement specific properties for LPAQ submissions
4616
+ * Schema defining any common enforcement specific properties for LPAQ submissions
4605
4617
  */
4606
4618
  export type LPAQEnforcementSubmissionProperties = {
4607
4619
  noticeRelatesToBuildingEngineeringMiningOther?: boolean | null;
@@ -4623,6 +4635,11 @@ export type LPAQEnforcementSubmissionProperties = {
4623
4635
  relatesToErectionOfBuildingOrBuildings?: boolean | null;
4624
4636
  relatesToBuildingWithAgriculturalPurpose?: boolean | null;
4625
4637
  relatesToBuildingSingleDwellingHouse?: boolean | null;
4638
+ };
4639
+ /**
4640
+ * Schema defining any enforcement specific properties for LPAQ submissions
4641
+ */
4642
+ export type LPAQEnforcementSubmissionProperties1 = {
4626
4643
  /**
4627
4644
  * Name of trunk road within 67M of site
4628
4645
  */
@@ -4693,7 +4710,7 @@ export interface LPAQuestionnaireCommand {
4693
4710
  LPAProcedurePreferenceProperties &
4694
4711
  ChangedListedBuildingNumbersProperties)
4695
4712
  | ({
4696
- caseType?: 'C' | 'F';
4713
+ caseType?: 'C';
4697
4714
  } & LPAQCommonSubmissionProperties &
4698
4715
  LPAQHASSubmissionProperties &
4699
4716
  LPAProcedurePreferenceProperties &
@@ -4701,13 +4718,25 @@ export interface LPAQuestionnaireCommand {
4701
4718
  InfrastructureLevyProperties &
4702
4719
  EnvironmentalImpactAssessmentProperties &
4703
4720
  SiteDesignationsAndProtectionSpecificProperties &
4704
- LPAQEnforcementSubmissionProperties)
4721
+ LPAQEnforcementSubmissionProperties &
4722
+ LPAQEnforcementSubmissionProperties1)
4705
4723
  | ({
4706
4724
  caseType?: 'X';
4707
4725
  } & LPAQCommonSubmissionProperties &
4708
4726
  LPAProcedurePreferenceProperties &
4709
4727
  InfrastructureLevyProperties &
4710
- LPAQLDCSubmissionProperties);
4728
+ LPAQLDCSubmissionProperties)
4729
+ | ({
4730
+ caseType?: 'F';
4731
+ } & LPAQCommonSubmissionProperties &
4732
+ LPAQHASSubmissionProperties &
4733
+ LPAProcedurePreferenceProperties &
4734
+ ChangedListedBuildingNumbersProperties &
4735
+ InfrastructureLevyProperties &
4736
+ EnvironmentalImpactAssessmentProperties &
4737
+ SiteDesignationsAndProtectionSpecificProperties &
4738
+ LPAQEnforcementSubmissionProperties &
4739
+ LPAQListedBuildingSubmissionProperties);
4711
4740
  documents: {
4712
4741
  /**
4713
4742
  * The unique identifier for the document
@@ -58,6 +58,7 @@ const appealHas = {
58
58
  applicationDate: '1949-08-25T15:11:02.0Z',
59
59
  applicationDecision: 'refused',
60
60
  applicationDecisionDate: '1952-08-09T01:03:51.0Z',
61
+ applicationDecisionDueDate: null,
61
62
  caseSubmissionDueDate: '1942-02-09T21:03:07.0Z',
62
63
  siteAddressLine1: 'ullamco',
63
64
  siteAddressLine2: null,
@@ -52,6 +52,7 @@ const appealS78 = {
52
52
  applicationDate: '1910-07-16T04:24:14.0Z',
53
53
  applicationDecision: 'not_received',
54
54
  applicationDecisionDate: null,
55
+ applicationDecisionDueDate: '1952-08-09T01:03:51.0Z',
55
56
  caseSubmissionDueDate: null,
56
57
  siteAddressLine1: 'dolore mollit',
57
58
  siteAddressLine2: null,
@@ -145,6 +146,7 @@ const appealS78 = {
145
146
  proofsOfEvidenceDueDate: '1945-06-24T01:14:05.0Z',
146
147
  siteNoticesSentDate: '1904-03-09T08:52:39.0Z',
147
148
  statementDueDate: null,
149
+ caseManagementConferenceDate: '1904-03-09T08:52:39.0Z',
148
150
  numberOfResidencesNetChange: 59800248.893144846,
149
151
  siteGridReferenceEasting: '357144',
150
152
  siteGridReferenceNorthing: '400534',
@@ -71,9 +71,8 @@ const exampleEnforcementListedQuestionnaireSchema = {
71
71
  relatesToErectionOfBuildingOrBuildings: null,
72
72
  relatesToBuildingWithAgriculturalPurpose: null,
73
73
  relatesToBuildingSingleDwellingHouse: null,
74
- affectedTrunkRoadName: null,
75
- isSiteOnCrownLand: null,
76
- article4AffectedDevelopmentRights: null
74
+ preserveGrantLoan: null,
75
+ consultHistoricEngland: null
77
76
  },
78
77
  documents: [document]
79
78
  };