@pdtf/schemas 3.6.0-dev.6 → 3.6.0-dev.8

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 (131) hide show
  1. package/busy-spence-79c6bf/CLAUDE.md +101 -0
  2. package/busy-spence-79c6bf/LICENSE +21 -0
  3. package/busy-spence-79c6bf/README.md +435 -0
  4. package/busy-spence-79c6bf/docs/branch-release-workflow.md +138 -0
  5. package/busy-spence-79c6bf/docs/sef25-extensions-ui-spec.md +355 -0
  6. package/busy-spence-79c6bf/index.js +654 -0
  7. package/busy-spence-79c6bf/package-lock.json +7299 -0
  8. package/busy-spence-79c6bf/package.json +44 -0
  9. package/busy-spence-79c6bf/scripts/ta6ed6-titles.js +403 -0
  10. package/busy-spence-79c6bf/scripts/ta7ed5-titles.js +183 -0
  11. package/busy-spence-79c6bf/src/examples/v1/exampleAddParticipantVouch.json +32 -0
  12. package/busy-spence-79c6bf/src/examples/v1/exampleDocumentedVouch.json +40 -0
  13. package/busy-spence-79c6bf/src/examples/v1/exampleElectronicRecord.json +30 -0
  14. package/busy-spence-79c6bf/src/examples/v1/exampleTransaction.json +878 -0
  15. package/busy-spence-79c6bf/src/examples/v1/exampleVouch.json +25 -0
  16. package/busy-spence-79c6bf/src/examples/v2/exampleTransaction.json +901 -0
  17. package/busy-spence-79c6bf/src/examples/v3/exampleAddParticipantVouch.json +38 -0
  18. package/busy-spence-79c6bf/src/examples/v3/exampleCustomOverlay.json +55 -0
  19. package/busy-spence-79c6bf/src/examples/v3/exampleCustomOverlay2.json +93 -0
  20. package/busy-spence-79c6bf/src/examples/v3/exampleDocumentedVouch.json +47 -0
  21. package/busy-spence-79c6bf/src/examples/v3/exampleElectronicRecord.json +36 -0
  22. package/busy-spence-79c6bf/src/examples/v3/exampleTransaction.json +1038 -0
  23. package/busy-spence-79c6bf/src/examples/v3/exampleVouch.json +32 -0
  24. package/busy-spence-79c6bf/src/mappings/SendPropertyDetailsRequestSchema.json +1588 -0
  25. package/busy-spence-79c6bf/src/mappings/adf.js +198 -0
  26. package/busy-spence-79c6bf/src/schemas/v1/GeoJSON.json +1279 -0
  27. package/busy-spence-79c6bf/src/schemas/v1/baspiNotes.html +102 -0
  28. package/busy-spence-79c6bf/src/schemas/v1/energy-performance-certificate.json +124 -0
  29. package/busy-spence-79c6bf/src/schemas/v1/freehold-information.json +2051 -0
  30. package/busy-spence-79c6bf/src/schemas/v1/leasehold-information.json +2064 -0
  31. package/busy-spence-79c6bf/src/schemas/v1/legal-information.json +1045 -0
  32. package/busy-spence-79c6bf/src/schemas/v1/material-facts.json +2109 -0
  33. package/busy-spence-79c6bf/src/schemas/v1/pdtf-transaction-search-results.json +40 -0
  34. package/busy-spence-79c6bf/src/schemas/v1/pdtf-transaction.json +191 -0
  35. package/busy-spence-79c6bf/src/schemas/v1/searches/drainage-and-water.json +261 -0
  36. package/busy-spence-79c6bf/src/schemas/v1/searches/local-land-charges.json +69 -0
  37. package/busy-spence-79c6bf/src/schemas/v1/searches/local-searches-required.json +857 -0
  38. package/busy-spence-79c6bf/src/schemas/v1/searches.json +237 -0
  39. package/busy-spence-79c6bf/src/schemas/v1/title-deed.json +841 -0
  40. package/busy-spence-79c6bf/src/schemas/v2/baspiNotes.html +102 -0
  41. package/busy-spence-79c6bf/src/schemas/v2/combined.json +37731 -0
  42. package/busy-spence-79c6bf/src/schemas/v2/overlays/baspi.json +8132 -0
  43. package/busy-spence-79c6bf/src/schemas/v2/overlays/con29DW.json +705 -0
  44. package/busy-spence-79c6bf/src/schemas/v2/overlays/con29R.json +2956 -0
  45. package/busy-spence-79c6bf/src/schemas/v2/overlays/fme1.json +1259 -0
  46. package/busy-spence-79c6bf/src/schemas/v2/overlays/llc1.json +25 -0
  47. package/busy-spence-79c6bf/src/schemas/v2/overlays/lpe1.json +2171 -0
  48. package/busy-spence-79c6bf/src/schemas/v2/overlays/nts.json +326 -0
  49. package/busy-spence-79c6bf/src/schemas/v2/overlays/oc1.json +2799 -0
  50. package/busy-spence-79c6bf/src/schemas/v2/overlays/piq.json +3419 -0
  51. package/busy-spence-79c6bf/src/schemas/v2/overlays/rds.json +3736 -0
  52. package/busy-spence-79c6bf/src/schemas/v2/overlays/ta10.json +5306 -0
  53. package/busy-spence-79c6bf/src/schemas/v2/overlays/ta6.json +4533 -0
  54. package/busy-spence-79c6bf/src/schemas/v2/overlays/ta7.json +1045 -0
  55. package/busy-spence-79c6bf/src/schemas/v2/pdtf-transaction.json +34351 -0
  56. package/busy-spence-79c6bf/src/schemas/v2/skeleton.json +3973 -0
  57. package/busy-spence-79c6bf/src/schemas/v3/combined.json +48643 -0
  58. package/busy-spence-79c6bf/src/schemas/v3/compactSkeleton.txt +4519 -0
  59. package/busy-spence-79c6bf/src/schemas/v3/overlays/README.md +299 -0
  60. package/busy-spence-79c6bf/src/schemas/v3/overlays/baspi4.json +8030 -0
  61. package/busy-spence-79c6bf/src/schemas/v3/overlays/baspi5.json +8180 -0
  62. package/busy-spence-79c6bf/src/schemas/v3/overlays/con29DW.json +716 -0
  63. package/busy-spence-79c6bf/src/schemas/v3/overlays/con29R.json +2981 -0
  64. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/ac.json +334 -0
  65. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/as.json +74 -0
  66. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/dk.json +33 -0
  67. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/dr.json +73 -0
  68. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/er.json +85 -0
  69. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/fd.json +75 -0
  70. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/hi.json +51 -0
  71. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/hs.json +73 -0
  72. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/ic.json +94 -0
  73. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/jk.json +96 -0
  74. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/la.json +85 -0
  75. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/lc.json +76 -0
  76. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/ma.json +90 -0
  77. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/mc.json +38 -0
  78. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/mi.json +70 -0
  79. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/nd.json +44 -0
  80. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/oa.json +24 -0
  81. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/oc.json +57 -0
  82. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/pc.json +38 -0
  83. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/ph.json +195 -0
  84. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/rw.json +69 -0
  85. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/sb.json +73 -0
  86. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/sc.json +133 -0
  87. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/sd.json +64 -0
  88. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/sf.json +74 -0
  89. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/sl.json +68 -0
  90. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/ta.json +204 -0
  91. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/tf.json +103 -0
  92. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/tr.json +74 -0
  93. package/busy-spence-79c6bf/src/schemas/v3/overlays/extensions/wg.json +351 -0
  94. package/busy-spence-79c6bf/src/schemas/v3/overlays/fme1.json +1266 -0
  95. package/busy-spence-79c6bf/src/schemas/v3/overlays/llc1.json +25 -0
  96. package/busy-spence-79c6bf/src/schemas/v3/overlays/lpe1.json +2199 -0
  97. package/busy-spence-79c6bf/src/schemas/v3/overlays/nts.json +2159 -0
  98. package/busy-spence-79c6bf/src/schemas/v3/overlays/nts2.json +2744 -0
  99. package/busy-spence-79c6bf/src/schemas/v3/overlays/ntsl.json +1815 -0
  100. package/busy-spence-79c6bf/src/schemas/v3/overlays/ntsl2.json +2219 -0
  101. package/busy-spence-79c6bf/src/schemas/v3/overlays/oc1.json +2817 -0
  102. package/busy-spence-79c6bf/src/schemas/v3/overlays/piq.json +3324 -0
  103. package/busy-spence-79c6bf/src/schemas/v3/overlays/rds.json +3668 -0
  104. package/busy-spence-79c6bf/src/schemas/v3/overlays/sef25.json +1653 -0
  105. package/busy-spence-79c6bf/src/schemas/v3/overlays/sr24.json +38 -0
  106. package/busy-spence-79c6bf/src/schemas/v3/overlays/ta10.json +4645 -0
  107. package/busy-spence-79c6bf/src/schemas/v3/overlays/ta6.json +4572 -0
  108. package/busy-spence-79c6bf/src/schemas/v3/overlays/ta6ed6.json +6665 -0
  109. package/busy-spence-79c6bf/src/schemas/v3/overlays/ta7.json +1537 -0
  110. package/busy-spence-79c6bf/src/schemas/v3/overlays/ta7ed5.json +1585 -0
  111. package/busy-spence-79c6bf/src/schemas/v3/pdtf-transaction.json +41570 -0
  112. package/busy-spence-79c6bf/src/schemas/v3/skeleton.json +5767 -0
  113. package/busy-spence-79c6bf/src/schemas/verifiedClaims/README.md +21 -0
  114. package/busy-spence-79c6bf/src/schemas/verifiedClaims/pdtf-verified-claims.json +447 -0
  115. package/busy-spence-79c6bf/src/utils/compactSkeleton.js +84 -0
  116. package/busy-spence-79c6bf/src/utils/countTokens.js +67 -0
  117. package/busy-spence-79c6bf/src/utils/extractExtensionOverlays.js +633 -0
  118. package/busy-spence-79c6bf/src/utils/extractOverlay.js +381 -0
  119. package/busy-spence-79c6bf/src/utils/extractProperties.js +44 -0
  120. package/busy-spence-79c6bf/src/utils/listPathTitles.js +36 -0
  121. package/busy-spence-79c6bf/src/utils/minimalSkeleton.js +85 -0
  122. package/busy-spence-79c6bf/src/utils/pathList.csv +903 -0
  123. package/busy-spence-79c6bf/src/utils/pathSkeleton.js +181 -0
  124. package/busy-spence-79c6bf/ta6ed6-titles-plan.md +118 -0
  125. package/busy-spence-79c6bf/ta7ed5-titles-spec.md +89 -0
  126. package/package.json +1 -1
  127. package/src/schemas/v3/combined.json +228 -50
  128. package/src/schemas/v3/overlays/llc1.json +1 -2
  129. package/src/schemas/v3/overlays/ta6ed6.json +204 -75
  130. package/src/schemas/v3/overlays/ta7ed5.json +54 -20
  131. package/src/schemas/v3/pdtf-transaction.json +7 -7
@@ -0,0 +1,633 @@
1
+ const traverse = require("traverse");
2
+ const jp = require("jsonpointer");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const combinedSchema = require("../schemas/v3/combined.json");
7
+
8
+ // Derive ref-related field names from a refType
9
+ function getRefConfig(refType) {
10
+ if (refType === "nts2Ref") {
11
+ // Legacy mapping: nts2Ref -> ntsRef in output
12
+ return {
13
+ ref: "nts2Ref",
14
+ outputRef: "ntsRef",
15
+ required: "nts2Required",
16
+ title: "nts2Title",
17
+ description: "nts2Description",
18
+ enumKey: "nts2Enum",
19
+ };
20
+ }
21
+ if (refType === "sef25Ref") {
22
+ // SEF25 extensions also appear as ntsRef in output
23
+ return {
24
+ ref: "sef25Ref",
25
+ outputRef: "ntsRef",
26
+ required: "sef25Required",
27
+ title: "sef25Title",
28
+ description: "sef25Description",
29
+ enumKey: "sef25Enum",
30
+ };
31
+ }
32
+ // For other ref types, derive keys by replacing "Ref" suffix
33
+ return {
34
+ ref: refType,
35
+ outputRef: refType,
36
+ required: refType.replace("Ref", "Required"),
37
+ title: refType.replace("Ref", "Title"),
38
+ description: refType.replace("Ref", "Description"),
39
+ enumKey: refType.replace("Ref", "Enum"),
40
+ };
41
+ }
42
+
43
+ // Define the extension overlay mappings
44
+ const extensionMappings = {
45
+ // Outside areas
46
+ oa: {
47
+ name: "outsideAreas",
48
+ description: "Outside areas extension for NTS",
49
+ paths: [
50
+ "/properties/propertyPack/properties/residentialPropertyFeatures/properties/outsideAreas",
51
+ ],
52
+ },
53
+
54
+ // Estate rentcharges for freehold
55
+ er: {
56
+ name: "estateRentcharges",
57
+ description: "Estate rentcharges for freehold properties",
58
+ paths: [
59
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/estateRentcharges",
60
+ ],
61
+ },
62
+
63
+ // Managing agent contact details for leasehold
64
+ ma: {
65
+ name: "managingAgent",
66
+ description: "Managing agent contact details for leasehold",
67
+ paths: [
68
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/contactDetails",
69
+ ],
70
+ },
71
+
72
+ // Transfer fees for leasehold
73
+ tf: {
74
+ name: "transferFees",
75
+ description: "Transfer fees for leasehold",
76
+ paths: [
77
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/2/properties/leaseholdInformation/properties/serviceCharge/oneOf/1/properties/transferFees",
78
+ ],
79
+ },
80
+
81
+ // Main construction type if standard
82
+ mc: {
83
+ name: "mainConstruction",
84
+ description: "Main construction type if standard form",
85
+ paths: [
86
+ "/properties/propertyPack/properties/typeOfConstruction/properties/isStandardForm/oneOf/0/properties/constructionType",
87
+ ],
88
+ },
89
+
90
+ // Loft access and details
91
+ la: {
92
+ name: "loftAccess",
93
+ description: "Loft access and details",
94
+ paths: [
95
+ "/properties/propertyPack/properties/typeOfConstruction/properties/loft",
96
+ ],
97
+ },
98
+
99
+ // Spray foam insulation
100
+ sf: {
101
+ name: "sprayFoam",
102
+ description: "Spray foam insulation",
103
+ paths: [
104
+ "/properties/propertyPack/properties/typeOfConstruction/properties/sprayFoamInsulation",
105
+ ],
106
+ },
107
+
108
+ // Specialist issues - dry rot
109
+ dr: {
110
+ name: "dryRot",
111
+ description: "Dry rot treatment specialist issue",
112
+ paths: [
113
+ "/properties/propertyPack/properties/specialistIssues/properties/dryRotEtcTreatment",
114
+ ],
115
+ },
116
+
117
+ // Specialist issues - asbestos
118
+ as: {
119
+ name: "asbestos",
120
+ description: "Asbestos specialist issue",
121
+ paths: [
122
+ "/properties/propertyPack/properties/specialistIssues/properties/containsAsbestos",
123
+ ],
124
+ },
125
+
126
+ // Specialist issues - Japanese knotweed
127
+ jk: {
128
+ name: "japaneseKnotweed",
129
+ description: "Japanese knotweed specialist issue",
130
+ paths: [
131
+ "/properties/propertyPack/properties/specialistIssues/properties/japaneseKnotweed",
132
+ ],
133
+ },
134
+
135
+ // Specialist issues - subsidence
136
+ sb: {
137
+ name: "subsidence",
138
+ description: "Subsidence or structural fault specialist issue",
139
+ paths: [
140
+ "/properties/propertyPack/properties/specialistIssues/properties/subsidenceOrStructuralFault",
141
+ ],
142
+ },
143
+
144
+ // Specialist issues - health and safety
145
+ hs: {
146
+ name: "healthSafety",
147
+ description: "Health and safety specialist issue",
148
+ paths: [
149
+ "/properties/propertyPack/properties/specialistIssues/properties/ongoingHealthOrSafetyIssue",
150
+ ],
151
+ },
152
+
153
+ // Solar panels leased
154
+ sl: {
155
+ name: "solarPanelsLeased",
156
+ description: "Solar panels ownership details",
157
+ paths: [
158
+ "/properties/propertyPack/properties/electricity/properties/solarPanels/oneOf/1/properties/panelsOwnedOutright",
159
+ ],
160
+ },
161
+
162
+ // Heating installation date
163
+ hi: {
164
+ name: "heatingInstalled",
165
+ description: "Central heating installation date",
166
+ paths: [
167
+ "/properties/propertyPack/properties/heating/properties/heatingSystem/oneOf/1/properties/centralHeatingDetails/properties/centralHeatingInstalled",
168
+ ],
169
+ },
170
+
171
+ // Flood defences
172
+ fd: {
173
+ name: "floodDefences",
174
+ description: "Flood defence information",
175
+ paths: [
176
+ "/properties/propertyPack/properties/environmentalIssues/properties/flooding/properties/floodDefences",
177
+ ],
178
+ },
179
+
180
+ // Other property in chain
181
+ oc: {
182
+ name: "otherPropertyChain",
183
+ description: "Other property in chain dependency",
184
+ paths: [
185
+ "/properties/propertyPack/properties/completionAndMoving/properties/otherPropertyInChain",
186
+ ],
187
+ },
188
+
189
+ // Private supply costs (water & sewerage)
190
+ sc: {
191
+ name: "supplyCosts",
192
+ description: "Associated costs for private water and sewerage supply",
193
+ refType: "sef25Ref",
194
+ paths: [
195
+ "/properties/propertyPack/properties/waterAndDrainage/properties/water/properties/mainsWater/oneOf/0/properties/associatedCost",
196
+ "/properties/propertyPack/properties/waterAndDrainage/properties/drainage/properties/mainsFoulDrainage/oneOf/2/properties/associatedCost",
197
+ ],
198
+ },
199
+
200
+ // Parking permit costs
201
+ pc: {
202
+ name: "parkingPermitCost",
203
+ description: "Parking permit cost and frequency",
204
+ refType: "sef25Ref",
205
+ paths: [
206
+ "/properties/propertyPack/properties/parking/properties/controlledParking/oneOf/1/properties/costOfPermit",
207
+ "/properties/propertyPack/properties/parking/properties/controlledParking/oneOf/1/properties/costOfPermitFrequency",
208
+ ],
209
+ },
210
+
211
+ // Property hazards
212
+ ph: {
213
+ name: "propertyHazards",
214
+ description: "Property hazards and known issues",
215
+ refType: "sef25Ref",
216
+ paths: [
217
+ "/properties/propertyPack/properties/specialistIssues",
218
+ ],
219
+ },
220
+
221
+ // Dropped kerb access to parking
222
+ dk: {
223
+ name: "droppedKerbAccess",
224
+ description: "Dropped kerb access to parking",
225
+ refType: "sef25Ref",
226
+ paths: [
227
+ "/properties/propertyPack/properties/parking/properties/droppedKerbAccess",
228
+ ],
229
+ },
230
+
231
+ // Private right of way
232
+ rw: {
233
+ name: "privateRightOfWay",
234
+ description: "Private right of way",
235
+ refType: "sef25Ref",
236
+ paths: [
237
+ "/properties/propertyPack/properties/rightsAndInformalArrangements/properties/rightsOrArrangements/properties/privateRightOfWay",
238
+ ],
239
+ },
240
+
241
+ // Storm, fire or flood damage
242
+ sd: {
243
+ name: "stormFireFloodDamage",
244
+ description: "Storm, fire or flood damage",
245
+ refType: "sef25Ref",
246
+ paths: [
247
+ "/properties/propertyPack/properties/environmentalIssues/properties/stormFireFloodDamage",
248
+ ],
249
+ },
250
+
251
+ // Solar panel lease costs
252
+ lc: {
253
+ name: "solarLeaseCosts",
254
+ description: "Solar panel lease costs",
255
+ refType: "sef25Ref",
256
+ paths: [
257
+ "/properties/propertyPack/properties/electricity/properties/solarPanels/oneOf/1/properties/panelsOwnedOutright/oneOf/1/properties/associatedCost",
258
+ ],
259
+ },
260
+
261
+ // Warranties and guarantees upfront
262
+ wg: {
263
+ name: "warrantiesGuarantees",
264
+ description: "Warranties and guarantees upfront Y/N for SEF25",
265
+ refType: "sef25Ref",
266
+ paths: [
267
+ "/properties/propertyPack/properties/guaranteesWarrantiesAndIndemnityInsurances",
268
+ ],
269
+ },
270
+
271
+ // Insurance claims upfront
272
+ ic: {
273
+ name: "insuranceClaims",
274
+ description: "Insurance claims upfront for SEF25",
275
+ refType: "sef25Ref",
276
+ paths: [
277
+ "/properties/propertyPack/properties/insurance",
278
+ ],
279
+ },
280
+
281
+ // Neighbour development upfront
282
+ nd: {
283
+ name: "neighbourDevelopment",
284
+ description: "Neighbour development upfront for SEF25",
285
+ refType: "sef25Ref",
286
+ paths: [
287
+ "/properties/propertyPack/properties/notices/properties/neighbourDevelopment",
288
+ ],
289
+ },
290
+
291
+ // Other material issue upfront
292
+ mi: {
293
+ name: "otherMaterialIssue",
294
+ description: "Other material issue upfront for SEF25",
295
+ refType: "sef25Ref",
296
+ paths: [
297
+ "/properties/propertyPack/properties/additionalInformation/properties/otherMaterialIssue",
298
+ ],
299
+ },
300
+
301
+ // Title restrictions for freehold
302
+ tr: {
303
+ name: "titleRestrictions",
304
+ description: "Title restrictions for freehold properties",
305
+ refType: "sef25Ref",
306
+ paths: [
307
+ "/properties/propertyPack/properties/ownership/properties/ownershipsToBeTransferred/items/oneOf/0/properties/titleRestrictions",
308
+ ],
309
+ },
310
+
311
+ // Alterations and changes
312
+ ac: {
313
+ name: "alterationsAndChanges",
314
+ description: "Selected alterations and changes fields for SEF25",
315
+ refType: "sef25Ref",
316
+ preserveDiscriminators: true,
317
+ paths: [
318
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/extension/properties/yesNo",
319
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/details",
320
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/planningPermission/properties/yesNo",
321
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/extension/oneOf/1/properties/buildingRegApproval/properties/yesNo",
322
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/properties/yesNo",
323
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/details",
324
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/planningPermission/properties/yesNo",
325
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/loftConversion/oneOf/1/properties/buildingRegApproval/properties/yesNo",
326
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/properties/yesNo",
327
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/details",
328
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/planningPermission/properties/yesNo",
329
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/garageConversion/oneOf/1/properties/buildingRegApproval/properties/yesNo",
330
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/properties/yesNo",
331
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/details",
332
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/planningPermission/properties/yesNo",
333
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/removalOfInternalWalls/oneOf/1/properties/buildingRegApproval/properties/yesNo",
334
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/properties/yesNo",
335
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/details",
336
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/planningPermission/properties/yesNo",
337
+ "/properties/propertyPack/properties/alterationsAndChanges/properties/changeOfUse/oneOf/1/properties/buildingRegApproval/properties/yesNo",
338
+ ],
339
+ },
340
+
341
+ // TA6 edition 6 compatibility for shared MI/NTS validation
342
+ ta: {
343
+ name: "ta6Ed6Compatibility",
344
+ description:
345
+ "Compatibility enum ranges for validating shared PDTF state containing TA6 edition 6 answers",
346
+ refType: "ta6ed6CompatRef",
347
+ paths: [
348
+ "/properties/propertyPack/properties/parking",
349
+ "/properties/propertyPack/properties/heating",
350
+ "/properties/propertyPack/properties/rightsAndInformalArrangements",
351
+ "/properties/propertyPack/properties/legalBoundaries",
352
+ ],
353
+ },
354
+ };
355
+
356
+ // Function to extract properties at specific paths with given ref type
357
+ function extractPathsWithRef(
358
+ schema,
359
+ paths,
360
+ refConfig,
361
+ preserveDiscriminators = false
362
+ ) {
363
+ const result = {};
364
+
365
+ paths.forEach((path) => {
366
+ try {
367
+ const value = jp.get(schema, path);
368
+ if (value && hasRef(value, refConfig)) {
369
+ // Set the value at the same path in result
370
+ jp.set(result, path, extractRefProperties(value, refConfig));
371
+ if (preserveDiscriminators) {
372
+ addAncestorDiscriminators(result, schema, path);
373
+ }
374
+ }
375
+ } catch (err) {
376
+ console.warn(`Path not found: ${path}`);
377
+ }
378
+ });
379
+
380
+ return result;
381
+ }
382
+
383
+ function addAncestorDiscriminators(result, schema, path) {
384
+ const parts = path.split("/").filter(Boolean);
385
+
386
+ for (let index = 1; index <= parts.length; index += 1) {
387
+ const ancestorPath = `/${parts.slice(0, index).join("/")}`;
388
+
389
+ try {
390
+ const schemaNode = jp.get(schema, ancestorPath);
391
+ if (!schemaNode?.discriminator) continue;
392
+
393
+ const resultNode = jp.get(result, ancestorPath);
394
+ if (resultNode && typeof resultNode === "object") {
395
+ resultNode.discriminator = schemaNode.discriminator;
396
+ }
397
+ } catch (err) {
398
+ // Ancestor was not materialised by this selected path.
399
+ }
400
+ }
401
+ }
402
+
403
+ // Check if an object or its descendants have the specified ref
404
+ function hasRef(obj, refConfig) {
405
+ let found = false;
406
+ traverse(obj).forEach(function (element) {
407
+ if (element && element[refConfig.ref]) {
408
+ found = true;
409
+ this.stop();
410
+ }
411
+ });
412
+ return found;
413
+ }
414
+
415
+ // Extract only ref-specific properties
416
+ function extractRefProperties(obj, refConfig, parentKey) {
417
+ const result = {};
418
+
419
+ // Copy ref-specific metadata at current level
420
+ if (obj[refConfig.ref]) result[refConfig.outputRef] = obj[refConfig.ref];
421
+ if (obj[refConfig.required]) result.required = obj[refConfig.required];
422
+ if (obj[refConfig.title]) result.title = obj[refConfig.title];
423
+ if (obj[refConfig.description]) result.description = obj[refConfig.description];
424
+ if (obj[refConfig.enumKey]) result.enum = obj[refConfig.enumKey];
425
+
426
+ // Handle discriminator
427
+ if (obj.discriminator) {
428
+ result.discriminator = obj.discriminator;
429
+ }
430
+
431
+ // Recursively process properties
432
+ if (obj.properties) {
433
+ result.properties = {};
434
+ Object.keys(obj.properties).forEach((key) => {
435
+ const prop = obj.properties[key];
436
+ if (hasRef(prop, refConfig)) {
437
+ result.properties[key] = extractRefProperties(prop, refConfig, key);
438
+ }
439
+ });
440
+ // Only keep properties if we found some with ref
441
+ if (Object.keys(result.properties).length === 0) {
442
+ delete result.properties;
443
+ }
444
+ }
445
+
446
+ // Handle arrays
447
+ if (obj.items) {
448
+ if (hasRef(obj.items, refConfig)) {
449
+ result.items = extractRefProperties(obj.items, refConfig);
450
+ }
451
+ }
452
+
453
+ // Handle oneOf
454
+ if (obj.oneOf) {
455
+ // Always preserve the full array structure and order
456
+ const processedOneOf = obj.oneOf.map((schema, index) => {
457
+ if (hasRef(schema, refConfig)) {
458
+ const extracted = extractRefProperties(schema, refConfig);
459
+ // For oneOf schemas, we need to preserve discriminator enum values
460
+ if (obj.discriminator && schema.properties) {
461
+ const propName = obj.discriminator.propertyName;
462
+ if (schema.properties[propName]) {
463
+ if (!extracted.properties) extracted.properties = {};
464
+ extracted.properties[propName] = {
465
+ enum:
466
+ schema.properties[propName][refConfig.enumKey] ||
467
+ schema.properties[propName].enum,
468
+ };
469
+ }
470
+ }
471
+ // Also preserve base enum values for all other properties in the extension
472
+ if (schema.properties) {
473
+ if (!extracted.properties) extracted.properties = {};
474
+ Object.keys(schema.properties).forEach((propName) => {
475
+ const prop = schema.properties[propName];
476
+ if (prop.enum) {
477
+ if (!extracted.properties[propName]) {
478
+ extracted.properties[propName] = {
479
+ enum: prop.enum,
480
+ };
481
+ } else if (!extracted.properties[propName].enum) {
482
+ // If the property exists but doesn't have enum, add it
483
+ extracted.properties[propName].enum = prop.enum;
484
+ }
485
+ }
486
+ });
487
+ }
488
+ return extracted;
489
+ } else {
490
+ // If this branch doesn't have ref, preserve the base discriminator enum
491
+ if (obj.discriminator && schema.properties) {
492
+ const propName = obj.discriminator.propertyName;
493
+ if (schema.properties[propName]) {
494
+ return {
495
+ properties: {
496
+ [propName]: {
497
+ enum: schema.properties[propName].enum,
498
+ },
499
+ },
500
+ };
501
+ }
502
+ }
503
+ // If no discriminator, return empty object to preserve structure
504
+ return {};
505
+ }
506
+ });
507
+ result.oneOf = processedOneOf;
508
+
509
+ // --- NEW: If this is an extension property inside a oneOf, promote it to the parent overlay ---
510
+ // If parentKey is set, and this object has extension metadata, return a stub for the parent
511
+ if (
512
+ parentKey &&
513
+ (result[refConfig.outputRef] || result.required || result.discriminator)
514
+ ) {
515
+ // This is an extension property inside a oneOf branch
516
+ // Return a stub for the parent property with metadata and oneOf structure
517
+ const parentStub = {};
518
+ Object.assign(parentStub, result);
519
+ return parentStub;
520
+ }
521
+ }
522
+
523
+ // Copy other JSON Schema keywords that might be present
524
+ [
525
+ "type",
526
+ "format",
527
+ "minimum",
528
+ "maximum",
529
+ "minLength",
530
+ "maxLength",
531
+ "minItems",
532
+ "maxItems",
533
+ ].forEach((keyword) => {
534
+ if (obj[keyword] !== undefined) {
535
+ result[keyword] = obj[keyword];
536
+ }
537
+ });
538
+
539
+ return result;
540
+ }
541
+
542
+ // Add required array at parent levels if needed
543
+ function addRequiredArrays(overlay, schema, refConfig) {
544
+ traverse(overlay).forEach(function (node) {
545
+ if (node && node.properties) {
546
+ const schemaPath = "/" + this.path.join("/");
547
+ try {
548
+ const schemaNode = jp.get(schema, schemaPath);
549
+ if (schemaNode && schemaNode[refConfig.required]) {
550
+ // Filter to only include properties that exist in this overlay
551
+ const overlayProps = Object.keys(node.properties);
552
+ const requiredProps = schemaNode[refConfig.required].filter((prop) =>
553
+ overlayProps.includes(prop)
554
+ );
555
+ if (requiredProps.length > 0) {
556
+ node.required = requiredProps;
557
+ }
558
+ }
559
+ } catch (err) {
560
+ // Path doesn't exist in schema, skip
561
+ }
562
+ }
563
+ });
564
+ return overlay;
565
+ }
566
+
567
+ // Annotate intermediate nodes with refs from the source schema
568
+ // This ensures every level of the overlay tree carries an ntsRef
569
+ function addIntermediateRefs(overlay, schema, refConfig) {
570
+ traverse(overlay).forEach(function (node) {
571
+ if (node && typeof node === "object" && !Array.isArray(node) && node.properties) {
572
+ // Skip if this node already has a ref
573
+ if (node[refConfig.outputRef]) return;
574
+
575
+ const schemaPath = "/" + this.path.join("/");
576
+ try {
577
+ const schemaNode = jp.get(schema, schemaPath);
578
+ if (!schemaNode) return;
579
+ // When the output key differs from the source key (e.g. sef25Ref -> ntsRef),
580
+ // prefer the schema's existing outputRef on intermediate parents so we
581
+ // don't clobber legitimate NTS refs when the extension is later merged.
582
+ if (
583
+ refConfig.ref !== refConfig.outputRef &&
584
+ schemaNode[refConfig.outputRef]
585
+ ) {
586
+ node[refConfig.outputRef] = schemaNode[refConfig.outputRef];
587
+ } else if (schemaNode[refConfig.ref]) {
588
+ node[refConfig.outputRef] = schemaNode[refConfig.ref];
589
+ }
590
+ } catch (err) {
591
+ // Path doesn't exist in schema, skip
592
+ }
593
+ }
594
+ });
595
+ return overlay;
596
+ }
597
+
598
+ // Generate extension overlays
599
+ Object.entries(extensionMappings).forEach(([code, config]) => {
600
+ const refType = config.refType || "nts2Ref";
601
+ const refConfig = getRefConfig(refType);
602
+
603
+ console.log(`\nGenerating extension overlay: ${code} (${config.name}) [${refType}]`);
604
+
605
+ // Extract the specific paths
606
+ let overlay = extractPathsWithRef(
607
+ combinedSchema,
608
+ config.paths,
609
+ refConfig,
610
+ config.preserveDiscriminators
611
+ );
612
+
613
+ // Add required arrays where needed
614
+ overlay = addRequiredArrays(overlay, combinedSchema, refConfig);
615
+
616
+ // Add refs to intermediate nodes in the path hierarchy
617
+ overlay = addIntermediateRefs(overlay, combinedSchema, refConfig);
618
+
619
+ // Add schema metadata
620
+ overlay.$schema = "http://json-schema.org/draft-07/schema#";
621
+ overlay.$id = `https://trust.propdata.org.uk/schemas/v3/overlays/extensions/${code}.json`;
622
+ overlay.$comment = config.description;
623
+
624
+ // Write the overlay file
625
+ const fileName = path.join(
626
+ __dirname,
627
+ `../schemas/v3/overlays/extensions/${code}.json`
628
+ );
629
+ fs.writeFileSync(fileName, JSON.stringify(overlay, null, 2));
630
+ console.log(`Extension overlay ${code} written to ${fileName}`);
631
+ });
632
+
633
+ console.log("\nExtension overlay extraction complete!");