@parseo/appraisals 1.0.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 (50) hide show
  1. package/README.md +35 -0
  2. package/dist/form-1004mc/extract-checkboxes.d.ts +43 -0
  3. package/dist/form-1004mc/extract-checkboxes.d.ts.map +1 -0
  4. package/dist/form-1004mc/extract-checkboxes.js +145 -0
  5. package/dist/form-1004mc/index.d.ts +3 -0
  6. package/dist/form-1004mc/index.d.ts.map +1 -0
  7. package/dist/form-1004mc/index.js +1 -0
  8. package/dist/form-1004mc/parse-page1.d.ts +8 -0
  9. package/dist/form-1004mc/parse-page1.d.ts.map +1 -0
  10. package/dist/form-1004mc/parse-page1.js +760 -0
  11. package/dist/form-1004mc/parse-sales.d.ts +6 -0
  12. package/dist/form-1004mc/parse-sales.d.ts.map +1 -0
  13. package/dist/form-1004mc/parse-sales.js +505 -0
  14. package/dist/form-1004mc/parser.d.ts +5 -0
  15. package/dist/form-1004mc/parser.d.ts.map +1 -0
  16. package/dist/form-1004mc/parser.js +437 -0
  17. package/dist/form-1004mc/types.d.ts +302 -0
  18. package/dist/form-1004mc/types.d.ts.map +1 -0
  19. package/dist/form-1004mc/types.js +1 -0
  20. package/dist/form-1073/index.d.ts +3 -0
  21. package/dist/form-1073/index.d.ts.map +1 -0
  22. package/dist/form-1073/index.js +1 -0
  23. package/dist/form-1073/parse-page1.d.ts +8 -0
  24. package/dist/form-1073/parse-page1.d.ts.map +1 -0
  25. package/dist/form-1073/parse-page1.js +704 -0
  26. package/dist/form-1073/parse-page2.d.ts +6 -0
  27. package/dist/form-1073/parse-page2.d.ts.map +1 -0
  28. package/dist/form-1073/parse-page2.js +438 -0
  29. package/dist/form-1073/parse-sales.d.ts +7 -0
  30. package/dist/form-1073/parse-sales.d.ts.map +1 -0
  31. package/dist/form-1073/parse-sales.js +477 -0
  32. package/dist/form-1073/parser.d.ts +5 -0
  33. package/dist/form-1073/parser.d.ts.map +1 -0
  34. package/dist/form-1073/parser.js +102 -0
  35. package/dist/form-1073/types.d.ts +300 -0
  36. package/dist/form-1073/types.d.ts.map +1 -0
  37. package/dist/form-1073/types.js +1 -0
  38. package/dist/index.d.ts +13 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +9 -0
  41. package/dist/richer-values/index.d.ts +3 -0
  42. package/dist/richer-values/index.d.ts.map +1 -0
  43. package/dist/richer-values/index.js +1 -0
  44. package/dist/richer-values/parser.d.ts +5 -0
  45. package/dist/richer-values/parser.d.ts.map +1 -0
  46. package/dist/richer-values/parser.js +1067 -0
  47. package/dist/richer-values/types.d.ts +225 -0
  48. package/dist/richer-values/types.d.ts.map +1 -0
  49. package/dist/richer-values/types.js +1 -0
  50. package/package.json +24 -0
@@ -0,0 +1,6 @@
1
+ import type { TextLine } from "@parseo/shared";
2
+ import type { ProjectAnalysisSection, UnitDescriptionSection, PriorSaleHistorySection } from "./types.js";
3
+ export declare function parseProjectAnalysisSection(lines: TextLine[]): ProjectAnalysisSection;
4
+ export declare function parseUnitDescriptionSection(lines: TextLine[]): UnitDescriptionSection;
5
+ export declare function parsePriorSaleHistorySection(lines: TextLine[]): PriorSaleHistorySection;
6
+ //# sourceMappingURL=parse-page2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-page2.d.ts","sourceRoot":"","sources":["../../src/form-1073/parse-page2.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,gBAAgB,CAAC;AAC5D,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EAExB,MAAM,YAAY,CAAC;AAqCpB,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,sBAAsB,CAmHrF;AAID,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,sBAAsB,CA8KrF;AAID,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,uBAAuB,CAgDvF"}
@@ -0,0 +1,438 @@
1
+ import { toBBox } from "@parseo/shared";
2
+ // ── Utilities ────────────────────────────────────────────────────────────
3
+ function parseNum(raw) {
4
+ if (!raw)
5
+ return null;
6
+ const cleaned = raw.replace(/[$,%]/g, "").replace(/,/g, "").trim();
7
+ if (!cleaned)
8
+ return null;
9
+ const n = Number(cleaned);
10
+ return Number.isNaN(n) ? null : n;
11
+ }
12
+ function parseCurrency(raw) {
13
+ const match = raw.match(/\$?\s*([\d,]+(?:\.\d+)?)/);
14
+ return match ? parseNum(match[1]) : null;
15
+ }
16
+ function extractAfterLabel(seg, label) {
17
+ return seg.text.replace(label, "").trim();
18
+ }
19
+ function findLine(lines, pattern) {
20
+ return lines.find((l) => pattern.test(l.fullText));
21
+ }
22
+ function collectText(lines, startIdx, endPattern) {
23
+ const parts = [];
24
+ for (let i = startIdx; i < lines.length; i++) {
25
+ if (endPattern.test(lines[i].fullText))
26
+ break;
27
+ const t = lines[i].fullText.trim();
28
+ if (t)
29
+ parts.push(t);
30
+ }
31
+ return parts.join(" ").trim();
32
+ }
33
+ // ── Project Analysis (Page 2 top) ────────────────────────────────────────
34
+ export function parseProjectAnalysisSection(lines) {
35
+ const bb = {};
36
+ // Condition and quality
37
+ const condLine = findLine(lines, /condition of the project and quality/i);
38
+ let conditionAndQuality = "";
39
+ if (condLine) {
40
+ const idx = lines.indexOf(condLine);
41
+ const valueSeg = condLine.segments.find((s) => s.x > 300);
42
+ const parts = [];
43
+ if (valueSeg)
44
+ parts.push(valueSeg.text.trim());
45
+ for (let i = idx + 1; i < lines.length; i++) {
46
+ if (/^Describe the common elements/i.test(lines[i].fullText))
47
+ break;
48
+ const t = lines[i].fullText.trim();
49
+ if (t)
50
+ parts.push(t);
51
+ }
52
+ conditionAndQuality = parts.join(" ").trim();
53
+ bb.conditionAndQuality = toBBox(condLine.segments[0], condLine);
54
+ }
55
+ // Common elements
56
+ const commLine = findLine(lines, /common elements and recreational/i);
57
+ let commonElements = "";
58
+ if (commLine) {
59
+ const valueSeg = commLine.segments.find((s) => s.x > 280);
60
+ if (valueSeg) {
61
+ commonElements = valueSeg.text.trim();
62
+ bb.commonElements = toBBox(valueSeg, commLine);
63
+ }
64
+ }
65
+ // Common elements leased
66
+ const leasedLine = findLine(lines, /common elements leased/i);
67
+ const commonElementsLeased = leasedLine?.fullText.includes("Yes") ? "Yes" : leasedLine?.fullText.includes("No") ? "No" : "";
68
+ // Ground rent
69
+ const groundLine = findLine(lines, /subject to a ground rent/i);
70
+ const groundRent = groundLine?.fullText.includes("Yes") ? "Yes" : groundLine?.fullText.includes("No") ? "No" : "";
71
+ // Parking adequacy
72
+ const parkLine = findLine(lines, /parking facilities adequate/i);
73
+ const parkingAdequacy = parkLine?.fullText.includes("Yes") ? "Yes" : parkLine?.fullText.includes("No") ? "No" : "";
74
+ // Budget analysis
75
+ const budgetLine = findLine(lines, /analyze the condominium project budget/i);
76
+ let budgetAnalysis = "";
77
+ if (budgetLine) {
78
+ const idx = lines.indexOf(budgetLine);
79
+ const parts = [];
80
+ for (let i = idx + 1; i < lines.length; i++) {
81
+ if (/^Are there any other fees/i.test(lines[i].fullText))
82
+ break;
83
+ const t = lines[i].fullText.trim();
84
+ if (t)
85
+ parts.push(t);
86
+ }
87
+ budgetAnalysis = parts.join(" ").trim();
88
+ }
89
+ // Other fees
90
+ const feeLine = findLine(lines, /other fees.*for the use of/i);
91
+ const otherFees = feeLine?.fullText.includes("Yes") ? "Yes" : feeLine?.fullText.includes("No") ? "No" : "";
92
+ // Unit charge comparison
93
+ const compLine = findLine(lines, /subject unit charge appears/i);
94
+ let unitChargeComparison = "";
95
+ if (compLine) {
96
+ if (/\bHigh\b/.test(compLine.fullText))
97
+ unitChargeComparison = "High";
98
+ else if (/\bAverage\b/.test(compLine.fullText))
99
+ unitChargeComparison = "Average";
100
+ else if (/\bLow\b/.test(compLine.fullText))
101
+ unitChargeComparison = "Low";
102
+ }
103
+ // Special characteristics
104
+ const specLine = findLine(lines, /special or unusual characteristics/i);
105
+ let specialCharacteristics = "";
106
+ if (specLine) {
107
+ const idx = lines.indexOf(specLine);
108
+ const parts = [];
109
+ // Check next line(s)
110
+ if (lines[idx + 1]) {
111
+ const valueSeg = lines[idx + 1].segments.find((s) => s.x > 280);
112
+ if (valueSeg)
113
+ parts.push(valueSeg.text.trim());
114
+ }
115
+ for (let i = idx + 2; i < lines.length; i++) {
116
+ if (/^Unit Charge/i.test(lines[i].fullText))
117
+ break;
118
+ const t = lines[i].fullText.trim();
119
+ if (t)
120
+ parts.push(t);
121
+ }
122
+ specialCharacteristics = parts.join(" ").trim();
123
+ }
124
+ // Unit charge
125
+ const chargeLine = findLine(lines, /^Unit Charge \$/i);
126
+ let unitChargeMonthly = null, unitChargeAnnual = null, assessmentPerSqft = null;
127
+ if (chargeLine) {
128
+ for (const seg of chargeLine.segments) {
129
+ const t = seg.text.trim();
130
+ if (/^Unit Charge \$\s/i.test(t)) {
131
+ unitChargeMonthly = parseNum(extractAfterLabel(seg, /^Unit Charge \$\s*/i));
132
+ bb.unitChargeMonthly = toBBox(seg, chargeLine);
133
+ }
134
+ const annualMatch = t.match(/\$\s*([\d,.]+)\s*per year/i);
135
+ if (annualMatch)
136
+ unitChargeAnnual = parseNum(annualMatch[1]);
137
+ const sqftMatch = t.match(/=\s*\$\s*([\d,.]+)/i);
138
+ if (sqftMatch)
139
+ assessmentPerSqft = parseNum(sqftMatch[1]);
140
+ }
141
+ }
142
+ // Utilities included
143
+ const utilLine = findLine(lines, /Utilities included in the unit/i);
144
+ let utilitiesIncluded = "";
145
+ if (utilLine) {
146
+ // All labels after "None" or specific utility names
147
+ const segs = utilLine.segments.filter((s) => s.x > 200);
148
+ utilitiesIncluded = segs.map((s) => s.text.trim()).join(", ");
149
+ }
150
+ return {
151
+ conditionAndQuality, commonElements, commonElementsLeased, groundRent,
152
+ parkingAdequacy, budgetAnalysis, otherFees, unitChargeComparison, specialCharacteristics,
153
+ unitChargeMonthly, unitChargeAnnual, assessmentPerSqft, utilitiesIncluded,
154
+ boundingBoxes: bb,
155
+ };
156
+ }
157
+ // ── Unit Description (Page 2) ────────────────────────────────────────────
158
+ export function parseUnitDescriptionSection(lines) {
159
+ const bb = {};
160
+ const floorLine = findLine(lines, /^Floor #/i);
161
+ let floorNumber = "";
162
+ if (floorLine) {
163
+ const seg = floorLine.segments.find((s) => /^Floor #/i.test(s.text));
164
+ if (seg)
165
+ floorNumber = extractAfterLabel(seg, /^Floor #\s*/i);
166
+ }
167
+ const levelsLine = findLine(lines, /^# of Levels/i);
168
+ let numberOfLevels = null;
169
+ if (levelsLine) {
170
+ const seg = levelsLine.segments.find((s) => /^# of Levels/i.test(s.text));
171
+ if (seg)
172
+ numberOfLevels = parseNum(extractAfterLabel(seg, /^# of Levels\s+/i));
173
+ }
174
+ const heatLine = findLine(lines, /^Heating Type/i);
175
+ let heatingType = "", heatingFuel = "";
176
+ if (heatLine) {
177
+ for (const seg of heatLine.segments) {
178
+ const t = seg.text.trim();
179
+ if (/^Heating Type\s/i.test(t))
180
+ heatingType = extractAfterLabel(seg, /^Heating Type\s+/i);
181
+ else if (/^Fuel\s/i.test(t))
182
+ heatingFuel = extractAfterLabel(seg, /^Fuel\s+/i);
183
+ }
184
+ }
185
+ const acLine = findLine(lines, /^Central AC/i);
186
+ let centralAC = "";
187
+ if (acLine) {
188
+ const seg = acLine.segments.find((s) => /^Central AC/i.test(s.text) || /^Individual AC/i.test(s.text));
189
+ if (seg)
190
+ centralAC = /Central AC/i.test(seg.text) ? "Central" : "Individual";
191
+ }
192
+ // Interior materials
193
+ function findMaterial(label) {
194
+ for (const l of lines) {
195
+ if (l.y < 415 || l.y > 500)
196
+ continue;
197
+ for (const seg of l.segments) {
198
+ if (label.test(seg.text)) {
199
+ const val = seg.text.replace(label, "").trim();
200
+ if (val)
201
+ return val;
202
+ const next = l.segments[l.segments.indexOf(seg) + 1];
203
+ if (next)
204
+ return next.text.trim();
205
+ }
206
+ }
207
+ }
208
+ return "";
209
+ }
210
+ const floors = findMaterial(/^Floors\s+/i);
211
+ const walls = findMaterial(/^Walls\s+/i);
212
+ const trimFinish = findMaterial(/^Trim\/Finish\s+/i);
213
+ const bathWainscot = findMaterial(/^Bath Wainscot\s+/i);
214
+ const doors = findMaterial(/^Doors\s+/i);
215
+ // Amenities
216
+ let fireplaces = null, deckPatio = "", porchBalcony = "";
217
+ for (const l of lines) {
218
+ if (l.y < 415 || l.y > 500)
219
+ continue;
220
+ for (const seg of l.segments) {
221
+ const t = seg.text.trim();
222
+ if (/^Fireplace\(s\) #/i.test(t))
223
+ fireplaces = parseNum(extractAfterLabel(seg, /^Fireplace\(s\) #\s*/i));
224
+ else if (/^Deck\/Patio/i.test(t))
225
+ deckPatio = extractAfterLabel(seg, /^Deck\/Patio\s*/i);
226
+ else if (/^Porch\/Balcony/i.test(t))
227
+ porchBalcony = extractAfterLabel(seg, /^Porch\/Balcony\s*/i);
228
+ }
229
+ }
230
+ // Appliances
231
+ let refrigerator = "", rangeOven = "", disposal = "", dishwasher = "", microwave = "", washerDryer = "";
232
+ for (const l of lines) {
233
+ if (l.y < 415 || l.y > 500)
234
+ continue;
235
+ for (const seg of l.segments) {
236
+ const t = seg.text.trim();
237
+ if (/^Refrigerator$/i.test(t))
238
+ refrigerator = "Yes";
239
+ if (/^Range\/Oven$/i.test(t))
240
+ rangeOven = "Yes";
241
+ if (/^Disp$/i.test(t) || /^Disposal$/i.test(t))
242
+ disposal = "Yes";
243
+ if (/^Dishwasher$/i.test(t))
244
+ dishwasher = "Yes";
245
+ if (/^Microwave$/i.test(t))
246
+ microwave = "Yes";
247
+ if (/^Washer\/Dryer$/i.test(t))
248
+ washerDryer = "Yes";
249
+ }
250
+ }
251
+ // Car storage
252
+ let carStorageType = "", carStorageCount = null, carStorageOwnership = "";
253
+ for (const l of lines) {
254
+ if (l.y < 415 || l.y > 500)
255
+ continue;
256
+ for (const seg of l.segments) {
257
+ const t = seg.text.trim();
258
+ if (/^Garage$/i.test(t))
259
+ carStorageType = "Garage";
260
+ if (/^# of Cars\s/i.test(t))
261
+ carStorageCount = parseNum(extractAfterLabel(seg, /^# of Cars\s+/i));
262
+ if (/^Assigned$/i.test(t))
263
+ carStorageOwnership = "Assigned";
264
+ if (/^Owned$/i.test(t))
265
+ carStorageOwnership = "Owned";
266
+ }
267
+ }
268
+ // Room count
269
+ const roomLine = findLine(lines, /^Finished area above grade/i);
270
+ let roomCount = null, bedrooms = null, baths = null, grossLivingArea = null;
271
+ if (roomLine) {
272
+ for (const seg of roomLine.segments) {
273
+ const t = seg.text.trim();
274
+ const roomMatch = t.match(/^(\d+)\s*Rooms?/i);
275
+ if (roomMatch) {
276
+ roomCount = parseNum(roomMatch[1]);
277
+ bb.roomCount = toBBox(seg, roomLine);
278
+ }
279
+ const bedMatch = t.match(/^(\d+)\s*Bedrooms?/i);
280
+ if (bedMatch) {
281
+ bedrooms = parseNum(bedMatch[1]);
282
+ bb.bedrooms = toBBox(seg, roomLine);
283
+ }
284
+ const bathMatch = t.match(/^([\d.]+)\s*Bath/i);
285
+ if (bathMatch) {
286
+ baths = parseNum(bathMatch[1]);
287
+ bb.baths = toBBox(seg, roomLine);
288
+ }
289
+ const sqftMatch = t.match(/^([\d,]+)\s*Square Feet/i);
290
+ if (sqftMatch) {
291
+ grossLivingArea = parseNum(sqftMatch[1]);
292
+ bb.grossLivingArea = toBBox(seg, roomLine);
293
+ }
294
+ }
295
+ }
296
+ const meterLine = findLine(lines, /separately metered/i);
297
+ const separatelyMetered = meterLine?.fullText.includes("Yes") ? "Yes" : meterLine?.fullText.includes("No") ? "No" : "";
298
+ const featLine = findLine(lines, /^Additional features/i);
299
+ let additionalFeatures = "";
300
+ if (featLine) {
301
+ const valueSeg = featLine.segments.find((s) => s.x > 180);
302
+ if (valueSeg)
303
+ additionalFeatures = valueSeg.text.trim();
304
+ }
305
+ const condLine = findLine(lines, /^Describe the condition of the property/i);
306
+ let conditionDescription = "";
307
+ if (condLine) {
308
+ const idx = lines.indexOf(condLine);
309
+ const valueSeg = condLine.segments.find((s) => s.x > 350);
310
+ const parts = [];
311
+ if (valueSeg)
312
+ parts.push(valueSeg.text.trim());
313
+ for (let i = idx + 1; i < lines.length; i++) {
314
+ if (/^Are there any physical/i.test(lines[i].fullText) || /^UNIT DESCRIPTION$/i.test(lines[i].fullText.trim()))
315
+ break;
316
+ const t = lines[i].fullText.trim();
317
+ if (t)
318
+ parts.push(t);
319
+ }
320
+ conditionDescription = parts.join(" ").trim();
321
+ }
322
+ const defLine = findLine(lines, /physical deficiencies or adverse conditions/i);
323
+ let physicalDeficiencies = "";
324
+ if (defLine) {
325
+ const idx = lines.indexOf(defLine);
326
+ const parts = [];
327
+ for (let i = idx + 1; i < lines.length; i++) {
328
+ if (/^Does the property generally conform/i.test(lines[i].fullText))
329
+ break;
330
+ const t = lines[i].fullText.trim();
331
+ if (t)
332
+ parts.push(t);
333
+ }
334
+ physicalDeficiencies = parts.join(" ").trim();
335
+ }
336
+ const confLine = findLine(lines, /Does the property generally conform/i);
337
+ let conformity = "";
338
+ if (confLine) {
339
+ const idx = lines.indexOf(confLine);
340
+ const parts = [];
341
+ for (let i = idx + 1; i < lines.length; i++) {
342
+ if (/^I\s+(did|did not)\s+research/i.test(lines[i].fullText) || /^Freddie Mac Form/i.test(lines[i].fullText))
343
+ break;
344
+ const t = lines[i].fullText.trim();
345
+ if (t)
346
+ parts.push(t);
347
+ }
348
+ conformity = parts.join(" ").trim();
349
+ }
350
+ return {
351
+ floorNumber, numberOfLevels, heatingType, heatingFuel, centralAC,
352
+ floors, walls, trimFinish, bathWainscot, doors,
353
+ fireplaces, deckPatio, porchBalcony,
354
+ refrigerator, rangeOven, disposal, dishwasher, microwave, washerDryer,
355
+ carStorageType, carStorageCount, carStorageOwnership,
356
+ roomCount, bedrooms, baths, grossLivingArea,
357
+ separatelyMetered, additionalFeatures, conditionDescription, physicalDeficiencies, conformity,
358
+ boundingBoxes: bb,
359
+ };
360
+ }
361
+ // ── Prior Sale History (Page 2 bottom) ───────────────────────────────────
362
+ export function parsePriorSaleHistorySection(lines) {
363
+ const bb = {};
364
+ const researchLine = findLine(lines, /did.*research the sale or transfer/i);
365
+ const researchPerformed = researchLine?.fullText.includes("did not") ? "did not" : "did";
366
+ const subjRevealLine = findLine(lines, /My research.*reveal any prior sales.*subject property/i);
367
+ const subjectPriorSaleRevealed = subjRevealLine?.fullText.includes("did not") ? "did not" : "did";
368
+ const subjDsLine = findLine(lines, /^Data source\(s\)\s+Public Records/i);
369
+ const subjectDataSources = subjDsLine?.fullText.replace(/^Data source\(s\)\s*/i, "").trim() ?? "";
370
+ const compRevealLine = findLine(lines, /My research.*reveal any prior sales.*comparable/i);
371
+ const comparablePriorSaleRevealed = compRevealLine?.fullText.includes("did not") ? "did not" : "did";
372
+ // Look for the second Data source(s) line
373
+ const allDsLines = lines.filter((l) => /^Data source\(s\)/i.test(l.fullText));
374
+ const comparableDataSources = allDsLines.length > 1 ? allDsLines[1].fullText.replace(/^Data source\(s\)\s*/i, "").trim() : "";
375
+ // Prior sale table: ITEM / SUBJECT / COMP1 / COMP2 / COMP3
376
+ const headerLine = findLine(lines, /^ITEM\s+SUBJECT\s+COMPARABLE SALE/i);
377
+ const subject = parsePriorSaleColumn(lines, headerLine, 0);
378
+ const comparables = [];
379
+ for (let i = 1; i <= 3; i++) {
380
+ comparables.push(parsePriorSaleColumn(lines, headerLine, i));
381
+ }
382
+ // Analysis text
383
+ const analysisLine = findLine(lines, /^Analysis of prior sale or transfer/i);
384
+ let analysis = "";
385
+ if (analysisLine) {
386
+ const idx = lines.indexOf(analysisLine);
387
+ const valueSeg = analysisLine.segments.find((s) => s.x > 280);
388
+ const parts = [];
389
+ if (valueSeg)
390
+ parts.push(valueSeg.text.trim());
391
+ for (let i = idx + 1; i < lines.length; i++) {
392
+ if (/^Freddie Mac Form/i.test(lines[i].fullText))
393
+ break;
394
+ const t = lines[i].fullText.trim();
395
+ if (t)
396
+ parts.push(t);
397
+ }
398
+ analysis = parts.join(" ").trim();
399
+ }
400
+ return {
401
+ researchPerformed, subjectPriorSaleRevealed, subjectDataSources,
402
+ comparablePriorSaleRevealed, comparableDataSources,
403
+ subject, comparables, analysis, boundingBoxes: bb,
404
+ };
405
+ }
406
+ function parsePriorSaleColumn(lines, headerLine, colIndex) {
407
+ const bb = {};
408
+ if (!headerLine)
409
+ return { dateOfPriorSale: "", priceOfPriorSale: null, dataSources: "", effectiveDateOfDataSources: "", boundingBoxes: bb };
410
+ // Column x boundaries based on header segments
411
+ const colRanges = [
412
+ { min: 80, max: 180 }, // Subject
413
+ { min: 180, max: 320 }, // Comp 1
414
+ { min: 320, max: 450 }, // Comp 2
415
+ { min: 450, max: 580 }, // Comp 3
416
+ ];
417
+ const col = colRanges[colIndex] ?? colRanges[0];
418
+ function getColValue(line) {
419
+ if (!line)
420
+ return "";
421
+ return line.segments
422
+ .filter((s) => s.x >= col.min && s.x < col.max)
423
+ .map((s) => s.text.trim())
424
+ .join(" ")
425
+ .trim();
426
+ }
427
+ const headerIdx = lines.indexOf(headerLine);
428
+ const dateLine = lines[headerIdx + 1];
429
+ const priceLine = lines[headerIdx + 2];
430
+ const dsLine = lines[headerIdx + 3];
431
+ const effLine = lines[headerIdx + 4];
432
+ const dateOfPriorSale = getColValue(dateLine);
433
+ const priceText = getColValue(priceLine);
434
+ const priceOfPriorSale = priceText ? parseCurrency(priceText) : null;
435
+ const dataSources = getColValue(dsLine);
436
+ const effectiveDateOfDataSources = getColValue(effLine);
437
+ return { dateOfPriorSale, priceOfPriorSale, dataSources, effectiveDateOfDataSources, boundingBoxes: bb };
438
+ }
@@ -0,0 +1,7 @@
1
+ import type { TextLine } from "@parseo/shared";
2
+ import type { SalesComparisonSection, ReconciliationSection, AppraiserInfo, LenderClientInfo } from "./types.js";
3
+ export declare function parseSalesComparisonSection(lines: TextLine[], compStartNum?: number): SalesComparisonSection;
4
+ export declare function parseReconciliationSection(lines: TextLine[]): ReconciliationSection;
5
+ export declare function parseAppraiserInfo(lines: TextLine[], supervisory: boolean): AppraiserInfo | null;
6
+ export declare function parseLenderClientInfo(lines: TextLine[]): LenderClientInfo;
7
+ //# sourceMappingURL=parse-sales.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-sales.d.ts","sourceRoot":"","sources":["../../src/form-1073/parse-sales.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,gBAAgB,CAAC;AAC5D,OAAO,KAAK,EACV,sBAAsB,EAGtB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EACjB,MAAM,YAAY,CAAC;AA8NpB,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,QAAQ,EAAE,EACjB,YAAY,GAAE,MAAU,GACvB,sBAAsB,CAoIxB;AAID,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,qBAAqB,CA4DnF;AAID,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CA2ChG;AAID,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,gBAAgB,CAgCzE"}