@cenogram/mcp-server 0.6.0 → 0.11.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.
@@ -254,7 +254,7 @@ export function formatMarketOverview(stats) {
254
254
  export function formatPriceStats(rows, location) {
255
255
  if (rows.length === 0) {
256
256
  return location
257
- ? `No price statistics found for "${location}". Note: this endpoint only covers residential units (apartments). Use list_locations to find valid location names.`
257
+ ? `No price statistics found for "${location}". Note: this endpoint only covers residential units (apartments), and matches on name only. Call list_locations(search=...) to confirm the name is a valid RCN district (rcn_district).`
258
258
  : "No price statistics available.";
259
259
  }
260
260
  const header = location
@@ -291,7 +291,7 @@ export function formatHistogram(bins) {
291
291
  }
292
292
  export function formatParcelResults(res, query) {
293
293
  if (res.results.length === 0) {
294
- return `No parcels found matching "${query}".`;
294
+ return `No parcels found matching "${query}". This searches the parcels we hold, which are near-complete coverage of the cadastral register rather than the whole of it, so a miss is more likely a recent change or a mistyped prefix than evidence that no such parcel exists — ${CORPUS_COVERAGE_HINT}${formatCorpusCoverage(res.corpus_coverage)}`;
295
295
  }
296
296
  const lines = [`Found ${res.results.length} parcels matching "${query}":\n`];
297
297
  for (const [i, p] of res.results.entries()) {
@@ -301,11 +301,180 @@ export function formatParcelResults(res, query) {
301
301
  lines.push(`${i + 1}. ${p.parcel_id ?? "(parcel number requires a paid plan)"}`);
302
302
  lines.push(` District: ${district} | Area: ${area} | Location: ${location}`);
303
303
  }
304
+ return lines.join("\n") + formatCorpusCoverage(res.corpus_coverage);
305
+ }
306
+ const PARCEL_IDENTITY_WITHHELD = "(parcel number requires a paid plan)";
307
+ const CORPUS_COVERAGE_HINT = "looking the parcel up by its FULL cadastral id (resolve_parcel) goes down a different path that can confirm and add a parcel we do not yet hold.";
308
+ const COVERAGE_UNMEASURED_FALLBACK = "we hold near-complete coverage of the cadastral register, though not the whole of it and not live, " +
309
+ "and how much of it this query covers is not measured. Read the answer above as what we hold, not " +
310
+ "as what is there.";
311
+ function finiteOrNull(value) {
312
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
313
+ }
314
+ function coverageScopeLabel(counties) {
315
+ if (counties === 1)
316
+ return "this county";
317
+ if (counties != null && counties > 1)
318
+ return `these ${counties.toLocaleString("en-US")} counties`;
319
+ return "the counties this query addresses";
320
+ }
321
+ function coveragePctLabel(held, source, reported) {
322
+ const exact = (held / source) * 100;
323
+ if (held > 0 && exact < 0.05)
324
+ return "<0.1%";
325
+ if (held < source && exact >= 99.95)
326
+ return ">99.9%";
327
+ return `${(reported ?? exact).toFixed(1)}%`;
328
+ }
329
+ export function formatCorpusCoverage(cov) {
330
+ if (!cov)
331
+ return "";
332
+ const asOf = typeof cov.as_of === "string" ? cov.as_of.split("T")[0] : null;
333
+ const held = finiteOrNull(cov.held_parcels);
334
+ const source = finiteOrNull(cov.source_parcels);
335
+ if (held != null && source != null && source > 0) {
336
+ if (held >= source)
337
+ return "";
338
+ const scope = coverageScopeLabel(finiteOrNull(cov.counties));
339
+ const pct = coveragePctLabel(held, source, finiteOrNull(cov.held_pct));
340
+ const stamp = asOf ? `, measured ${asOf}` : "";
341
+ return `\n\nCOVERAGE: we hold ${held.toLocaleString("en-US")} of the ${source.toLocaleString("en-US")} parcels the cadastral register lists for ${scope} (${pct}${stamp}). A short or empty list above reflects that, not the land.`;
342
+ }
343
+ const note = typeof cov.note === "string" && cov.note.trim().length > 0
344
+ ? cov.note.trim()
345
+ : COVERAGE_UNMEASURED_FALLBACK;
346
+ const stamp = asOf ? ` Our latest coverage measurement is dated ${asOf}.` : "";
347
+ return `\n\nCOVERAGE: ${note}${stamp}`;
348
+ }
349
+ function collectPositions(node, out) {
350
+ if (!Array.isArray(node))
351
+ return;
352
+ if (node.length >= 2 && typeof node[0] === "number" && typeof node[1] === "number") {
353
+ out.push([node[0], node[1]]);
354
+ return;
355
+ }
356
+ for (const child of node)
357
+ collectPositions(child, out);
358
+ }
359
+ function outlineCentre(geometry) {
360
+ if (!geometry)
361
+ return null;
362
+ const positions = [];
363
+ collectPositions(geometry.coordinates, positions);
364
+ if (positions.length === 0)
365
+ return null;
366
+ let minLng = Infinity, maxLng = -Infinity, minLat = Infinity, maxLat = -Infinity;
367
+ for (const [lng, lat] of positions) {
368
+ if (lng < minLng)
369
+ minLng = lng;
370
+ if (lng > maxLng)
371
+ maxLng = lng;
372
+ if (lat < minLat)
373
+ minLat = lat;
374
+ if (lat > maxLat)
375
+ maxLat = lat;
376
+ }
377
+ return { lat: (minLat + maxLat) / 2, lng: (minLng + maxLng) / 2 };
378
+ }
379
+ function formatParcelAddress(p) {
380
+ if (p.street == null || p.street === "")
381
+ return null;
382
+ const approx = p.address_source === "approx_high" || p.address_source === "approx_low";
383
+ const tag = approx ? " [street approximate — derived, not from the record]" : "";
384
+ return [p.street, p.building_number].filter(Boolean).join(" ") + tag;
385
+ }
386
+ export function formatParcelList(res, scope, creditsRefunded = false) {
387
+ if (res.data.length === 0) {
388
+ const lines = [
389
+ `No parcels found for ${scope}. The filter is valid; nothing matched it AMONG THE PARCELS WE HOLD, which are near-complete coverage of the cadastral register rather than the whole of it. So this is not evidence that the area has no parcels.`,
390
+ ];
391
+ if (creditsRefunded)
392
+ lines.push("The tokens for this call were refunded — an unmatched street page costs nothing.");
393
+ const streets = res.suggestions?.streets ?? [];
394
+ const numbers = res.suggestions?.building_numbers ?? [];
395
+ if (streets.length > 0) {
396
+ lines.push("Close street names on record here — matching is case- and accent-insensitive but does not inflect, so retry with one of these, each a ready next call:");
397
+ for (const s of streets)
398
+ lines.push(` - street="${s}"`);
399
+ }
400
+ if (numbers.length > 0) {
401
+ lines.push("The street is on record but that number is not; these are the numbers held on it, in the compound form the register uses — retry with one of them:");
402
+ for (const n of numbers)
403
+ lines.push(` - buildingNumber="${n}"`);
404
+ }
405
+ if (streets.length === 0 && numbers.length === 0) {
406
+ lines.push("Widening the area or dropping a surface filter is the next step.");
407
+ }
408
+ lines.push(CORPUS_COVERAGE_HINT);
409
+ return lines.join("\n") + formatCorpusCoverage(res.corpus_coverage);
410
+ }
411
+ const lines = [`Found ${res.data.length} parcel${res.data.length === 1 ? "" : "s"} for ${scope}:\n`];
412
+ for (const [i, p] of res.data.entries()) {
413
+ const district = p.district ?? "Unknown";
414
+ const location = p.lat != null && p.lng != null
415
+ ? `${p.lat.toFixed(4)}°N, ${p.lng.toFixed(4)}°E`
416
+ : "no outline held";
417
+ lines.push(`${i + 1}. ${p.parcel_id ?? PARCEL_IDENTITY_WITHHELD}`);
418
+ lines.push(` District: ${district} | Location: ${location}`);
419
+ const address = formatParcelAddress(p);
420
+ if (address)
421
+ lines.push(` Address: ${address}`);
422
+ }
423
+ if (res.pagination.has_more && res.pagination.next_cursor) {
424
+ lines.push(`\nMore parcels match than are shown. Pass cursor="${res.pagination.next_cursor}" to get the next page (the value is opaque — pass it back unchanged).`);
425
+ }
426
+ else if (res.pagination.has_more) {
427
+ lines.push(`\nMore parcels match than are shown, but no cursor came back — narrow the filter instead of paging.`);
428
+ }
429
+ lines.push(`\nNo outline and no surface on these rows: call list_parcels_in_area with includeGeometry=true (or a polygon) for outlines, or get_parcel_report for one parcel in full.`);
430
+ return lines.join("\n") + formatCorpusCoverage(res.corpus_coverage);
431
+ }
432
+ export function formatStreetList(res, q, scope) {
433
+ if (res.data.length === 0) {
434
+ return `No street name in ${scope} matches "${q}". The scope is valid and nothing we hold there contains that fragment — two different things can put you here, and this answer cannot tell them apart:\n`
435
+ + ` 1. We hold no street addresses in this area at all. Coverage follows the addresses themselves and is thin outside towns, so rural parcels are often there while their streets are not — for one of those, resolve_parcel with the precinct name and the parcel number is the way in, not a street.\n`
436
+ + ` 2. There is genuinely no such street here.\n`
437
+ + `Matching is case- and accent-insensitive but does not inflect, so pass the name in the nominative ('karmelicka' or 'Karmelicką' both find 'Karmelicka', but 'Karmelickiej' does not).`;
438
+ }
439
+ const lines = [
440
+ `${res.data.length} street name${res.data.length === 1 ? "" : "s"} in ${scope} matching "${q}":\n`,
441
+ ];
442
+ for (const row of res.data) {
443
+ const tag = row.address_source === "approx" ? " [approximate — derived, not from the record]" : "";
444
+ lines.push(` - ${row.street}${tag}`);
445
+ }
446
+ lines.push("\nUntagged names are on record; tagged ones we worked out for parcels the record left without a street. " +
447
+ "Any of these can be passed to list_parcels_in_area as street= inside the same scope.");
448
+ if (res.pagination.has_more) {
449
+ lines.push(`More names match than are shown (the answer is capped at ${res.pagination.limit}). Type more of the name — there is no second page.`);
450
+ }
304
451
  return lines.join("\n");
305
452
  }
453
+ export function formatParcelFeatures(res, scope) {
454
+ if (res.features.length === 0) {
455
+ return `No parcels found in ${scope}. The area is valid and holds no parcel WE CAN PLACE THERE — coverage is near-complete but not the whole cadastral register and not live, so read this as "we have nothing mapped here", not as "there is nothing here". ${CORPUS_COVERAGE_HINT}${formatCorpusCoverage(res.corpus_coverage)}`;
456
+ }
457
+ const lines = [`Found ${res.features.length} parcel${res.features.length === 1 ? "" : "s"} in ${scope}:`];
458
+ if (res.truncated) {
459
+ lines.push(`TRUNCATED — more of the parcels we hold match than the limit returns, and the ones below are an arbitrary subset, not the first, nearest or largest. Read this as a sample of the area, never as its parcel list. A smaller area returns everything WE HOLD there, which is not the same as every parcel there: coverage is near-complete but not the whole cadastral register and not live.`);
460
+ }
461
+ lines.push("");
462
+ for (const [i, f] of res.features.entries()) {
463
+ const district = f.properties.district ?? "Unknown";
464
+ const centre = outlineCentre(f.geometry);
465
+ const where = centre ? `${centre.lat.toFixed(4)}°N, ${centre.lng.toFixed(4)}°E` : "unknown";
466
+ lines.push(`${i + 1}. ${f.properties.parcel_id ?? PARCEL_IDENTITY_WITHHELD}`);
467
+ lines.push(` District: ${district} | Outline centre: ${where}`);
468
+ lines.push(` Outline (GeoJSON, WGS84): ${f.geometry ? JSON.stringify(f.geometry) : "not returned"}`);
469
+ }
470
+ return lines.join("\n") + formatCorpusCoverage(res.corpus_coverage);
471
+ }
306
472
  export function formatParcelResolve(res) {
473
+ if (res.coverage === "not_computed") {
474
+ return "The lookup could not be completed, so this says nothing about whether the parcel exists (the credit is refunded). Best next step: pass the full cadastral id in parcelId — that path does not go through the name at all. A retry helps only if a live confirmation timed out; it will not change the answer for a name carried by very many precincts.";
475
+ }
307
476
  if (res.coverage === "not_covered" || res.matches.length === 0) {
308
- return "No parcel matched. The identifier or 'name + number' is not in our cadastral copy (the credit is refunded). Check the spelling of the locality name, or use search_parcels to look up a parcel id by prefix.";
477
+ return "No parcel matched (the credit is refunded). Two different causes, and with near-complete coverage the first is now the common one: the name may be spelled differently than the register spells it (for 'name + number' it is matched against the gmina name and the cadastral precinct (obręb) name, exactly), OR we do not hold the parcel — coverage is near-complete but not the whole cadastral register and not live, so a discovery lookup can still miss a parcel that exists. This is not a confirmation that the parcel does not exist. Passing the FULL cadastral id in parcelId goes down a different path that can confirm and add a parcel we do not yet hold." + formatCorpusCoverage(res.corpus_coverage);
309
478
  }
310
479
  const lines = [`Found ${res.matches.length} parcel${res.matches.length === 1 ? "" : "s"}:\n`];
311
480
  for (const [i, m] of res.matches.entries()) {
@@ -322,7 +491,8 @@ export function formatParcelResolve(res) {
322
491
  if (res.as_of) {
323
492
  lines.push(`\nCadastral copy as of ${res.as_of.split("T")[0]}.`);
324
493
  }
325
- return lines.join("\n");
494
+ lines.push(`\nQuery cost: 0 API tokens — resolving a parcel is free.`);
495
+ return lines.join("\n") + formatCorpusCoverage(res.corpus_coverage);
326
496
  }
327
497
  function formatSpatialFeature(f) {
328
498
  return formatTransactionCore({
@@ -355,6 +525,37 @@ export function formatSpatialResults(res) {
355
525
  }
356
526
  return lines.join("\n");
357
527
  }
528
+ export function formatBuildingAge(age) {
529
+ if (age == null)
530
+ return "";
531
+ const works = age.last_works_year != null ? `; later works ${age.last_works_year}` : "";
532
+ const clause = (body) => `construction year not established: ${body}${works}`;
533
+ switch (age.status) {
534
+ case "estimated": {
535
+ if (age.year_from == null || age.year_to == null) {
536
+ return clause("an estimate came back without the interval it has to carry, so there is no honest year to state");
537
+ }
538
+ const range = age.year_from === age.year_to ? `${age.year_from}` : `${age.year_from}-${age.year_to}`;
539
+ const point = age.year_point != null ? `, point estimate ~${age.year_point} (least certain)` : "";
540
+ const conf = age.confidence ? `, ${age.confidence} confidence in the permit match (not in the year)` : "";
541
+ return `built range ${range}${point} [estimate from permit records, not a registry date${conf}]${works}`;
542
+ }
543
+ case "older_than_register":
544
+ return clause("no construction record for this land falls inside our coverage, which starts in 2016 — the works may predate it, or the land may have been renumbered since");
545
+ case "ambiguous_permits":
546
+ case "ambiguous_buildings":
547
+ case "ambiguous_both":
548
+ return clause("construction records for this land could not be tied to this specific building");
549
+ case "no_parcel_key":
550
+ return clause("this land has no cadastral identifier to match records against");
551
+ case "not_applicable":
552
+ return clause("this building has no outline we can place on the land");
553
+ case "not_computed":
554
+ return clause("the lookup could not be completed for this request — retrying later may return one");
555
+ default:
556
+ return clause("the answer came back in a form this client version does not recognise — a newer client may be able to state it");
557
+ }
558
+ }
358
559
  export function formatBuildingBreakdown(res) {
359
560
  const { data, truncated } = res;
360
561
  if (data.length === 0) {
@@ -380,6 +581,9 @@ export function formatBuildingBreakdown(res) {
380
581
  }
381
582
  if (b.match_confidence)
382
583
  cells.push(`match confidence: ${b.match_confidence}`);
584
+ const age = formatBuildingAge(b.age_estimate);
585
+ if (age)
586
+ cells.push(age);
383
587
  lines.push(`${i + 1}. ${cells.join(" | ")}`);
384
588
  });
385
589
  if (truncated) {
@@ -498,6 +702,155 @@ export function formatLandslideBreakdown(res) {
498
702
  lines.push("", "Note: based on official landslide-hazard maps (1:10,000 scale). An intersection means the parcel overlaps a mapped hazard area, not that the parcel itself is a landslide.");
499
703
  return lines.join("\n");
500
704
  }
705
+ const PROTECTION_RANK_LABEL = {
706
+ 1: "national park",
707
+ 2: "nature reserve",
708
+ 3: "Natura 2000",
709
+ 4: "landscape park",
710
+ 5: "protected landscape",
711
+ 6: "a minor protection form, or a buffer zone",
712
+ };
713
+ const BUILDING_RESTRICTION_NOTE = {
714
+ statutory_ban: "a build ban that follows directly from the Nature Protection Act (national parks and reserves), with statutory exceptions — not a ruling on any specific project",
715
+ conditional: "no blanket statutory ban; restrictions depend on the act that established the area",
716
+ };
717
+ const NATURE_EMPTY_MESSAGE = {
718
+ covered_no_data: "No forest within 2 km and no protected natural area overlaps this transaction's parcels (or the id was not found). An empty result is never a statement that building is allowed — this layer does not cover local zoning plans, planning-permission decisions or areas under designation.",
719
+ not_covered: "This layer holds no nature reference data for these parcels yet, so nothing was checked. That is NOT a finding that there is no forest nearby and no protected natural area here — it means the check could not be made. The credit for this call is refunded; try again later.",
720
+ unknown: "No forest or protected-area signal was returned for this transaction's parcels. This response does not distinguish 'checked, nothing found' from 'not checked' (the id may also be unknown), so do not read it as a finding that there is no forest or protected area — and never as a statement that building is allowed.",
721
+ };
722
+ export function formatNatureBreakdown(res) {
723
+ const { data, truncated } = res;
724
+ if (data.length === 0) {
725
+ const key = res.coverage === "covered_no_data" || res.coverage === "not_covered" ? res.coverage : "unknown";
726
+ return NATURE_EMPTY_MESSAGE[key];
727
+ }
728
+ const lines = [
729
+ `Per-parcel nature breakdown (${data.length} parcel${data.length === 1 ? "" : "s"} with a forest or protected-area signal):`,
730
+ "",
731
+ ];
732
+ data.forEach((r, i) => {
733
+ const cells = [];
734
+ const dist = r.forest_distance_m != null ? Number(r.forest_distance_m) : null;
735
+ if (dist != null && Number.isFinite(dist)) {
736
+ if (dist === 0) {
737
+ const ov = r.forest_overlap_pct != null ? Number(r.forest_overlap_pct) : null;
738
+ cells.push(`forest: overlaps the parcel${ov != null && Number.isFinite(ov) ? ` (${Math.round(ov)}% of area)` : ""}`);
739
+ }
740
+ else {
741
+ cells.push(`forest: ${dist} m away`);
742
+ }
743
+ }
744
+ if (r.protection_rank != null) {
745
+ const label = PROTECTION_RANK_LABEL[r.protection_rank] ?? `protection rank ${r.protection_rank}`;
746
+ cells.push(`protection: ${label}`);
747
+ if (r.building_restriction) {
748
+ const note = BUILDING_RESTRICTION_NOTE[r.building_restriction];
749
+ cells.push(`build restriction: ${r.building_restriction}${note ? ` (${note})` : ""}`);
750
+ }
751
+ if (r.protected_overlap_pct != null) {
752
+ const p = Number(r.protected_overlap_pct);
753
+ if (Number.isFinite(p))
754
+ cells.push(`${Math.round(p)}% of the parcel under protection`);
755
+ }
756
+ if (Array.isArray(r.protected_areas) && r.protected_areas.length > 0) {
757
+ const labels = r.protected_areas.map((a) => a.name ?? a.form).filter((x) => !!x);
758
+ if (labels.length > 0)
759
+ cells.push(`areas: ${labels.join("; ")}`);
760
+ }
761
+ }
762
+ lines.push(`${i + 1}. ${cells.join(" | ")}`);
763
+ });
764
+ if (truncated) {
765
+ lines.push("", "Showing the first 500 parcels (the transaction is linked to more).");
766
+ }
767
+ lines.push("", "Note: forest_distance_m is the nearest forest within 2 km (0 = the parcel overlaps forest). building_restriction names the SOURCE of the restriction (statute vs the act that established the area), not the outcome of any permitting case; an empty result is never a statement that building is allowed — this layer does not cover local zoning plans, planning-permission decisions or areas under designation. A buffer zone around a park or reserve IS reported, as form 'buffer_zone' at rank 6, and never as a statutory ban. Forest coverage is mainly publicly-managed land, so private forests may be incomplete.");
768
+ return lines.join("\n");
769
+ }
770
+ const MINERAL_CLASS_NOTE = {
771
+ subsidence: "extraction with surface deformation — the actual mining-damage risk",
772
+ surface: "open-pit working — mostly local impact (neighbourhood, noise)",
773
+ fluid: "borehole extraction — usually no surface deformation, but a concession and zones still apply",
774
+ other: "mineral not classified",
775
+ };
776
+ const SUBSURFACE_NOTE = "Note: a mining terrain is a legally defined zone of anticipated mining influence; its mapped location is approximate — an intersection is an advisory signal to verify with the competent mining-supervision authority (named per terrain as the oversight authority), not a legal determination. Some terrains with status 'active' carry a valid_until already in the past, because the register entry can lag behind the expiry of a concession. A groundwater reservoir's extent alone imposes NO restriction; a restriction would come only from an established protection zone, which is not published here. Absence of a match is never asserted as 'safe': the mining register covers concession areas, so historic or shallow workings may not appear, and an absent reservoir does not mean there is no groundwater beneath the parcel.";
777
+ function subsurfacePct(v) {
778
+ if (v == null)
779
+ return null;
780
+ const n = Number(v);
781
+ return Number.isFinite(n) ? Math.round(n) : null;
782
+ }
783
+ export function formatSubsurfaceBreakdown(res) {
784
+ const { data, truncated } = res;
785
+ if (data.length === 0) {
786
+ return "No mapped mining terrain or major groundwater reservoir overlaps this transaction's parcels (or the id was not found). Absence of mapped data is never asserted as 'safe' — the mining register covers concession areas, so historic or shallow workings may not appear.";
787
+ }
788
+ const lines = [
789
+ `Per-parcel subsurface breakdown (${data.length} parcel${data.length === 1 ? "" : "s"} overlapping a mining terrain or a major groundwater reservoir):`,
790
+ "",
791
+ ];
792
+ let n = 0;
793
+ for (const r of data) {
794
+ const cells = [];
795
+ if (r.mining_status) {
796
+ const cls = r.mineral_class ?? null;
797
+ const clsNote = cls ? MINERAL_CLASS_NOTE[cls] : undefined;
798
+ const pct = subsurfacePct(r.mining_overlap_pct);
799
+ cells.push(`mining terrain: ${r.mining_status}${cls ? `, ${cls}${clsNote ? ` (${clsNote})` : ""}` : ""}${pct != null ? `, ${pct}% of the parcel in the terrain` : ""}`);
800
+ if (Array.isArray(r.mining_terrains) && r.mining_terrains.length > 0) {
801
+ const labels = r.mining_terrains
802
+ .map((t) => {
803
+ const parts = [];
804
+ if (t.name)
805
+ parts.push(t.name);
806
+ if (t.oversight_authority)
807
+ parts.push(`oversight: ${t.oversight_authority}`);
808
+ if (t.valid_until)
809
+ parts.push(`valid until ${t.valid_until}`);
810
+ if (t.revoked_on)
811
+ parts.push(`revoked ${t.revoked_on}`);
812
+ return parts.length > 0 ? parts.join(", ") : null;
813
+ })
814
+ .filter((s) => !!s);
815
+ if (labels.length > 0)
816
+ cells.push(`terrains: ${labels.join("; ")}${r.mining_terrains_capped ? " (list reached the 5-entry cap)" : ""}`);
817
+ }
818
+ }
819
+ if (r.groundwater_status) {
820
+ const pct = subsurfacePct(r.groundwater_overlap_pct);
821
+ cells.push(`groundwater reservoir: ${r.groundwater_status} (reservoir extent only — not a restriction)${pct != null ? `, ${pct}% of the parcel in the reservoir` : ""}`);
822
+ if (Array.isArray(r.groundwater_bodies) && r.groundwater_bodies.length > 0) {
823
+ const labels = r.groundwater_bodies
824
+ .map((b) => {
825
+ const parts = [];
826
+ if (b.number != null)
827
+ parts.push(`no. ${b.number}`);
828
+ if (b.name)
829
+ parts.push(b.name);
830
+ if (b.documented_year != null)
831
+ parts.push(`documented ${b.documented_year}`);
832
+ if (b.depth_from_m != null)
833
+ parts.push(`from ${b.depth_from_m} m`);
834
+ if (b.medium_type)
835
+ parts.push(b.medium_type);
836
+ return parts.length > 0 ? parts.join(", ") : null;
837
+ })
838
+ .filter((s) => !!s);
839
+ if (labels.length > 0)
840
+ cells.push(`reservoirs: ${labels.join("; ")}${r.groundwater_bodies_capped ? " (list reached the 5-entry cap)" : ""}`);
841
+ }
842
+ }
843
+ if (cells.length === 0)
844
+ continue;
845
+ n += 1;
846
+ lines.push(`${n}. ${cells.join(" | ")}`);
847
+ }
848
+ if (truncated) {
849
+ lines.push("", "Showing the first 500 parcels (the transaction is linked to more).");
850
+ }
851
+ lines.push("", SUBSURFACE_NOTE);
852
+ return lines.join("\n");
853
+ }
501
854
  const SURROUNDINGS_CATEGORIES = [
502
855
  { key: "cemetery_distance_m", label: "cemetery", radiusLabel: "1 km" },
503
856
  { key: "landfill_distance_m", label: "landfill (waste disposal)", radiusLabel: "3 km" },
@@ -505,6 +858,8 @@ const SURROUNDINGS_CATEGORIES = [
505
858
  { key: "industrial_area_distance_m", label: "industrial/storage area", radiusLabel: "1 km" },
506
859
  { key: "industrial_plant_distance_m", label: "large industrial plant", radiusLabel: "3 km" },
507
860
  { key: "livestock_farm_distance_m", label: "intensive livestock farm", radiusLabel: "3 km" },
861
+ { key: "power_line_hv_distance_m", label: "high-voltage overhead power line", radiusLabel: "1 km" },
862
+ { key: "power_line_ehv_distance_m", label: "extra-high-voltage overhead power line", radiusLabel: "1 km" },
508
863
  ];
509
864
  export function formatSurroundings(res) {
510
865
  const { data, truncated } = res;
@@ -520,7 +875,7 @@ export function formatSurroundings(res) {
520
875
  lines.push(`${i + 1}. not assessed yet — this plot has not been evaluated (no statement either way)`);
521
876
  return;
522
877
  }
523
- const cells = SURROUNDINGS_CATEGORIES.map(({ key, label, radiusLabel }) => {
878
+ const cells = SURROUNDINGS_CATEGORIES.filter(({ key }) => key in r).map(({ key, label, radiusLabel }) => {
524
879
  const raw = r[key];
525
880
  const dist = raw == null ? null : Number(raw);
526
881
  if (dist == null || !Number.isFinite(dist)) {
@@ -537,6 +892,64 @@ export function formatSurroundings(res) {
537
892
  }
538
893
  return lines.join("\n");
539
894
  }
895
+ const ROADS_NOTE = "Note: access_indicator is geometric evidence measured from carriageway centrelines in reference road-network data. It does NOT determine legal access and says nothing about easements or rights of way, which are recorded in the land register and are not published here. Distances are approximate and measured from the plot boundary; a public road and a road of any kind are searched within 500 m, a motorway/expressway/dual-carriageway (a traffic-nuisance proxy, not access) within 3 km. A null distance means nothing of that kind within that radius — never a guarantee of absence.";
896
+ const ROAD_INDICATOR_GLOSS = {
897
+ likely: "access likely",
898
+ uncertain: "access uncertain",
899
+ unlikely: "access unlikely",
900
+ };
901
+ function roadCells(r) {
902
+ const cells = [];
903
+ const indicator = typeof r.access_indicator === "string" ? r.access_indicator : null;
904
+ cells.push(indicator ? ROAD_INDICATOR_GLOSS[indicator] ?? `access ${indicator}` : "access not classified");
905
+ if (typeof r.access_rule_version === "number")
906
+ cells.push(`rule v${r.access_rule_version}`);
907
+ const pub = toNum(r.public_road_distance_m);
908
+ const edge = toNum(r.public_road_edge_distance_m);
909
+ if (pub == null) {
910
+ cells.push("public road: none within 500 m");
911
+ }
912
+ else {
913
+ const kind = [r.public_road_category, r.public_road_class].filter((x) => typeof x === "string");
914
+ const edgePart = edge != null ? `, ~${Math.round(edge)} m to the carriageway edge` : "";
915
+ cells.push(`public road: ~${Math.round(pub)} m${kind.length > 0 ? ` (${kind.join(", ")})` : ""}${edgePart}`);
916
+ if (r.public_road_at_grade === false)
917
+ cells.push("that road crosses on a viaduct or in a tunnel");
918
+ }
919
+ const any = toNum(r.any_road_distance_m);
920
+ cells.push(any == null ? "any road: none within 500 m" : `any road: ~${Math.round(any)} m`);
921
+ const major = toNum(r.major_road_distance_m);
922
+ cells.push(major == null ? "major road: none within 3 km" : `major road: ~${Math.round(major)} m`);
923
+ return cells;
924
+ }
925
+ export function formatRoads(res) {
926
+ const { data, truncated } = res;
927
+ if (data.length === 0) {
928
+ return "No road-access data is available for this transaction (no linked plots, or the id was not found).";
929
+ }
930
+ const lines = [
931
+ `Per-parcel road access (${data.length} plot${data.length === 1 ? "" : "s"}; distances from the plot boundary, "~" = approximate):`,
932
+ "",
933
+ ];
934
+ data.forEach((r, i) => {
935
+ if (!r.assessed) {
936
+ lines.push(`${i + 1}. not assessed yet — this plot has not been evaluated (no statement either way)`);
937
+ return;
938
+ }
939
+ lines.push(`${i + 1}. ${roadCells(r).join(" | ")}`);
940
+ });
941
+ if (truncated) {
942
+ lines.push("", "Showing the first 500 plots (the transaction is linked to more).");
943
+ }
944
+ const dates = data
945
+ .map((r) => r.source_as_of)
946
+ .filter((d) => typeof d === "string" && d.length > 0)
947
+ .sort();
948
+ if (dates.length > 0)
949
+ lines.push("", `Reference road-network snapshot as of ${dates[0]} (oldest of the plots shown).`);
950
+ lines.push("", ROADS_NOTE);
951
+ return lines.join("\n");
952
+ }
540
953
  const TRANSIT_COVERAGE_NOTE = "Note: distances are from open public-transport schedules (GTFS format); coverage is cities and national rail, not every rural area. A mode missing above means no stop of that mode was found within its distance cap — never read as 'no public transport access'.";
541
954
  export function formatTransitBreakdown(res) {
542
955
  const { data, truncated } = res;
@@ -565,7 +978,7 @@ export function formatTransitBreakdown(res) {
565
978
  lines.push("", TRANSIT_COVERAGE_NOTE);
566
979
  return lines.join("\n");
567
980
  }
568
- const PERMITS_EMPTY_NOTE = "No positively-resolved building permit or works notification is on record for this transaction's parcels (or the id was not found). The register covers cases resolved since 2016, matched by the parcel's current identifier — an empty list is never a statement that nothing was ever planned.";
981
+ const PERMITS_EMPTY_NOTE = "No building permit or works notification is on record for this transaction's parcels (or the id was not found). Permits are held once a decision has been issued, and notifications only where they were accepted without objection; cases registered since 2016 are matched by the parcel's current identifier — an empty list is never a statement that nothing was ever planned.";
569
982
  export function formatPermitsBreakdown(res) {
570
983
  const { data, truncated } = res;
571
984
  if (data.length === 0) {
@@ -765,6 +1178,74 @@ export function formatLocationHierarchy(items, parent) {
765
1178
  }
766
1179
  return lines.join("\n");
767
1180
  }
1181
+ const SEARCH_LEVEL_ORDER = {
1182
+ voivodeship: 0,
1183
+ county: 1,
1184
+ municipality: 2,
1185
+ precinct: 3,
1186
+ };
1187
+ const SEARCH_LEVEL_LABEL = {
1188
+ voivodeship: "Voivodeships",
1189
+ county: "Counties",
1190
+ municipality: "Municipalities",
1191
+ precinct: "Precincts",
1192
+ };
1193
+ function searchCallsForItem(it) {
1194
+ const code = it.code;
1195
+ const calls = [
1196
+ `search_transactions(teryt="${code}")`,
1197
+ `list_parcels_in_area(teryt="${code}")`,
1198
+ ];
1199
+ if (it.level === "county" || it.level === "municipality") {
1200
+ calls.push(`list_locations(parent="${code}")`);
1201
+ }
1202
+ if (it.level !== "precinct") {
1203
+ calls.push(`get_demographics(teryt="${code}")`);
1204
+ }
1205
+ if (it.level === "county" || it.level === "municipality") {
1206
+ calls.push(`get_infrastructure_signals(teryt="${code}")`);
1207
+ }
1208
+ if (it.rcn_district) {
1209
+ calls.push(`search_transactions(location="${it.name}")`);
1210
+ calls.push(`compare_locations(districts="${it.name},…")`);
1211
+ calls.push(`get_price_statistics(location="${it.name}")`);
1212
+ }
1213
+ return calls;
1214
+ }
1215
+ export function formatLocationSearch(items, rcnOnly, query) {
1216
+ const lines = [`Location search for "${query}":`, ""];
1217
+ const byLevel = new Map();
1218
+ for (const it of items) {
1219
+ const bucket = byLevel.get(it.level) ?? [];
1220
+ bucket.push(it);
1221
+ byLevel.set(it.level, bucket);
1222
+ }
1223
+ const levels = [...byLevel.keys()].sort((a, b) => (SEARCH_LEVEL_ORDER[a] ?? 99) - (SEARCH_LEVEL_ORDER[b] ?? 99));
1224
+ for (const level of levels) {
1225
+ lines.push(`${SEARCH_LEVEL_LABEL[level] ?? level}:`);
1226
+ for (const it of byLevel.get(level)) {
1227
+ const typeSuffix = it.typeName ? ` (${it.typeName})` : "";
1228
+ const parentSuffix = it.parent_name ? `, ${it.parent_name}` : "";
1229
+ lines.push(` ${it.code} - ${it.name}${typeSuffix}${parentSuffix}`);
1230
+ for (const call of searchCallsForItem(it)) {
1231
+ lines.push(` ${call}`);
1232
+ }
1233
+ }
1234
+ lines.push("");
1235
+ }
1236
+ if (rcnOnly.length > 0) {
1237
+ lines.push("RCN district names without a TERYT code:");
1238
+ for (const name of rcnOnly) {
1239
+ lines.push(` - ${name}`);
1240
+ lines.push(` search_transactions(location="${name}")`);
1241
+ }
1242
+ lines.push("");
1243
+ }
1244
+ if (items.length === 50) {
1245
+ lines.push("Showing the first 50 matches — type more letters to narrow the search.");
1246
+ }
1247
+ return lines.join("\n").trimEnd();
1248
+ }
768
1249
  export function formatCompareResults(res) {
769
1250
  const districts = Object.keys(res);
770
1251
  if (districts.length === 0) {
@@ -1067,6 +1548,49 @@ export function formatPriceSpreadLocations(r) {
1067
1548
  }
1068
1549
  return lines.join("\n");
1069
1550
  }
1551
+ const FLOOD_RISK_LOCATIONS_PATH = "flood-risk/locations";
1552
+ export function formatFloodRisk(r) {
1553
+ const q = r.quality;
1554
+ const sev = r.result.by_severity;
1555
+ const lines = [`Flood-hazard exposure — ${r.location.name}`, ""];
1556
+ lines.push(r.result.flood_share_pct != null
1557
+ ? `Share in a mapped flood-hazard zone: ${r.result.flood_share_pct}% of assessed transactions`
1558
+ : `Share: N/A (coverage: ${q.coverage})`);
1559
+ if (sev.low != null || sev.medium != null || sev.high != null) {
1560
+ lines.push("");
1561
+ lines.push("Transactions by severity (return period):");
1562
+ lines.push(` High (~1-in-10-year): ${formatNumber(sev.high ?? 0)}`);
1563
+ lines.push(` Medium (~1-in-100-year): ${formatNumber(sev.medium ?? 0)}`);
1564
+ lines.push(` Low (~1-in-500-year): ${formatNumber(sev.low ?? 0)}`);
1565
+ }
1566
+ lines.push("");
1567
+ lines.push(`Assessed transactions: ${r.inputs.assessed_sample_n != null ? formatNumber(r.inputs.assessed_sample_n) : "N/A"} (all-time)`);
1568
+ lines.push(`Coverage: ${q.coverage} | Confidence: ${q.confidence}${q.stale ? " | transaction data lags publication" : ""}`);
1569
+ if (q.as_of)
1570
+ lines.push(`Transaction data as of: ${q.as_of}`);
1571
+ const visibleNotes = q.notes.filter((n) => !n.includes(FLOOD_RISK_LOCATIONS_PATH));
1572
+ if (visibleNotes.length > 0) {
1573
+ lines.push("", "Notes:");
1574
+ for (const n of visibleNotes)
1575
+ lines.push(` - ${n}`);
1576
+ }
1577
+ return lines.join("\n");
1578
+ }
1579
+ export function formatFloodRiskLocations(r) {
1580
+ const { data, meta } = r;
1581
+ if (data.length === 0) {
1582
+ return "No flood-risk-covered locations match.";
1583
+ }
1584
+ const dateSuffix = meta.snapshot_date ? `, data from ${meta.snapshot_date}` : "";
1585
+ const lines = [
1586
+ `Flood-risk coverage — ${meta.total} location${meta.total === 1 ? "" : "s"}${dateSuffix}`,
1587
+ "",
1588
+ ];
1589
+ for (const loc of data) {
1590
+ lines.push(`- ${loc.location} (teryt ${loc.county_code}, ${loc.voivodeship}, ${loc.type}) — n=${formatNumber(loc.assessed_sample_n)}, ${loc.confidence} confidence`);
1591
+ }
1592
+ return lines.join("\n");
1593
+ }
1070
1594
  function valuationCompLine(c) {
1071
1595
  const parts = [`${formatNumber(c.distance_m)} m`, c.transaction_date, formatArea(c.area_m2), `${formatPLN(c.price_per_m2)}/m²`];
1072
1596
  if (c.market_type)
@@ -1131,6 +1655,15 @@ function fourStateGloss(coverage) {
1131
1655
  default: return coverage;
1132
1656
  }
1133
1657
  }
1658
+ function stringList(v) {
1659
+ return Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
1660
+ }
1661
+ function landClassHeadline(useNames, soilClasses) {
1662
+ return [
1663
+ useNames.length ? useNames.join(", ") : null,
1664
+ soilClasses.length ? `soil class ${soilClasses.join(", ")}` : null,
1665
+ ].filter(Boolean).join("; ");
1666
+ }
1134
1667
  function toNum(v) {
1135
1668
  if (v == null)
1136
1669
  return null;
@@ -1165,6 +1698,19 @@ function reportSectionDetail(name, s) {
1165
1698
  const risk = typeof s.landslide_risk === "string" ? s.landslide_risk : null;
1166
1699
  return risk ? LANDSLIDE_RISK_NOTE[risk] ?? risk : "";
1167
1700
  }
1701
+ case "subsurface": {
1702
+ if (!covered)
1703
+ return "";
1704
+ const parts = [];
1705
+ const mining = typeof s.mining_status === "string" ? s.mining_status : null;
1706
+ const mineral = typeof s.mineral_class === "string" ? s.mineral_class : null;
1707
+ if (mining)
1708
+ parts.push(`mining terrain ${mining}${mineral ? ` (${mineral})` : ""}`);
1709
+ const gw = typeof s.groundwater_status === "string" ? s.groundwater_status : null;
1710
+ if (gw)
1711
+ parts.push(`groundwater reservoir ${gw}`);
1712
+ return parts.join("; ");
1713
+ }
1168
1714
  case "surroundings": {
1169
1715
  if (!covered)
1170
1716
  return "";
@@ -1180,6 +1726,21 @@ function reportSectionDetail(name, s) {
1180
1726
  return "no mapped nuisance object within range";
1181
1727
  return dists.slice(0, 3).map(([k, m]) => `${k} ${Math.round(m)} m`).join(", ");
1182
1728
  }
1729
+ case "roads": {
1730
+ if (!covered)
1731
+ return "";
1732
+ const indicator = typeof s.access_indicator === "string" ? s.access_indicator : null;
1733
+ if (!indicator)
1734
+ return "";
1735
+ const edge = toNum(s.public_road_edge_distance_m);
1736
+ const pub = toNum(s.public_road_distance_m);
1737
+ const gloss = ROAD_INDICATOR_GLOSS[indicator] ?? indicator;
1738
+ if (edge != null)
1739
+ return `${gloss} (~${Math.round(edge)} m to the nearest public road's carriageway edge)`;
1740
+ if (pub != null)
1741
+ return `${gloss} (nearest public road ~${Math.round(pub)} m)`;
1742
+ return `${gloss} (no public road within 500 m)`;
1743
+ }
1183
1744
  case "transit": {
1184
1745
  if (!covered)
1185
1746
  return "";
@@ -1204,7 +1765,16 @@ function reportSectionDetail(name, s) {
1204
1765
  if (!covered)
1205
1766
  return "";
1206
1767
  const rows = Array.isArray(s.data) ? s.data : [];
1207
- return `${rows.length} building(s) on the parcel`;
1768
+ const ages = rows
1769
+ .map((r) => r.age_estimate?.status)
1770
+ .filter((x) => x != null);
1771
+ const dated = ages.filter((x) => x === "estimated").length;
1772
+ const agePart = ages.length === 0
1773
+ ? ""
1774
+ : dated === 0
1775
+ ? "; construction year not established for any of them"
1776
+ : `; construction year estimated for ${dated} of ${ages.length} (estimate from permit records, not a registry date)`;
1777
+ return `${rows.length} building(s) on the parcel${agePart}`;
1208
1778
  }
1209
1779
  case "permits": {
1210
1780
  if (!covered)
@@ -1219,6 +1789,27 @@ function reportSectionDetail(name, s) {
1219
1789
  const pct = toNum(s.pct_of_parcel);
1220
1790
  return area != null ? `${formatArea(area)} eligible${pct != null ? ` (${pct}% of parcel)` : ""}` : "";
1221
1791
  }
1792
+ case "land_class": {
1793
+ if (!covered)
1794
+ return "";
1795
+ const note = typeof s.legal_note === "string" ? s.legal_note : null;
1796
+ const head = landClassHeadline(stringList(s.use_names), stringList(s.soil_classes));
1797
+ return [head || null, note].filter(Boolean).join(" — ");
1798
+ }
1799
+ case "nature": {
1800
+ if (!covered)
1801
+ return "";
1802
+ const rank = toNum(s.protection_rank);
1803
+ const dist = toNum(s.forest_distance_m);
1804
+ const parts = [];
1805
+ if (rank != null) {
1806
+ const label = PROTECTION_RANK_LABEL[rank] ?? `protection rank ${rank}`;
1807
+ parts.push(s.building_restriction === "statutory_ban" ? `${label} (statutory build ban)` : label);
1808
+ }
1809
+ if (dist != null)
1810
+ parts.push(dist === 0 ? "overlaps forest" : `forest ${Math.round(dist)} m`);
1811
+ return parts.join(", ");
1812
+ }
1222
1813
  default:
1223
1814
  return "";
1224
1815
  }
@@ -1253,23 +1844,27 @@ function reportBillingFooter(billing) {
1253
1844
  const reason = why[billing.rule] ?? billing.rule;
1254
1845
  return `Billing: ${billing.charged} charged, ${billing.refunded} refunded — ${reason}`;
1255
1846
  }
1256
- const REPORT_LAYER_ORDER = [
1847
+ export const REPORT_LAYER_ORDER = [
1257
1848
  ["flood", "Flood risk"],
1258
1849
  ["heritage", "Heritage listing"],
1259
1850
  ["landslide", "Landslide risk"],
1851
+ ["subsurface", "Subsurface constraints"],
1260
1852
  ["surroundings", "Nuisance surroundings"],
1261
1853
  ["transit", "Public transport"],
1262
1854
  ["planning", "Planning (general plan)"],
1263
1855
  ["buildings", "Buildings"],
1264
1856
  ["permits", "Building activity"],
1265
1857
  ["farmland", "Agricultural land"],
1858
+ ["land_class", "Land use & soil class"],
1859
+ ["nature", "Nature (forest & protected areas)"],
1860
+ ["roads", "Road access"],
1266
1861
  ];
1267
1862
  export function formatParcelReport(res) {
1268
1863
  const p = res.parcel;
1269
1864
  const id = p.parcel_id ?? p.parcel_key ?? "(parcel id requires a paid plan)";
1270
1865
  if (res.coverage !== "covered") {
1271
1866
  const head = res.coverage === "not_covered"
1272
- ? `Parcel ${id} could not be resolved — it is not in our cadastral copy.`
1867
+ ? `Parcel ${id} could not be resolved — we do not hold it. Coverage is near-complete but not the whole cadastral register and not live, so this is not a finding that the parcel does not exist.`
1273
1868
  : res.billing.rule === "disabled"
1274
1869
  ? `The composite report is temporarily unavailable for parcel ${id}.`
1275
1870
  : `Parcel ${id} could not be resolved right now (a live lookup did not finish — retry).`;
@@ -1292,6 +1887,8 @@ export function formatParcelReport(res) {
1292
1887
  const sections = res.sections;
1293
1888
  for (const [key, label] of REPORT_LAYER_ORDER) {
1294
1889
  const s = sections[key];
1890
+ if (s == null)
1891
+ continue;
1295
1892
  const detail = reportSectionDetail(key, s);
1296
1893
  lines.push(`- ${label}: ${fourStateGloss(s.coverage)}${detail ? ` — ${detail}` : ""}`);
1297
1894
  }
@@ -1344,3 +1941,44 @@ export function formatParcelReport(res) {
1344
1941
  lines.push("", "---", reportBillingFooter(res.billing));
1345
1942
  return lines.join("\n");
1346
1943
  }
1944
+ export function formatParcelLandClass(res) {
1945
+ const id = res.parcel.parcel_id ?? res.parcel.parcel_key ?? res.parcel.id ?? "(identifier withheld)";
1946
+ const asOfDay = typeof res.as_of === "string" ? res.as_of.split("T")[0] : null;
1947
+ if (res.coverage === "not_computed") {
1948
+ return `The classification lookup for parcel ${id} could not be completed, so this says nothing about what is recorded for it (the tokens are refunded). Retry shortly.`;
1949
+ }
1950
+ if (res.coverage === "not_covered") {
1951
+ const why = res.note ? ` ${res.note}` : "";
1952
+ return `No land-use or soil-quality classification is available for parcel ${id} (the tokens are refunded). Either the county does not publish one, or we hold no classification for this parcel.${why}`;
1953
+ }
1954
+ if (res.coverage === "covered_no_data") {
1955
+ const asOf = asOfDay ? ` (county data as of ${asOfDay})` : "";
1956
+ return `The county publishes the classification, but parcel ${id} has no entry in it${asOf}. That is a checked negative, not a gap in coverage: it says nothing about what the land is, only that the county's dataset holds no entry for this parcel — and it is billed as an answer.`;
1957
+ }
1958
+ if (res.coverage !== "covered") {
1959
+ return `The classification for parcel ${id} came back in a state this client does not recognise (${String(res.coverage)}); it says nothing about what is recorded for the parcel.`;
1960
+ }
1961
+ const useNames = stringList(res.use_names);
1962
+ const soilClasses = stringList(res.soil_classes);
1963
+ const useCodes = stringList(res.use_codes);
1964
+ const lines = [`Land-use and soil-quality classification: ${id}`];
1965
+ const headline = landClassHeadline(useNames, soilClasses);
1966
+ if (headline)
1967
+ lines.push(headline);
1968
+ if (useCodes.length > 0)
1969
+ lines.push(`Use codes: ${useCodes.join(", ")}`);
1970
+ lines.push(`Protected soil grade (I-III) present: ${res.protected_class_present ? "yes" : "no"}`);
1971
+ lines.push(`Inside a city's administrative boundary: ${res.in_city == null ? "could not be determined from the parcel id" : res.in_city ? "yes" : "no"}`);
1972
+ lines.push("", "Categories and grades are listed as sets: the source records no area for any of them, so this cannot say which prevails on the parcel.");
1973
+ if (res.legal_note) {
1974
+ lines.push("", `Re-designation: ${res.legal_note}`);
1975
+ }
1976
+ if (res.legal_state_as_of) {
1977
+ lines.push(`Legal state verified as of ${res.legal_state_as_of}.`);
1978
+ }
1979
+ if (asOfDay)
1980
+ lines.push(`Classification data as of ${asOfDay}.`);
1981
+ if (res.note)
1982
+ lines.push("", res.note);
1983
+ return lines.join("\n");
1984
+ }