@pdtf/schemas 3.6.0-dev.7 → 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 (130) 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 +225 -47
  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
@@ -0,0 +1,181 @@
1
+ const fs = require("fs");
2
+ const traverse = require("traverse");
3
+
4
+ const combinedSchema = require("../schemas/v3/combined.json");
5
+
6
+ // Extract fields that have overlay-specific properties
7
+ const extractFields = [
8
+ "baspi4", "baspi5", "nts", "nts2", "ntsl", "ntsl2",
9
+ "ta6", "ta7", "ta7ed5", "ta10", "lpe1", "fme1", "piq",
10
+ "con29R", "con29DW", "llc1", "rds", "oc1", "sr24",
11
+ ];
12
+
13
+ // Delete properties we don't want in the skeleton
14
+ const deleteProperties = (sourceSchema, propertyNames) => {
15
+ traverse(sourceSchema).forEach(function (element) {
16
+ if (propertyNames.includes(this.key)) {
17
+ this.delete(true);
18
+ }
19
+ });
20
+ return sourceSchema;
21
+ };
22
+
23
+ // Collect all paths in path notation
24
+ const collectPaths = (schema, currentPath = "") => {
25
+ const paths = [];
26
+
27
+ if (!schema || typeof schema !== 'object') {
28
+ return paths;
29
+ }
30
+
31
+ // Handle arrays
32
+ if (schema.type === "array" && schema.items) {
33
+ const arrayPath = currentPath + "[]";
34
+ paths.push(arrayPath);
35
+ // Continue with items
36
+ const itemPaths = collectPaths(schema.items, arrayPath);
37
+ paths.push(...itemPaths);
38
+ return paths;
39
+ }
40
+
41
+ // Handle primitives - add type suffix
42
+ if (schema.type && !schema.properties && !schema.oneOf && !schema.items) {
43
+ let typeChar = "";
44
+ switch(schema.type) {
45
+ case "string": typeChar = ":s"; break;
46
+ case "number": typeChar = ":n"; break;
47
+ case "integer": typeChar = ":i"; break;
48
+ case "boolean": typeChar = ":b"; break;
49
+ default: typeChar = "";
50
+ }
51
+ if (currentPath) {
52
+ paths.push(currentPath + typeChar);
53
+ }
54
+ return paths;
55
+ }
56
+
57
+ // Handle objects with properties
58
+ if (schema.properties) {
59
+ // If this is an object without a path yet, don't add it
60
+ if (currentPath && !schema.type?.includes("array")) {
61
+ paths.push(currentPath);
62
+ }
63
+
64
+ Object.keys(schema.properties).forEach((propName) => {
65
+ const newPath = currentPath ? `${currentPath}.${propName}` : propName;
66
+ const propPaths = collectPaths(schema.properties[propName], newPath);
67
+ paths.push(...propPaths);
68
+ });
69
+ }
70
+
71
+ // Handle oneOf - collect all possible properties
72
+ if (schema.oneOf) {
73
+ const variantProps = new Set();
74
+ schema.oneOf.forEach((oneOfSchema) => {
75
+ if (oneOfSchema.properties) {
76
+ Object.keys(oneOfSchema.properties).forEach((propName) => {
77
+ variantProps.add(propName);
78
+ });
79
+ }
80
+ });
81
+
82
+ // Process each unique property
83
+ variantProps.forEach((propName) => {
84
+ const newPath = currentPath ? `${currentPath}.${propName}` : propName;
85
+ // Check if all variants have the same type
86
+ let commonType = null;
87
+ let isVariant = false;
88
+
89
+ schema.oneOf.forEach((oneOfSchema) => {
90
+ if (oneOfSchema.properties && oneOfSchema.properties[propName]) {
91
+ const propType = oneOfSchema.properties[propName].type;
92
+ if (commonType === null) {
93
+ commonType = propType;
94
+ } else if (commonType !== propType) {
95
+ isVariant = true;
96
+ }
97
+ }
98
+ });
99
+
100
+ if (isVariant || !commonType) {
101
+ paths.push(newPath + ":v"); // variant
102
+ } else {
103
+ // Process the first variant's version of this property
104
+ for (const oneOfSchema of schema.oneOf) {
105
+ if (oneOfSchema.properties && oneOfSchema.properties[propName]) {
106
+ const propPaths = collectPaths(oneOfSchema.properties[propName], newPath);
107
+ paths.push(...propPaths);
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ });
113
+ }
114
+
115
+ // Empty objects
116
+ if (currentPath && !schema.properties && !schema.oneOf && !schema.items && !schema.type) {
117
+ paths.push(currentPath);
118
+ }
119
+
120
+ return paths;
121
+ };
122
+
123
+ // Remove duplicates
124
+ const deduplicatePaths = (paths) => {
125
+ return [...new Set(paths)];
126
+ };
127
+
128
+ // Sort paths for readability
129
+ const sortPaths = (paths) => {
130
+ return paths.sort((a, b) => {
131
+ // Sort by depth first (fewer dots = higher up)
132
+ const depthA = (a.match(/\./g) || []).length;
133
+ const depthB = (b.match(/\./g) || []).length;
134
+ if (depthA !== depthB) return depthA - depthB;
135
+
136
+ // Then alphabetically
137
+ return a.localeCompare(b);
138
+ });
139
+ };
140
+
141
+ // Create clean schema
142
+ const coreSchema = deleteProperties(JSON.parse(JSON.stringify(combinedSchema)), [
143
+ "discriminator",
144
+ ...extractFields.map((item) => `${item}Ref`),
145
+ ...extractFields.map((item) => `${item}Required`),
146
+ ...extractFields.map((item) => `${item}Title`),
147
+ ...extractFields.map((item) => `${item}Description`),
148
+ ...extractFields.map((item) => `${item}Enum`),
149
+ ]);
150
+
151
+ // Remove metadata
152
+ const skeletonSchema = deleteProperties(coreSchema, [
153
+ "$schema", "$id", "title", "description", "required",
154
+ "enum", "minItems", "minLength", "format", "minimum", "maximum",
155
+ ]);
156
+
157
+ // Collect paths
158
+ const allPaths = collectPaths(skeletonSchema);
159
+ const uniquePaths = deduplicatePaths(allPaths);
160
+ const sortedPaths = sortPaths(uniquePaths);
161
+
162
+ // Write as text file
163
+ const pathContent = sortedPaths.join('\n');
164
+ fs.writeFileSync("../schemas/v3/pathSkeleton.txt", pathContent);
165
+
166
+ // Also create a JSON version for comparison
167
+ fs.writeFileSync("../schemas/v3/pathSkeleton.json", JSON.stringify(sortedPaths, null, 2));
168
+
169
+ console.log(`Path skeleton written to ../schemas/v3/pathSkeleton.txt`);
170
+ console.log(`Total paths: ${sortedPaths.length}`);
171
+ console.log(`Text size: ${pathContent.length} bytes`);
172
+
173
+ // Count tokens
174
+ try {
175
+ const GPT3Encoder = require('gpt-3-encoder');
176
+ const encoded = GPT3Encoder.encode(pathContent);
177
+ console.log(`Tokens (GPT-3): ${encoded.length}`);
178
+ console.log(`Reduction: ${((1 - encoded.length / 78734) * 100).toFixed(1)}% fewer tokens than JSON skeleton`);
179
+ } catch (e) {
180
+ console.log(`Estimated tokens: ~${Math.ceil(pathContent.length / 4)}`);
181
+ }
@@ -0,0 +1,118 @@
1
+ # Plan: Exact TA6 6th-edition field titles (`ta6ed6Title`) in combined.json
2
+
3
+ ## Context
4
+
5
+ The Law Society requires that when a TA6 form is rendered digitally, field labels match the
6
+ published form **exactly**. The PDTF schema already carries form-specific labels for other forms
7
+ via `<form>Title` properties (e.g. `ta6Title`, `ntsTitle`). The TA6 6th-edition (2025) layer was
8
+ recently added: `combined.json` now has **537 `ta6ed6Ref`** markers identifying which schema
9
+ properties belong to the 6th-edition form, and **51 `ta6ed6Title`** values already applied. The
10
+ remaining 6th-edition fields still fall back to the generic base `title`, whose wording differs
11
+ from the Law Society form.
12
+
13
+ This change completes the `ta6ed6Title` coverage so that **every** 6th-edition field whose form
14
+ wording differs from the base `title` carries its exact Law Society label, verified against the
15
+ source form PDF.
16
+
17
+ ## Architecture (verified)
18
+
19
+ - **Source of truth:** `src/schemas/v3/combined.json` is hand-edited. It is the only place the 51
20
+ existing `ta6ed6Title` values live (the `ta6ed6.json` overlay has the 537 refs but 0 titles —
21
+ it is simply stale).
22
+ - **Generated artifacts:** `src/utils/extractOverlay.js` reads `combined.json` and regenerates the
23
+ per-form overlay files (incl. `overlays/ta6ed6.json`) and the core schema
24
+ (`pdtf-transaction.json`). Its `extractFields` list already includes `"ta6ed6"`, so it extracts
25
+ `ta6ed6Ref`, `ta6ed6Title`, `ta6ed6Required`, `ta6ed6Enum`, `ta6ed6Type`, etc. Running it after
26
+ editing propagates the new titles into the overlay.
27
+ - **Field identity:** a property belongs to the 6th-edition form iff it has a `ta6ed6Ref`. The ref
28
+ value (e.g. `10.2`, `5.9a`) matches the `ref` keys in the extracted form file
29
+ `tmp/ta6ed6-fields.json`, which was validated against the PDF (e.g. ref `10.2` →
30
+ "Is a permit required for on-road parking?").
31
+
32
+ ## Authoritative title sources
33
+
34
+ 1. `tmp/TA6 Form 6th edition.pdf` — the Law Society source form (20 pages, text-based). **The
35
+ final authority** per the user's "re-verify all against PDF" decision.
36
+ 2. `tmp/ta6ed6-fields.json` — clean extraction keyed by `ref` (16 sections, ~195 labelled fields).
37
+ Used as the working map; every title applied must be reconciled against the PDF text.
38
+
39
+ ## House style for titles (from `tmp/ta6ed6-mapping-report.md` §C.5 — matches the existing 51)
40
+
41
+ A `ta6ed6Title` is the exact form question/field text, with:
42
+ 1. Sub-question prefixes stripped — `"(a) "`, `"(b) "`, `"(i) "`, `"(ii) "` (hierarchy is carried
43
+ by `ta6ed6Ref` such as `5.6a`, `5.6g(i)`).
44
+ 2. Conditional prefixes stripped — `"If yes, "`, `"If no, "` (implicit from the `oneOf` branch).
45
+ 3. First letter capitalised after any stripping.
46
+ Otherwise verbatim — punctuation, casing, and wording must match the form character-for-character.
47
+
48
+ ## Scope decision (confirmed with user)
49
+
50
+ - **Base branch:** rebase this work onto `dev` (which holds the 537 refs + 51 titles + the
51
+ `ta6ed6.json` overlay). This worktree is currently off `master`, which has none of the ta6ed6
52
+ layer. PR target: `dev`.
53
+ - **Which fields:** add `ta6ed6Title` **only where the 6th-edition label differs** from the
54
+ property's existing base `title`. Skip fields whose base `title` already matches the form.
55
+ Also re-verify the existing 51 against the PDF and correct any that are wrong.
56
+ - **Sub-components** (`yesNo`, `.1`/`.2` answer parts that have no distinct printed label) inherit
57
+ the parent question label and do **not** receive their own `ta6ed6Title`.
58
+
59
+ ## Steps
60
+
61
+ ### 1. Rebase onto dev
62
+ The worktree branch `claude/busy-spence-79c6bf` equals `master` (clean, no work commits), so:
63
+ `git reset --hard dev`. Confirm `combined.json` then shows 537 `ta6ed6Ref` / 51 `ta6ed6Title`.
64
+
65
+ ### 2. Build the canonical title map
66
+ - Read the full PDF (pages 1–20) and transcribe the exact label for every form ref.
67
+ - Load `tmp/ta6ed6-fields.json` and reconcile each entry against the transcribed PDF text;
68
+ correct the JSON map where the extraction and PDF disagree (PDF wins).
69
+ - Produce an in-memory map `ta6ed6Ref → canonicalTitle` (after applying the §C.5 house style).
70
+
71
+ ### 3. Apply titles to `combined.json`
72
+ Walk `combined.json`. For each object carrying a `ta6ed6Ref` that maps to a canonical label:
73
+ - If `canonicalTitle !== base title` → set/replace `ta6ed6Title` with the canonical value, placed
74
+ immediately **after** the `title` property (matching the existing convention:
75
+ `…refs, "title", "ta6ed6Title", "type"…`).
76
+ - If `canonicalTitle === base title` → ensure no redundant `ta6ed6Title` is present.
77
+ - Re-verify each of the existing 51 and correct mismatches.
78
+ Preserve JSON formatting (2-space indent, key order) to keep the diff reviewable. Where a single
79
+ form field maps to two refs (e.g. `documents` + `attachments` sharing a ref, per §C.4), title the
80
+ user-facing question property, not the attachment helper.
81
+
82
+ ### 4. Regenerate derived artifacts
83
+ Run `node src/utils/extractOverlay.js` so the new titles propagate into `overlays/ta6ed6.json`
84
+ (and refresh the core schema). Confirm `overlays/ta6ed6.json` `ta6ed6Title` count now matches the
85
+ number of titled fields. Regenerate skeletons too if the repo's release flow does (see recent
86
+ commit "Regenerate overlays and skeletons").
87
+
88
+ ## Verification
89
+
90
+ 1. **Exactness audit script (read-only):** generate a table of every `ta6ed6Ref` → applied
91
+ `ta6ed6Title` (or inherited base `title`) and diff it against the PDF-derived canonical map.
92
+ Output must show **zero** unexplained mismatches. Save the report alongside the existing
93
+ `tmp/ta6ed6-mapping-report.md` for reviewer sign-off.
94
+ 2. **Schema validity:** `npm test` (jest) — all suites green, especially
95
+ `src/tests/v3/ta6ed6.test.js` and `transactionSchema.test.js`.
96
+ 3. **Overlay parity:** assert every `ta6ed6Title` in `combined.json` is present in the regenerated
97
+ `overlays/ta6ed6.json` (proves the generation step captured them).
98
+ 4. **Spot render check:** optionally load a ta6ed6 transaction via `getTransactionSchema` /
99
+ `getValidator` to confirm titles surface as expected.
100
+
101
+ ## Critical files
102
+
103
+ - `src/schemas/v3/combined.json` — primary edit target.
104
+ - `src/schemas/v3/overlays/ta6ed6.json`, `src/schemas/v3/pdtf-transaction.json` — regenerated, not
105
+ hand-edited.
106
+ - `src/utils/extractOverlay.js` — generation script (already lists `ta6ed6`).
107
+ - `tmp/TA6 Form 6th edition.pdf`, `tmp/ta6ed6-fields.json`, `tmp/ta6ed6-mapping-report.md` —
108
+ title sources / reference (git-ignored; not committed).
109
+ - `src/tests/v3/ta6ed6.test.js` — existing test coverage.
110
+
111
+ ## Open considerations
112
+
113
+ - The audit in step-4/verification may surface base-schema labels that are *already* exactly right
114
+ for the form (no `ta6ed6Title` needed) — these are correctly left untouched under the
115
+ "only where wording differs" decision.
116
+ - A handful of refs are overlay-only discriminators / aggregations with no printed form label
117
+ (e.g. `areBoundariesUniform`, `isInsured`, `offMainsDrainageSystemType`; see mapping report
118
+ §C.1). These get no `ta6ed6Title`.
@@ -0,0 +1,89 @@
1
+ # Spec: Exact TA7 5th-edition field titles (`ta7ed5Title`)
2
+
3
+ ## Context
4
+
5
+ Mirror of the TA6 6th-edition work: add `ta7ed5Title` properties carrying the verbatim
6
+ Law Society *TA7 Leasehold Information Form (5th edition) (2025)* labels, so the form renders
7
+ with compliant labelling. Source verified against `TA7-5th-edition.pdf` (9 pages, 12 sections),
8
+ extracted with `pdftotext`.
9
+
10
+ ## Audit (current state on `dev`)
11
+
12
+ - `ta7ed5Ref` markers in `combined.json`: **156**
13
+ - Existing `ta7ed5Title`: **20** (sections 6, 7, 10, 11 mostly done; some retain trailing colons)
14
+ - Candidates (non-empty base title, no `ta7ed5Title`): **109**
15
+ - Empty base title (yesNo/answer components — inherit, no title): 27
16
+ - Overlay `overlays/ta7ed5.json` exists; titles propagate via `extractOverlay.js` as `title`.
17
+
18
+ ## House style (same as TA6, confirmed)
19
+
20
+ Strip `(a)`/`(i)` and `If yes,`/`If no,` prefixes; omit trailing colons; **no override when a
21
+ colon is the only difference**; `ta7ed5Title` placed immediately after `title`. Use the form's
22
+ exact wording incl. its curly apostrophes (e.g. landlord’s). Also re-verify the existing 20 and
23
+ strip their trailing colons (4 have them: 8.1.2, 11.2.2, 11.4.2, 11.6.2).
24
+
25
+ ## Proposed changes (clear adds — form question/field wording differs from base)
26
+
27
+ | Ref | Property | ta7ed5Title (exact form text, normalised) |
28
+ |-----|----------|-------------------------------------------|
29
+ | 1.1 | address | Address of the property |
30
+ | 1.1.3 | uprn | UPRN (if known) |
31
+ | 4.4.1 | sellerOwnership.yesNo | Does the seller alone, or jointly with others, own the freehold, any headlease or a management company named in the lease? |
32
+ | 4.4.2 | sellerOwnership.attachments | Supply evidence of your ownership or share certificate for membership of the organisation that is responsible for the management of the building. |
33
+ | 5.3 | sellerContributesToServiceCharge | Do you contribute to the cost of maintaining the building? |
34
+ | 5.4 | largeAdditionalExpense | Do you know of any expense (e.g. the cost of redecoration of outside or communal areas that is not usually incurred annually) that is likely to be shown in the service charge accounts within the next three years? |
35
+ | 5.4.2 | details | Please give details |
36
+ | 5.5 | serviceChargeChallenged | Have you challenged the service charge or any expense in the last three years? |
37
+ | 5.6 | problemInServiceChargeLevel | Do you know of any issues in the last three years regarding the level of service charges or with the management? |
38
+ | 7.3 | sellerAwareOfResponse | Are you aware of any response to a notice disclosed in replies to 7.1 and 7.2 above? |
39
+ | 8.1 | changesInTermOfLease | Are you aware of any changes to the terms of the lease? |
40
+ | 9.1 | sellerAwareOfAlterations | Are you aware of any alterations having been made to the property since the lease was originally granted? |
41
+
42
+ (8.2 `landlordConsents`, 8.2.2 `details`, 9.2 `details`, 9.3 `landlordConsentObtained` already match the
43
+ form once colon/case-normalised → no override; confirmed at implementation.)
44
+
45
+ ## Decisions to confirm (judgment calls — see questions)
46
+
47
+ **A. Section / sub-group headings** where the schema container name differs from the form heading:
48
+ - 3 `contactDetails` "Contact Details" → **"Contact details"**
49
+ - 3.1.2 `managementCompany` "Management Company" → **"Management company"**
50
+ - 10 `disputes` "Disputes" → **"Complaints"** (form section 10 is "Complaints")
51
+ - 11 `buildingSafetyAct` "Building safety" → **"Building safety for blocks of flats"**
52
+
53
+ **B. Per-contact field labels** (forms label these "Name:" / "Email:"), 5 contacts in §3 + managing
54
+ agent in §4 — ~14 tiny fields:
55
+ - `nameOrOrganisation` "Name or organisation" → **"Name"**
56
+ - `emailAddress` "Email address" → **"Email"**
57
+ - (`address`/`postcode`/`line1` already match or have no distinct form label → skip)
58
+
59
+ **C. "Who collects" questions (§4.2/4.3)** — schema frames these as *who collects*, the form asks a
60
+ yes/no first:
61
+ - 4.2 `collectsbuildingInsurancePremiums` → form 4.2 "Does the managing agent arrange or collect charges for the insurance of the building?"
62
+ - 4.3 `collectsGroundRent` → form 4.3 "If ground rent is payable, is it collected by the managing agent?"
63
+ - (sub-collectors `collector`/`otherCollector` → leave, or map to form's "Other (please provide details)")
64
+
65
+ **D. §2.2 payment-demand fields** — form 2.2 is one question with (a)/(b)/(c) sub-items:
66
+ - Option 1: label the demand-status fields with sub-item names — 2.2a → "Ground rent", 2.2b → "Service charge", 2.2c → "Buildings insurance"
67
+ - Option 2: label each with the full 2.2 question text
68
+
69
+ **E. §5.1/5.2 decoration years** — schema models `externally`/`internally` containers with a `.year`:
70
+ - 5.1 `externally` → "In what year was the outside of the building last decorated?"
71
+ - 5.2 `internally` → "In what year were any internal communal parts last decorated?"
72
+
73
+ ## Intentionally NOT titled (no distinct printed TA7 label / structural / overlay-only)
74
+
75
+ Section containers `ownership`, `ownershipsToBeTransferred`, `leaseholdInformation`,
76
+ `ownershipAndManagement`, `requiredDocuments`, `lastDecoratedPeriod`, `contacts`; non-TA7 fields
77
+ `ownershipType`, `isGroundRentPayable`, `typeOfConstruction`; per-line address fields; `13`
78
+ confirmation/declaration block (no numbered form heading); `attachments`/`details` that already
79
+ match the form's "Please supply a copy" / "Please give details".
80
+
81
+ ## Out of scope
82
+
83
+ Any TA7 questions lacking a `ta7ed5Ref` (ref-completion is separate, per `fix/ta7ed5-*` work).
84
+
85
+ ## Plan after sign-off
86
+
87
+ Replicate the TA6 mechanism: a `scripts/ta7ed5-titles.js` map keyed by `ref::segment`, surgical
88
+ `jsonc-parser` inserts (after `title`), prefix/colon normalisation incl. existing 20, regenerate
89
+ `ta7ed5.json` via `extractOverlay.js`, run `npm test`, audit all titles vs the form. Branch off `dev`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pdtf/schemas",
3
- "version": "3.6.0-dev.7",
3
+ "version": "3.6.0-dev.8",
4
4
  "description": "Property Data Trust Framework Schemas and Utilities",
5
5
  "main": "index.js",
6
6
  "directories": {