@whiteslove/parsing-lexicon 0.2.0 → 0.2.2

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 (94) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +137 -137
  3. package/index.d.ts +374 -374
  4. package/package.json +77 -75
  5. package/src/central-asia-locations.js +211 -211
  6. package/src/central-asia.js +233 -233
  7. package/src/contact.d.ts +44 -44
  8. package/src/contact.js +159 -159
  9. package/src/countries.js +175 -175
  10. package/src/country-context.d.ts +10 -10
  11. package/src/country-context.js +22 -22
  12. package/src/currency.d.ts +14 -14
  13. package/src/currency.js +66 -66
  14. package/src/geo.js +169 -169
  15. package/src/geography-central-asia.js +42 -42
  16. package/src/geography-detection.d.ts +14 -14
  17. package/src/geography-detection.js +64 -64
  18. package/src/geography-display.d.ts +13 -13
  19. package/src/geography-display.js +113 -113
  20. package/src/geography.js +210 -210
  21. package/src/hiring-advanced.js +147 -147
  22. package/src/hiring-candidate-fields.d.ts +9 -9
  23. package/src/hiring-candidate-fields.js +92 -92
  24. package/src/hiring-context.d.ts +64 -64
  25. package/src/hiring-context.js +412 -412
  26. package/src/hiring-language-extensions.d.ts +19 -19
  27. package/src/hiring-language-extensions.js +139 -139
  28. package/src/hiring-languages.js +135 -135
  29. package/src/hiring-location-fields.d.ts +3 -3
  30. package/src/hiring-location-fields.js +53 -53
  31. package/src/hiring-professions-ro.js +191 -191
  32. package/src/hiring-professions.js +437 -437
  33. package/src/hiring-requirements.d.ts +16 -16
  34. package/src/hiring-requirements.js +170 -170
  35. package/src/hiring-salary-context.d.ts +27 -27
  36. package/src/hiring-salary-context.js +76 -76
  37. package/src/hiring-semantics.d.ts +29 -29
  38. package/src/hiring-semantics.js +218 -218
  39. package/src/hiring-skills.d.ts +21 -21
  40. package/src/hiring-skills.js +315 -315
  41. package/src/hiring-source-aliases.d.ts +32 -32
  42. package/src/hiring-source-aliases.js +185 -185
  43. package/src/hiring-source-semantics.d.ts +31 -31
  44. package/src/hiring-source-semantics.js +293 -293
  45. package/src/hiring-temporal.d.ts +15 -15
  46. package/src/hiring-temporal.js +67 -67
  47. package/src/hiring-vacancy-fields.d.ts +5 -5
  48. package/src/hiring-vacancy-fields.js +23 -23
  49. package/src/hiring-work-semantics.d.ts +34 -34
  50. package/src/hiring-work-semantics.js +72 -72
  51. package/src/hiring.js +180 -180
  52. package/src/housing-address.d.ts +18 -18
  53. package/src/housing-address.js +217 -210
  54. package/src/housing-commercial.d.ts +1 -0
  55. package/src/housing-commercial.js +9 -0
  56. package/src/housing-context.d.ts +36 -36
  57. package/src/housing-context.js +183 -183
  58. package/src/housing-features.js +30 -30
  59. package/src/housing-intent.d.ts +9 -9
  60. package/src/housing-intent.js +132 -132
  61. package/src/housing-listing-fields.js +131 -131
  62. package/src/housing-money.d.ts +7 -7
  63. package/src/housing-money.js +102 -102
  64. package/src/housing-source-aliases.d.ts +8 -8
  65. package/src/housing-source-aliases.js +51 -51
  66. package/src/housing-structured.d.ts +55 -55
  67. package/src/housing-structured.js +219 -219
  68. package/src/housing.js +200 -200
  69. package/src/index.js +45 -44
  70. package/src/kz-location-extensions.js +307 -307
  71. package/src/landmarks.js +21 -21
  72. package/src/lexicon-core.js +58 -58
  73. package/src/location-merge.js +106 -106
  74. package/src/locations.js +456 -456
  75. package/src/money-core.d.ts +10 -10
  76. package/src/money-core.js +81 -81
  77. package/src/money-lexicon.d.ts +5 -5
  78. package/src/money-lexicon.js +79 -79
  79. package/src/money.js +154 -154
  80. package/src/normalization.js +384 -384
  81. package/src/odesa-metropolitan.js +144 -144
  82. package/src/romania-geography.js +47 -47
  83. package/src/tashkent-colloquial.js +80 -72
  84. package/src/tashkent-housing-geography.d.ts +38 -38
  85. package/src/tashkent-housing-geography.js +211 -211
  86. package/src/tashkent-pois.js +215 -215
  87. package/src/tashkent-residential-complexes.js +272 -272
  88. package/src/ua-location-extensions-major.js +118 -118
  89. package/src/ua-location-extensions-metro.js +9 -9
  90. package/src/ua-location-extensions-regional.js +246 -246
  91. package/src/ua-secondary-cities.d.ts +2 -2
  92. package/src/ua-secondary-cities.js +57 -57
  93. package/src/ukraine.js +75 -75
  94. package/src/uz-location-extensions.js +259 -259
@@ -1,102 +1,102 @@
1
- import { aliasesOf, findCanonical } from './normalization.js';
2
- import { CURRENCY_TERMS, NUMBER_MULTIPLIERS } from './money-lexicon.js';
3
- import {
4
- MONEY_NUMBER_PATTERN,
5
- moneyCurrencyFromText,
6
- moneyCurrencyPattern,
7
- parseNumericAmount,
8
- parseScaledAmount,
9
- } from './money-core.js';
10
- import { maskPhoneLikeSpans } from './contact.js';
11
-
12
- const PRICE_KEYWORD = '(?:цена|ціна|нарх(?:и)?|narx|price|стоимост[ьи]|аренд(?:а|ная\\s+плата)?|rent)';
13
- const PRICE_CURRENCY = `(?:${moneyCurrencyPattern()})`;
14
-
15
- function escapeRegex(value) {
16
- return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
17
- }
18
-
19
- export function parseHousingPrice(value, fallbackCurrency = '') {
20
- const original = String(value || '');
21
- if (!original) return Object.freeze({ price: null, currency: fallbackCurrency || '' });
22
-
23
- // Contact spans are removed once, before every money branch. A phone can
24
- // therefore never win as a labelled, currency-tagged or fallback amount.
25
- const text = maskPhoneLikeSpans(original);
26
- let currency = moneyCurrencyFromText(text, fallbackCurrency || '') || '';
27
- const explicit = Boolean(findCanonical(text, CURRENCY_TERMS, { partial: true }));
28
- let price = null;
29
-
30
- // Common Ukrainian/Russian classifieds shorthand: "10 т грн" / "10 т гр".
31
- // Keep this housing-specific instead of adding globally ambiguous aliases
32
- // `т` (tonne) and `гр` (gram) to the shared money lexicon. A nearby price
33
- // keyword plus an explicit hryvnia shorthand makes the intent unambiguous.
34
- const compactThousandUah = text.match(new RegExp(
35
- `${PRICE_KEYWORD}[^\\r\\n]{0,48}?(\\d{1,6}(?:[.,]\\d{1,2})?)\\s*т(?:ыс\\.?)?\\s*(?:гр(?:н)?|₴|uah)(?=$|[^\\p{L}\\p{N}_])`,
36
- 'iu',
37
- ));
38
- if (compactThousandUah) {
39
- const amount = parseNumericAmount(compactThousandUah[1]);
40
- if (amount != null && amount >= 1 && amount <= 5_000_000) {
41
- price = Math.round(amount * 1000);
42
- currency = 'UAH';
43
- }
44
- }
45
-
46
- const labelled = text.match(new RegExp(`${PRICE_KEYWORD}\\s*[:\\-–—]?\\s*(${MONEY_NUMBER_PATTERN})`, 'i'));
47
- if (price == null && labelled) {
48
- const amount = parseNumericAmount(labelled[1]);
49
- if (amount != null && amount >= 50 && amount <= 5_000_000_000) price = amount;
50
- }
51
-
52
- if (price == null) {
53
- let tagged = null;
54
- const reNumSym = new RegExp(`(${MONEY_NUMBER_PATTERN})\\s*${PRICE_CURRENCY}`, 'ig');
55
- const reSymNum = new RegExp(`${PRICE_CURRENCY}\\s*(${MONEY_NUMBER_PATTERN})`, 'ig');
56
- for (const regex of [reNumSym, reSymNum]) {
57
- let match;
58
- while ((match = regex.exec(text)) !== null) {
59
- const amount = parseNumericAmount(match[1]);
60
- if (amount != null && amount >= 50 && amount <= 5_000_000_000 && (tagged == null || amount > tagged)) tagged = amount;
61
- }
62
- }
63
- price = tagged;
64
- }
65
-
66
- if (price == null) {
67
- const scalePattern = [...new Set(NUMBER_MULTIPLIERS.flatMap((entry) => aliasesOf(entry)).filter(Boolean))]
68
- .sort((a, b) => String(b).length - String(a).length)
69
- .map(escapeRegex)
70
- .join('|');
71
- // Multiplier aliases include useful one-letter forms such as `m` and `k`.
72
- // Require the alias to end at a token boundary so measurements/words like
73
- // `500 m2` and `5 minut` cannot be promoted to 500 million / 5 million.
74
- const match = text.match(new RegExp(`(\\d+(?:[.,]\\d+)?)\\s*(${scalePattern})(?=$|[^\\p{L}\\p{N}_])`, 'iu'));
75
- if (match) {
76
- const amount = parseScaledAmount(match[1], match[2]);
77
- if (amount != null && amount >= 1000 && amount <= 5_000_000_000) price = Math.round(amount);
78
- }
79
- }
80
-
81
- if (price == null) {
82
- const matches = text.match(/\d{1,3}(?:[ \u00A0.,]\d{3})+|\d{4,}/g) || [];
83
- let best = null;
84
- for (const raw of matches) {
85
- const digits = raw.replace(/[\s.,]/g, '');
86
- if (digits[0] === '0') continue;
87
- const amount = parseNumericAmount(raw);
88
- if (amount != null && amount >= 1000 && amount <= 5_000_000_000 && (best == null || amount > best)) best = amount;
89
- }
90
- price = best;
91
- }
92
-
93
- if (!explicit && fallbackCurrency === 'UZS' && price != null) {
94
- const dailyUzbek = /(?:kunlik|sutkaga|kecha[- ]?kunduz|посуточн|суточн)/i.test(text);
95
- currency = price >= 1_000_000 || (dailyUzbek && price >= 10_000) ? 'UZS' : 'USD';
96
- }
97
-
98
- return Object.freeze({ price, currency });
99
- }
100
-
101
- // Compatibility name for callers migrating from Flat Finder's local parser.
102
- export const parsePriceFromText = parseHousingPrice;
1
+ import { aliasesOf, findCanonical } from './normalization.js';
2
+ import { CURRENCY_TERMS, NUMBER_MULTIPLIERS } from './money-lexicon.js';
3
+ import {
4
+ MONEY_NUMBER_PATTERN,
5
+ moneyCurrencyFromText,
6
+ moneyCurrencyPattern,
7
+ parseNumericAmount,
8
+ parseScaledAmount,
9
+ } from './money-core.js';
10
+ import { maskPhoneLikeSpans } from './contact.js';
11
+
12
+ const PRICE_KEYWORD = '(?:цена|ціна|нарх(?:и)?|narx|price|стоимост[ьи]|аренд(?:а|ная\\s+плата)?|rent)';
13
+ const PRICE_CURRENCY = `(?:${moneyCurrencyPattern()})`;
14
+
15
+ function escapeRegex(value) {
16
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
17
+ }
18
+
19
+ export function parseHousingPrice(value, fallbackCurrency = '') {
20
+ const original = String(value || '');
21
+ if (!original) return Object.freeze({ price: null, currency: fallbackCurrency || '' });
22
+
23
+ // Contact spans are removed once, before every money branch. A phone can
24
+ // therefore never win as a labelled, currency-tagged or fallback amount.
25
+ const text = maskPhoneLikeSpans(original);
26
+ let currency = moneyCurrencyFromText(text, fallbackCurrency || '') || '';
27
+ const explicit = Boolean(findCanonical(text, CURRENCY_TERMS, { partial: true }));
28
+ let price = null;
29
+
30
+ // Common Ukrainian/Russian classifieds shorthand: "10 т грн" / "10 т гр".
31
+ // Keep this housing-specific instead of adding globally ambiguous aliases
32
+ // `т` (tonne) and `гр` (gram) to the shared money lexicon. A nearby price
33
+ // keyword plus an explicit hryvnia shorthand makes the intent unambiguous.
34
+ const compactThousandUah = text.match(new RegExp(
35
+ `${PRICE_KEYWORD}[^\\r\\n]{0,48}?(\\d{1,6}(?:[.,]\\d{1,2})?)\\s*т(?:ыс\\.?)?\\s*(?:гр(?:н)?|₴|uah)(?=$|[^\\p{L}\\p{N}_])`,
36
+ 'iu',
37
+ ));
38
+ if (compactThousandUah) {
39
+ const amount = parseNumericAmount(compactThousandUah[1]);
40
+ if (amount != null && amount >= 1 && amount <= 5_000_000) {
41
+ price = Math.round(amount * 1000);
42
+ currency = 'UAH';
43
+ }
44
+ }
45
+
46
+ const labelled = text.match(new RegExp(`${PRICE_KEYWORD}\\s*[:\\-–—]?\\s*(${MONEY_NUMBER_PATTERN})`, 'i'));
47
+ if (price == null && labelled) {
48
+ const amount = parseNumericAmount(labelled[1]);
49
+ if (amount != null && amount >= 50 && amount <= 5_000_000_000) price = amount;
50
+ }
51
+
52
+ if (price == null) {
53
+ let tagged = null;
54
+ const reNumSym = new RegExp(`(${MONEY_NUMBER_PATTERN})\\s*${PRICE_CURRENCY}`, 'ig');
55
+ const reSymNum = new RegExp(`${PRICE_CURRENCY}\\s*(${MONEY_NUMBER_PATTERN})`, 'ig');
56
+ for (const regex of [reNumSym, reSymNum]) {
57
+ let match;
58
+ while ((match = regex.exec(text)) !== null) {
59
+ const amount = parseNumericAmount(match[1]);
60
+ if (amount != null && amount >= 50 && amount <= 5_000_000_000 && (tagged == null || amount > tagged)) tagged = amount;
61
+ }
62
+ }
63
+ price = tagged;
64
+ }
65
+
66
+ if (price == null) {
67
+ const scalePattern = [...new Set(NUMBER_MULTIPLIERS.flatMap((entry) => aliasesOf(entry)).filter(Boolean))]
68
+ .sort((a, b) => String(b).length - String(a).length)
69
+ .map(escapeRegex)
70
+ .join('|');
71
+ // Multiplier aliases include useful one-letter forms such as `m` and `k`.
72
+ // Require the alias to end at a token boundary so measurements/words like
73
+ // `500 m2` and `5 minut` cannot be promoted to 500 million / 5 million.
74
+ const match = text.match(new RegExp(`(\\d+(?:[.,]\\d+)?)\\s*(${scalePattern})(?=$|[^\\p{L}\\p{N}_])`, 'iu'));
75
+ if (match) {
76
+ const amount = parseScaledAmount(match[1], match[2]);
77
+ if (amount != null && amount >= 1000 && amount <= 5_000_000_000) price = Math.round(amount);
78
+ }
79
+ }
80
+
81
+ if (price == null) {
82
+ const matches = text.match(/\d{1,3}(?:[ \u00A0.,]\d{3})+|\d{4,}/g) || [];
83
+ let best = null;
84
+ for (const raw of matches) {
85
+ const digits = raw.replace(/[\s.,]/g, '');
86
+ if (digits[0] === '0') continue;
87
+ const amount = parseNumericAmount(raw);
88
+ if (amount != null && amount >= 1000 && amount <= 5_000_000_000 && (best == null || amount > best)) best = amount;
89
+ }
90
+ price = best;
91
+ }
92
+
93
+ if (!explicit && fallbackCurrency === 'UZS' && price != null) {
94
+ const dailyUzbek = /(?:kunlik|sutkaga|kecha[- ]?kunduz|посуточн|суточн)/i.test(text);
95
+ currency = price >= 1_000_000 || (dailyUzbek && price >= 10_000) ? 'UZS' : 'USD';
96
+ }
97
+
98
+ return Object.freeze({ price, currency });
99
+ }
100
+
101
+ // Compatibility name for callers migrating from Flat Finder's local parser.
102
+ export const parsePriceFromText = parseHousingPrice;
@@ -1,8 +1,8 @@
1
- export const HOUSING_DEAL_TYPE_EXTENSIONS: readonly unknown[];
2
- export const HOUSING_ROOM_ONLY_EXTENSIONS: readonly unknown[];
3
- export function resolveExtendedHousingIntent(value: unknown): Readonly<{
4
- action: string | null;
5
- listingKind: string | null;
6
- dealType: 'sale' | 'longRent' | 'shortRent';
7
- }> | null;
8
- export function isRoomOnlyHousing(value: unknown): boolean;
1
+ export const HOUSING_DEAL_TYPE_EXTENSIONS: readonly unknown[];
2
+ export const HOUSING_ROOM_ONLY_EXTENSIONS: readonly unknown[];
3
+ export function resolveExtendedHousingIntent(value: unknown): Readonly<{
4
+ action: string | null;
5
+ listingKind: string | null;
6
+ dealType: 'sale' | 'longRent' | 'shortRent';
7
+ }> | null;
8
+ export function isRoomOnlyHousing(value: unknown): boolean;
@@ -1,51 +1,51 @@
1
- import { lexiconEntity } from './lexicon-core.js';
2
- import { findCanonical } from './normalization.js';
3
- import { resolveHousingIntent } from './housing-intent.js';
4
- import { HOUSING_OCCUPANCY_TYPES, PROPERTY_TYPES } from './housing.js';
5
-
6
- const group = (canonical, aliases, extra = {}) => lexiconEntity(canonical, aliases, extra);
7
-
8
- /** Source/feed wording that is useful for normalization but too noisy for the generic core. */
9
- export const HOUSING_DEAL_TYPE_EXTENSIONS = Object.freeze([
10
- group('longRent', {
11
- ru: ['снять', 'в месяц', 'месяц'],
12
- en: ['monthly'],
13
- uzLatn: ['ijaraga', 'oyiga', 'beriladi'],
14
- uzCyrl: ['ижарага', 'ойига', 'берилади'],
15
- kk: ['айына'],
16
- }),
17
- group('shortRent', {
18
- ru: ['сутки', 'суток', 'суточно'],
19
- en: ['daily'],
20
- uzLatn: ['kunlik', 'sutkalik'],
21
- uzCyrl: ['кунлик', 'суткалик'],
22
- kk: ['тәуліктік'],
23
- }),
24
- ]);
25
-
26
- export const HOUSING_ROOM_ONLY_EXTENSIONS = Object.freeze([
27
- group('roomOnly', {
28
- ru: ['шеринг'],
29
- en: ['room only'],
30
- uzLatn: ['student qizlarga', 'talaba qizlarga', 'opshijit dom', 'obshijit dom'],
31
- uzCyrl: ['студент қизларга', 'талаба қизларга'],
32
- }),
33
- ]);
34
-
35
- export function resolveExtendedHousingIntent(value) {
36
- const text = String(value || '');
37
- const base = resolveHousingIntent(text);
38
- if (base) return base;
39
- const deal = findCanonical(text, HOUSING_DEAL_TYPE_EXTENSIONS, { partial: true });
40
- if (!deal?.canonical) return null;
41
- return Object.freeze({ action: null, listingKind: null, dealType: deal.canonical });
42
- }
43
-
44
- export function isRoomOnlyHousing(value) {
45
- const text = String(value || '');
46
- if (!text.trim()) return false;
47
- const occupancy = findCanonical(text, HOUSING_OCCUPANCY_TYPES, { partial: true })?.canonical;
48
- if (occupancy && ['room', 'sharedRoom', 'bedSpace'].includes(occupancy)) return true;
49
- if (findCanonical(text, PROPERTY_TYPES, { partial: true })?.canonical === 'dormitory') return true;
50
- return Boolean(findCanonical(text, HOUSING_ROOM_ONLY_EXTENSIONS, { partial: true }));
51
- }
1
+ import { lexiconEntity } from './lexicon-core.js';
2
+ import { findCanonical } from './normalization.js';
3
+ import { resolveHousingIntent } from './housing-intent.js';
4
+ import { HOUSING_OCCUPANCY_TYPES, PROPERTY_TYPES } from './housing.js';
5
+
6
+ const group = (canonical, aliases, extra = {}) => lexiconEntity(canonical, aliases, extra);
7
+
8
+ /** Source/feed wording that is useful for normalization but too noisy for the generic core. */
9
+ export const HOUSING_DEAL_TYPE_EXTENSIONS = Object.freeze([
10
+ group('longRent', {
11
+ ru: ['снять', 'в месяц', 'месяц'],
12
+ en: ['monthly'],
13
+ uzLatn: ['ijaraga', 'oyiga', 'beriladi'],
14
+ uzCyrl: ['ижарага', 'ойига', 'берилади'],
15
+ kk: ['айына'],
16
+ }),
17
+ group('shortRent', {
18
+ ru: ['сутки', 'суток', 'суточно'],
19
+ en: ['daily'],
20
+ uzLatn: ['kunlik', 'sutkalik'],
21
+ uzCyrl: ['кунлик', 'суткалик'],
22
+ kk: ['тәуліктік'],
23
+ }),
24
+ ]);
25
+
26
+ export const HOUSING_ROOM_ONLY_EXTENSIONS = Object.freeze([
27
+ group('roomOnly', {
28
+ ru: ['шеринг'],
29
+ en: ['room only'],
30
+ uzLatn: ['student qizlarga', 'talaba qizlarga', 'opshijit dom', 'obshijit dom'],
31
+ uzCyrl: ['студент қизларга', 'талаба қизларга'],
32
+ }),
33
+ ]);
34
+
35
+ export function resolveExtendedHousingIntent(value) {
36
+ const text = String(value || '');
37
+ const base = resolveHousingIntent(text);
38
+ if (base) return base;
39
+ const deal = findCanonical(text, HOUSING_DEAL_TYPE_EXTENSIONS, { partial: true });
40
+ if (!deal?.canonical) return null;
41
+ return Object.freeze({ action: null, listingKind: null, dealType: deal.canonical });
42
+ }
43
+
44
+ export function isRoomOnlyHousing(value) {
45
+ const text = String(value || '');
46
+ if (!text.trim()) return false;
47
+ const occupancy = findCanonical(text, HOUSING_OCCUPANCY_TYPES, { partial: true })?.canonical;
48
+ if (occupancy && ['room', 'sharedRoom', 'bedSpace'].includes(occupancy)) return true;
49
+ if (findCanonical(text, PROPERTY_TYPES, { partial: true })?.canonical === 'dormitory') return true;
50
+ return Boolean(findCanonical(text, HOUSING_ROOM_ONLY_EXTENSIONS, { partial: true }));
51
+ }
@@ -1,55 +1,55 @@
1
- export type HousingAreaDetails = Readonly<{
2
- total: number | null;
3
- living: number | null;
4
- kitchen: number | null;
5
- balcony: number | null;
6
- terrace: number | null;
7
- }>;
8
-
9
- export type HousingPaymentContext = Readonly<{
10
- deposit: Readonly<{
11
- required: boolean | null;
12
- kind: string | null;
13
- amount: number | null;
14
- currency: string | null;
15
- }>;
16
- prepaymentMonths: number | null;
17
- utilities: string | null;
18
- commission: Readonly<{
19
- required: boolean | null;
20
- percent: number | null;
21
- }>;
22
- }>;
23
-
24
- export type HousingInfrastructureDistance = Readonly<{
25
- value: number | null;
26
- unit: 'minute' | 'meter' | 'kilometer';
27
- mode: 'walk' | 'drive' | null;
28
- }>;
29
-
30
- export type HousingInfrastructureMatch = Readonly<{
31
- poi: string;
32
- relation: string | null;
33
- distance: HousingInfrastructureDistance | null;
34
- start: number;
35
- end: number;
36
- }>;
37
-
38
- export type HousingStructuredResult = Readonly<{
39
- intent: Readonly<{ action: string | null; listingKind: string | null; dealType: string }> | null;
40
- context: Readonly<Record<string, unknown>>;
41
- rooms: number | null;
42
- floor: Readonly<{ floor: number | null; totalFloors: number | null }>;
43
- area: HousingAreaDetails;
44
- payments: HousingPaymentContext;
45
- seller: Readonly<{ type: 'owner' | 'agency' | null; confidence: number }>;
46
- infrastructure: readonly HousingInfrastructureMatch[];
47
- }>;
48
-
49
- export function parseHousingRoomCount(value: unknown): number | null;
50
- export function parseHousingFloor(value: unknown): Readonly<{ floor: number | null; totalFloors: number | null }>;
51
- export function parseHousingAreas(value: unknown): HousingAreaDetails;
52
- export function parseHousingPayments(value: unknown): HousingPaymentContext;
53
- export function parseHousingSeller(value: unknown): Readonly<{ type: 'owner' | 'agency' | null; confidence: number }>;
54
- export function parseHousingInfrastructure(value: unknown): readonly HousingInfrastructureMatch[];
55
- export function parseHousingStructured(value: unknown): HousingStructuredResult;
1
+ export type HousingAreaDetails = Readonly<{
2
+ total: number | null;
3
+ living: number | null;
4
+ kitchen: number | null;
5
+ balcony: number | null;
6
+ terrace: number | null;
7
+ }>;
8
+
9
+ export type HousingPaymentContext = Readonly<{
10
+ deposit: Readonly<{
11
+ required: boolean | null;
12
+ kind: string | null;
13
+ amount: number | null;
14
+ currency: string | null;
15
+ }>;
16
+ prepaymentMonths: number | null;
17
+ utilities: string | null;
18
+ commission: Readonly<{
19
+ required: boolean | null;
20
+ percent: number | null;
21
+ }>;
22
+ }>;
23
+
24
+ export type HousingInfrastructureDistance = Readonly<{
25
+ value: number | null;
26
+ unit: 'minute' | 'meter' | 'kilometer';
27
+ mode: 'walk' | 'drive' | null;
28
+ }>;
29
+
30
+ export type HousingInfrastructureMatch = Readonly<{
31
+ poi: string;
32
+ relation: string | null;
33
+ distance: HousingInfrastructureDistance | null;
34
+ start: number;
35
+ end: number;
36
+ }>;
37
+
38
+ export type HousingStructuredResult = Readonly<{
39
+ intent: Readonly<{ action: string | null; listingKind: string | null; dealType: string }> | null;
40
+ context: Readonly<Record<string, unknown>>;
41
+ rooms: number | null;
42
+ floor: Readonly<{ floor: number | null; totalFloors: number | null }>;
43
+ area: HousingAreaDetails;
44
+ payments: HousingPaymentContext;
45
+ seller: Readonly<{ type: 'owner' | 'agency' | null; confidence: number }>;
46
+ infrastructure: readonly HousingInfrastructureMatch[];
47
+ }>;
48
+
49
+ export function parseHousingRoomCount(value: unknown): number | null;
50
+ export function parseHousingFloor(value: unknown): Readonly<{ floor: number | null; totalFloors: number | null }>;
51
+ export function parseHousingAreas(value: unknown): HousingAreaDetails;
52
+ export function parseHousingPayments(value: unknown): HousingPaymentContext;
53
+ export function parseHousingSeller(value: unknown): Readonly<{ type: 'owner' | 'agency' | null; confidence: number }>;
54
+ export function parseHousingInfrastructure(value: unknown): readonly HousingInfrastructureMatch[];
55
+ export function parseHousingStructured(value: unknown): HousingStructuredResult;