@land-catalyst/batch-data-sdk 1.2.8 → 1.2.10
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.
- package/dist/builders/builders.d.ts +83 -81
- package/dist/builders/builders.js +88 -13
- package/dist/client/client.d.ts +17 -0
- package/dist/client/client.js +96 -0
- package/dist/core/types.d.ts +163 -49
- package/dist/property-field/metadata.d.ts +55 -2
- package/dist/property-field/metadata.js +1276 -3
- package/dist/property-field/search-criteria-ai-context.d.ts +5 -0
- package/dist/property-field/search-criteria-ai-context.js +198 -12
- package/dist/property-field/search-criteria-filter-context.d.ts +14 -0
- package/dist/property-field/search-criteria-filter-context.js +102 -32
- package/package.json +1 -1
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Builds complete context for AI services to generate SearchCriteria from natural language.
|
|
5
5
|
* This includes all domain documentation, filter types, examples, and rules.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: This module only documents SEARCHABLE fields (fields that can be used in SearchCriteria).
|
|
8
|
+
* Response-only fields (like openLien.mortgages[], owner.names[], sale.lastSale.mortgages[], etc.)
|
|
9
|
+
* are intentionally excluded as they appear in Property responses but cannot be used as search filters.
|
|
10
|
+
* All fields documented here correspond to the SearchCriteria type interfaces in types.ts.
|
|
6
11
|
*/
|
|
7
12
|
/**
|
|
8
13
|
* Domain metadata with description
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Builds complete context for AI services to generate SearchCriteria from natural language.
|
|
6
6
|
* This includes all domain documentation, filter types, examples, and rules.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: This module only documents SEARCHABLE fields (fields that can be used in SearchCriteria).
|
|
9
|
+
* Response-only fields (like openLien.mortgages[], owner.names[], sale.lastSale.mortgages[], etc.)
|
|
10
|
+
* are intentionally excluded as they appear in Property responses but cannot be used as search filters.
|
|
11
|
+
* All fields documented here correspond to the SearchCriteria type interfaces in types.ts.
|
|
7
12
|
*/
|
|
8
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
14
|
exports.SEARCH_CRITERIA_DOMAIN_NAMES = exports.SEARCH_CRITERIA_DOMAINS = void 0;
|
|
@@ -212,19 +217,73 @@ function buildSearchCriteriaSystemPrompt(domains, options) {
|
|
|
212
217
|
${domainDocs}
|
|
213
218
|
|
|
214
219
|
FILTER TYPES:
|
|
215
|
-
- StringFilter: { equals?, contains?, startsWith?, endsWith?, inList?, matches? }
|
|
220
|
+
- StringFilter: { equals?, contains?, startsWith?, endsWith?, inList?, notInList?, matches? }
|
|
221
|
+
* equals: Exact string match - use for precise value matching
|
|
222
|
+
* contains: Substring match - matches if the field value contains the specified string anywhere
|
|
223
|
+
* startsWith: Prefix match - matches if the field value starts with the specified string
|
|
224
|
+
* endsWith: Suffix match - matches if the field value ends with the specified string
|
|
225
|
+
* inList: Array of allowed values - matches if field value is in the list (PREFERRED for fields with predefined possible values)
|
|
226
|
+
* notInList: Array of excluded values - matches if field value is NOT in the list (PREFERRED for excluding predefined values)
|
|
227
|
+
* matches: Array of pattern matches - similar to inList but for pattern-based matching
|
|
228
|
+
* For fields with predefined possible values (shown in field documentation), prefer inList (match any) or notInList (exclude) over equals/contains
|
|
229
|
+
* Example: { inList: ["Central", "Window Unit"] } or { notInList: ["None"] } or { contains: "Street" }
|
|
230
|
+
|
|
216
231
|
- NumericRangeFilter: { min?, max? }
|
|
217
|
-
|
|
232
|
+
* min: Minimum value (inclusive) - matches if field value >= min
|
|
233
|
+
* max: Maximum value (inclusive) - matches if field value <= max
|
|
234
|
+
* Can use both min and max together for a range, or just one for one-sided filtering
|
|
235
|
+
* Example: { min: 3, max: 5 } for "3 to 5 bedrooms", { min: 500000 } for "at least $500k", { max: 1000000 } for "under $1M"
|
|
236
|
+
|
|
237
|
+
- DateRangeFilter: { minDate?, maxDate? }
|
|
238
|
+
* minDate: Minimum date (inclusive) - dates in ISO 8601 format YYYY-MM-DD
|
|
239
|
+
* maxDate: Maximum date (inclusive) - dates in ISO 8601 format YYYY-MM-DD
|
|
240
|
+
* For "after [year]" queries, use next year's January 1st: "after 2015" = { minDate: "2016-01-01" }
|
|
241
|
+
* For "before [year]" queries, use that year's December 31st: "before 2020" = { maxDate: "2020-12-31" }
|
|
242
|
+
* Example: { minDate: "2016-01-01" } for "built after 2015", { maxDate: "2020-12-31" } for "sold before 2021"
|
|
243
|
+
|
|
218
244
|
- BooleanFilter: { equals: boolean }
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
245
|
+
* equals: Exact boolean match - must be true or false
|
|
246
|
+
* Use for fields that only have true/false values (e.g., ownerOccupied, hasChildren)
|
|
247
|
+
* Example: { equals: true } for "owner occupied properties", { equals: false } for "non-owner occupied"
|
|
248
|
+
|
|
249
|
+
- QuickListValue: string
|
|
250
|
+
* Single quickList value string - matches properties with that specific characteristic
|
|
251
|
+
* Can prefix with "not-" to exclude: "not-vacant" excludes vacant properties
|
|
252
|
+
* Valid values include: "vacant", "preforeclosure", "tax-default", "fix-and-flip", "absentee-owner", etc.
|
|
253
|
+
* See QUICKLIST VALUES section for complete list and descriptions
|
|
254
|
+
* Example: "vacant" or "not-active-listing"
|
|
255
|
+
|
|
256
|
+
- GeoLocationDistance: { geoPoint: { latitude, longitude }, distanceMiles?, distanceKilometers?, distanceMeters?, distanceFeet?, distanceYards? }
|
|
257
|
+
* geoPoint: Center point with latitude and longitude (both required)
|
|
258
|
+
* distanceMiles: Search radius in miles (one distance unit required)
|
|
259
|
+
* distanceKilometers: Search radius in kilometers (alternative to miles)
|
|
260
|
+
* distanceMeters: Search radius in meters (alternative to miles)
|
|
261
|
+
* distanceFeet: Search radius in feet (alternative to miles)
|
|
262
|
+
* distanceYards: Search radius in yards (alternative to miles)
|
|
263
|
+
* Matches properties within the specified distance of the center point
|
|
264
|
+
* Example: { geoPoint: { latitude: 33.4484, longitude: -112.0740 }, distanceMiles: "5" } for "within 5 miles of Phoenix"
|
|
265
|
+
|
|
266
|
+
- GeoLocationBoundingBox: { nwGeoPoint: { latitude, longitude }, seGeoPoint: { latitude, longitude } }
|
|
267
|
+
* nwGeoPoint: Northwest corner of the bounding box (top-left)
|
|
268
|
+
* seGeoPoint: Southeast corner of the bounding box (bottom-right)
|
|
269
|
+
* Matches properties within the rectangular area defined by these two points
|
|
270
|
+
* Useful for filtering by geographic regions or specific areas
|
|
271
|
+
* Example: { nwGeoPoint: { latitude: 33.5, longitude: -112.1 }, seGeoPoint: { latitude: 33.3, longitude: -112.0 } }
|
|
272
|
+
|
|
273
|
+
- GeoLocationPolygon: { geoPoints: [{ latitude, longitude }, ...] }
|
|
274
|
+
* geoPoints: Array of GeoPoint objects defining the polygon vertices
|
|
275
|
+
* Matches properties within the polygon area defined by connecting the points in order
|
|
276
|
+
* Requires at least 3 points to form a valid polygon
|
|
277
|
+
* Useful for irregularly shaped geographic areas
|
|
278
|
+
* Example: { geoPoints: [{ latitude: 33.5, longitude: -112.1 }, { latitude: 33.4, longitude: -112.1 }, { latitude: 33.4, longitude: -112.0 }, { latitude: 33.5, longitude: -112.0 }] }
|
|
223
279
|
|
|
224
280
|
IMPORTANT RULES:
|
|
225
281
|
1. Always include a "query" field with geographic scope (state, county, city, or "US")
|
|
226
282
|
2. Use numeric ranges (min/max) for numeric filters like yearBuilt, bedroomCount, price, etc.
|
|
227
|
-
3. Use string filters
|
|
283
|
+
3. Use string filters appropriately:
|
|
284
|
+
- For fields with predefined possible values (shown in field documentation): Use inList (match any) or notInList (exclude) instead of equals/contains
|
|
285
|
+
- For free-text fields: Use equals (exact match), contains (substring), startsWith, endsWith, or inList
|
|
286
|
+
- Example: building.airConditioningSource has predefined values ["Central", "Window Unit", "Wall", ...] - use { inList: ["Central", "Window Unit"] } instead of { equals: "Central" }
|
|
228
287
|
4. Be specific and realistic:
|
|
229
288
|
- "3 bedrooms" = building.bedroomCount: {min: 3, max: 3}
|
|
230
289
|
- "at least 3 bedrooms" = building.bedroomCount: {min: 3}
|
|
@@ -283,6 +342,55 @@ Example response format:
|
|
|
283
342
|
If existing criteria is provided, merge intelligently - update fields mentioned in the prompt, preserve others.
|
|
284
343
|
Return only valid JSON, no markdown formatting or code blocks.`;
|
|
285
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Consolidate repetitive openLien loan fields into groups
|
|
347
|
+
* Groups fields like firstLoanType, secondLoanType, thirdLoanType, fourthLoanType together
|
|
348
|
+
*/
|
|
349
|
+
function consolidateOpenLienFields(fields) {
|
|
350
|
+
const result = [];
|
|
351
|
+
const processed = new Set();
|
|
352
|
+
// Patterns to consolidate: [first|second|third|fourth]Loan[Type|InterestRate]
|
|
353
|
+
const loanTypePattern = /^(first|second|third|fourth)LoanType$/;
|
|
354
|
+
const loanInterestRatePattern = /^(first|second|third|fourth)LoanInterestRate$/;
|
|
355
|
+
// Group loan type fields
|
|
356
|
+
const loanTypeFields = fields.filter((f) => loanTypePattern.test(f.name));
|
|
357
|
+
if (loanTypeFields.length >= 2) {
|
|
358
|
+
// All should have same type and similar descriptions
|
|
359
|
+
const firstField = loanTypeFields[0];
|
|
360
|
+
const allSameType = loanTypeFields.every((f) => f.type === firstField.type);
|
|
361
|
+
if (allSameType) {
|
|
362
|
+
result.push({
|
|
363
|
+
type: "grouped",
|
|
364
|
+
pattern: firstField.description.replace(/^(First|Second|Third|Fourth)\s+/i, "[First/Second/Third/Fourth] "),
|
|
365
|
+
fieldNames: loanTypeFields.map((f) => f.name).sort(),
|
|
366
|
+
templateField: firstField,
|
|
367
|
+
});
|
|
368
|
+
loanTypeFields.forEach((f) => processed.add(f.name));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Group loan interest rate fields
|
|
372
|
+
const loanInterestRateFields = fields.filter((f) => loanInterestRatePattern.test(f.name));
|
|
373
|
+
if (loanInterestRateFields.length >= 2) {
|
|
374
|
+
const firstField = loanInterestRateFields[0];
|
|
375
|
+
const allSameType = loanInterestRateFields.every((f) => f.type === firstField.type);
|
|
376
|
+
if (allSameType) {
|
|
377
|
+
result.push({
|
|
378
|
+
type: "grouped",
|
|
379
|
+
pattern: firstField.description.replace(/^(first|second|third|fourth)/i, "[first/second/third/fourth]") + " (applies to all listed fields)",
|
|
380
|
+
fieldNames: loanInterestRateFields.map((f) => f.name).sort(),
|
|
381
|
+
templateField: firstField,
|
|
382
|
+
});
|
|
383
|
+
loanInterestRateFields.forEach((f) => processed.add(f.name));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Add remaining unprocessed fields
|
|
387
|
+
for (const field of fields) {
|
|
388
|
+
if (!processed.has(field.name)) {
|
|
389
|
+
result.push(field);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
286
394
|
/**
|
|
287
395
|
* Get context documentation for a specific domain
|
|
288
396
|
* @param domainName The domain name (e.g., "address", "building", "assessment")
|
|
@@ -327,6 +435,15 @@ function getDomainContext(domainName, options = {}) {
|
|
|
327
435
|
doc += "\n Available fields:\n";
|
|
328
436
|
for (const field of fields) {
|
|
329
437
|
doc += ` - ${field.name} (${field.type}): ${field.description}\n`;
|
|
438
|
+
// Add possible values information if available
|
|
439
|
+
const fieldContext = (0, search_criteria_filter_context_1.getSearchCriteriaFilterContext)(field.name);
|
|
440
|
+
if (fieldContext?.possibleValues &&
|
|
441
|
+
fieldContext.possibleValues.length > 0) {
|
|
442
|
+
doc += ` Possible Values (${fieldContext.possibleValues.length} total): `;
|
|
443
|
+
// Show all possible values inline
|
|
444
|
+
doc += fieldContext.possibleValues.map((v) => `"${v}"`).join(", ");
|
|
445
|
+
doc += `\n`;
|
|
446
|
+
}
|
|
330
447
|
// Removed filterGuidance - filter types are already defined in FILTER TYPES section
|
|
331
448
|
if (field.examples && field.examples.length > 0) {
|
|
332
449
|
doc += ` Examples: ${field.examples.join(", ")}\n`;
|
|
@@ -392,6 +509,8 @@ function buildDomainDocumentation(domainNames, options = {}) {
|
|
|
392
509
|
else if (optionalDomains.length > 0) {
|
|
393
510
|
doc += "OPTIONAL DOMAINS:\n";
|
|
394
511
|
}
|
|
512
|
+
// Note: Possible values are shown inline with each field for better context
|
|
513
|
+
// We no longer need a separate constants section since values appear with their fields
|
|
395
514
|
// Add QuickList values section once if quickList domain is included
|
|
396
515
|
if (hasQuickListDomain) {
|
|
397
516
|
doc += "\n";
|
|
@@ -405,6 +524,15 @@ function buildDomainDocumentation(domainNames, options = {}) {
|
|
|
405
524
|
doc += "\n Available fields:\n";
|
|
406
525
|
for (const field of quickListDomain.fields) {
|
|
407
526
|
doc += ` - ${field.name} (${field.type}): ${field.description}\n`;
|
|
527
|
+
// Add possible values information if available
|
|
528
|
+
const fieldContext = (0, search_criteria_filter_context_1.getSearchCriteriaFilterContext)(field.name);
|
|
529
|
+
if (fieldContext?.possibleValues &&
|
|
530
|
+
fieldContext.possibleValues.length > 0) {
|
|
531
|
+
doc += ` Possible Values (${fieldContext.possibleValues.length} total): `;
|
|
532
|
+
// Show all possible values inline
|
|
533
|
+
doc += fieldContext.possibleValues.map((v) => `"${v}"`).join(", ");
|
|
534
|
+
doc += `\n`;
|
|
535
|
+
}
|
|
408
536
|
// Removed filterGuidance - filter types are already defined in FILTER TYPES section
|
|
409
537
|
if (field.examples && field.examples.length > 0) {
|
|
410
538
|
doc += ` Examples: ${field.examples.join(", ")}\n`;
|
|
@@ -417,11 +545,69 @@ function buildDomainDocumentation(domainNames, options = {}) {
|
|
|
417
545
|
doc += `\n- ${domain.name}: ${domain.description}`;
|
|
418
546
|
if (domain.fields.length > 0) {
|
|
419
547
|
doc += "\n Available fields:\n";
|
|
420
|
-
for
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
548
|
+
// Special handling for openLien domain to consolidate repetitive loan fields
|
|
549
|
+
if (domain.name === "openLien") {
|
|
550
|
+
const consolidatedFields = consolidateOpenLienFields(domain.fields);
|
|
551
|
+
for (const fieldOrGroup of consolidatedFields) {
|
|
552
|
+
if (fieldOrGroup.type === "grouped") {
|
|
553
|
+
// Handle grouped fields (e.g., firstLoanType, secondLoanType, etc.)
|
|
554
|
+
const group = fieldOrGroup;
|
|
555
|
+
doc += ` - ${group.fieldNames.join(", ")} (${group.templateField.type}): ${group.pattern}\n`;
|
|
556
|
+
const templateContext = (0, search_criteria_filter_context_1.getDomainFilterContexts)(domain.name).find((ctx) => ctx.searchCriteriaPath.split(".").pop() ===
|
|
557
|
+
group.templateField.name ||
|
|
558
|
+
ctx.searchCriteriaPath === group.templateField.name);
|
|
559
|
+
if (templateContext?.possibleValues &&
|
|
560
|
+
templateContext.possibleValues.length > 0) {
|
|
561
|
+
doc += ` Possible Values (${templateContext.possibleValues.length} total): `;
|
|
562
|
+
// Show all possible values inline
|
|
563
|
+
doc +=
|
|
564
|
+
templateContext.possibleValues.map((v) => `"${v}"`).join(", ") +
|
|
565
|
+
`\n`;
|
|
566
|
+
}
|
|
567
|
+
if (group.templateField.examples &&
|
|
568
|
+
group.templateField.examples.length > 0) {
|
|
569
|
+
doc += ` Examples: ${group.templateField.examples.join(", ")}\n`;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
// Handle individual fields
|
|
574
|
+
const field = fieldOrGroup;
|
|
575
|
+
doc += ` - ${field.name} (${field.type}): ${field.description}\n`;
|
|
576
|
+
const fieldContext = (0, search_criteria_filter_context_1.getDomainFilterContexts)(domain.name).find((ctx) => ctx.searchCriteriaPath.split(".").pop() === field.name ||
|
|
577
|
+
ctx.searchCriteriaPath === field.name);
|
|
578
|
+
if (fieldContext?.possibleValues &&
|
|
579
|
+
fieldContext.possibleValues.length > 0) {
|
|
580
|
+
doc += ` Possible Values (${fieldContext.possibleValues.length} total): `;
|
|
581
|
+
// Show all possible values inline
|
|
582
|
+
doc +=
|
|
583
|
+
fieldContext.possibleValues.map((v) => `"${v}"`).join(", ") +
|
|
584
|
+
`\n`;
|
|
585
|
+
}
|
|
586
|
+
if (field.examples && field.examples.length > 0) {
|
|
587
|
+
doc += ` Examples: ${field.examples.join(", ")}\n`;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
// Standard output for other domains
|
|
594
|
+
for (const field of domain.fields) {
|
|
595
|
+
doc += ` - ${field.name} (${field.type}): ${field.description}\n`;
|
|
596
|
+
// Add possible values information if available
|
|
597
|
+
const fieldContext = (0, search_criteria_filter_context_1.getDomainFilterContexts)(domain.name).find((ctx) => ctx.searchCriteriaPath.split(".").pop() === field.name ||
|
|
598
|
+
ctx.searchCriteriaPath === field.name);
|
|
599
|
+
if (fieldContext?.possibleValues &&
|
|
600
|
+
fieldContext.possibleValues.length > 0) {
|
|
601
|
+
doc += ` Possible Values (${fieldContext.possibleValues.length} total): `;
|
|
602
|
+
// Show all possible values inline
|
|
603
|
+
doc +=
|
|
604
|
+
fieldContext.possibleValues.map((v) => `"${v}"`).join(", ") +
|
|
605
|
+
`\n`;
|
|
606
|
+
}
|
|
607
|
+
// Removed filterGuidance - filter types are already defined in FILTER TYPES section
|
|
608
|
+
if (field.examples && field.examples.length > 0) {
|
|
609
|
+
doc += ` Examples: ${field.examples.join(", ")}\n`;
|
|
610
|
+
}
|
|
425
611
|
}
|
|
426
612
|
}
|
|
427
613
|
}
|
|
@@ -41,6 +41,14 @@ export interface SearchCriteriaFilterContext {
|
|
|
41
41
|
* Additional context about how this filter should be used
|
|
42
42
|
*/
|
|
43
43
|
filterGuidance: string;
|
|
44
|
+
/**
|
|
45
|
+
* Possible values for this field (if it has a limited set of allowed values)
|
|
46
|
+
*/
|
|
47
|
+
possibleValues?: string[];
|
|
48
|
+
/**
|
|
49
|
+
* Reference to the constant name for possible values (if applicable)
|
|
50
|
+
*/
|
|
51
|
+
possibleValuesRef?: string;
|
|
44
52
|
}
|
|
45
53
|
/**
|
|
46
54
|
* Get filter context for a SearchCriteria field path
|
|
@@ -75,6 +83,12 @@ export declare function getSearchCriteriaFilterContextString(searchCriteriaPath:
|
|
|
75
83
|
* @returns Formatted string with all QuickList values and descriptions
|
|
76
84
|
*/
|
|
77
85
|
export declare function getQuickListValuesSection(): string;
|
|
86
|
+
/**
|
|
87
|
+
* Get formatted Possible Values Constants section for documentation
|
|
88
|
+
* This defines all possible value constants once, which can then be referenced by fields
|
|
89
|
+
* @returns Formatted string with all possible value constants
|
|
90
|
+
*/
|
|
91
|
+
export declare function getPossibleValuesConstantsSection(): string;
|
|
78
92
|
/**
|
|
79
93
|
* Get filter context for all fields in a SearchCriteria domain/group
|
|
80
94
|
* @param domainName The domain name (e.g., "address", "building", "assessment", "quickList")
|
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.getSearchCriteriaFilterContext = getSearchCriteriaFilterContext;
|
|
10
10
|
exports.getSearchCriteriaFilterContextString = getSearchCriteriaFilterContextString;
|
|
11
11
|
exports.getQuickListValuesSection = getQuickListValuesSection;
|
|
12
|
+
exports.getPossibleValuesConstantsSection = getPossibleValuesConstantsSection;
|
|
12
13
|
exports.getDomainFilterContexts = getDomainFilterContexts;
|
|
13
14
|
exports.getDomainFilterContextDocumentation = getDomainFilterContextDocumentation;
|
|
14
15
|
const metadata_1 = require("./metadata");
|
|
@@ -191,11 +192,14 @@ const SEARCH_CRITERIA_FILTER_TYPE_MAP = {
|
|
|
191
192
|
"valuation.equityPercent": "NumericRangeFilter",
|
|
192
193
|
};
|
|
193
194
|
/**
|
|
194
|
-
* Get filter guidance text based on filter type
|
|
195
|
+
* Get filter guidance text based on filter type and whether field has possible values
|
|
195
196
|
*/
|
|
196
|
-
function getFilterGuidance(filterType) {
|
|
197
|
+
function getFilterGuidance(filterType, hasPossibleValues) {
|
|
197
198
|
switch (filterType) {
|
|
198
199
|
case "StringFilter":
|
|
200
|
+
if (hasPossibleValues) {
|
|
201
|
+
return `Use StringFilter with: equals (exact match), inList (array of allowed values - RECOMMENDED for fields with predefined values), or notInList (array of excluded values). For fields with multiple possible values, prefer inList/notInList over equals/contains. Example: { equals: "Central" } or { inList: ["Central", "Window Unit", "Wall"] } or { notInList: ["None"] }`;
|
|
202
|
+
}
|
|
199
203
|
return `Use StringFilter with: equals (exact match), contains (substring), startsWith, endsWith, inList (array of values), or matches (regex). Example: { equals: "Phoenix" } or { inList: ["Phoenix", "Tucson"] }`;
|
|
200
204
|
case "NumericRangeFilter":
|
|
201
205
|
return `Use NumericRangeFilter with: min (minimum value), max (maximum value), or both. Example: { min: 3, max: 5 } for "3-5 bedrooms" or { min: 2010 } for "built after 2010"`;
|
|
@@ -212,39 +216,62 @@ function getFilterGuidance(filterType) {
|
|
|
212
216
|
/**
|
|
213
217
|
* Get example values based on field and filter type
|
|
214
218
|
*/
|
|
215
|
-
function getExamples(fieldPath, filterType,
|
|
219
|
+
function getExamples(fieldPath, filterType, propertyMetadata) {
|
|
216
220
|
const examples = [];
|
|
217
221
|
switch (filterType) {
|
|
218
|
-
case "StringFilter":
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
examples
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
examples.push(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
examples.push(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
examples.push('{ equals: "Sunset Hills" }');
|
|
239
|
-
examples.push('{ contains: "Hills" } // subdivisions containing "Hills"');
|
|
222
|
+
case "StringFilter": {
|
|
223
|
+
// If field has possible values, prioritize inList/notInList examples
|
|
224
|
+
const possibleValues = propertyMetadata
|
|
225
|
+
? (0, metadata_1.getPossibleValues)(propertyMetadata)
|
|
226
|
+
: undefined;
|
|
227
|
+
if (possibleValues && possibleValues.length > 0) {
|
|
228
|
+
// Show examples using inList and notInList for fields with predefined values
|
|
229
|
+
const sampleValues = possibleValues.slice(0, 3);
|
|
230
|
+
examples.push(`{ equals: "${sampleValues[0]}" } // exact match`);
|
|
231
|
+
examples.push(`{ inList: [${sampleValues.map((v) => `"${v}"`).join(", ")}] } // match any of these values`);
|
|
232
|
+
if (possibleValues.length > 3) {
|
|
233
|
+
examples.push(`{ inList: [${sampleValues.map((v) => `"${v}"`).join(", ")}, ...] } // can include more values from the allowed set`);
|
|
234
|
+
}
|
|
235
|
+
examples.push(`{ notInList: ["${sampleValues[0]}"] } // exclude this value`);
|
|
236
|
+
if (possibleValues.length > 1) {
|
|
237
|
+
examples.push(`{ notInList: [${sampleValues
|
|
238
|
+
.slice(0, 2)
|
|
239
|
+
.map((v) => `"${v}"`)
|
|
240
|
+
.join(", ")}] } // exclude multiple values`);
|
|
241
|
+
}
|
|
240
242
|
}
|
|
241
|
-
else
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
else {
|
|
244
|
+
// Generic string filter examples for fields without predefined values
|
|
245
|
+
if (fieldPath.includes("city")) {
|
|
246
|
+
examples.push('{ equals: "Phoenix" }');
|
|
247
|
+
examples.push('{ inList: ["Phoenix", "Tucson", "Mesa"] }');
|
|
248
|
+
}
|
|
249
|
+
else if (fieldPath.includes("state")) {
|
|
250
|
+
examples.push('{ equals: "AZ" }');
|
|
251
|
+
}
|
|
252
|
+
else if (fieldPath.includes("status")) {
|
|
253
|
+
examples.push('{ equals: "active" }');
|
|
254
|
+
}
|
|
255
|
+
else if (fieldPath.includes("street")) {
|
|
256
|
+
examples.push('{ equals: "123 Main Street" }');
|
|
257
|
+
examples.push('{ contains: "Main" } // streets containing "Main"');
|
|
258
|
+
}
|
|
259
|
+
else if (fieldPath.includes("description")) {
|
|
260
|
+
examples.push('{ contains: "pool" } // listings containing "pool"');
|
|
261
|
+
examples.push('{ contains: "renovated" } // listings containing "renovated"');
|
|
262
|
+
}
|
|
263
|
+
else if (fieldPath.includes("subdivisionName")) {
|
|
264
|
+
examples.push('{ equals: "Sunset Hills" }');
|
|
265
|
+
examples.push('{ contains: "Hills" } // subdivisions containing "Hills"');
|
|
266
|
+
}
|
|
267
|
+
else if (fieldPath.includes("Name") &&
|
|
268
|
+
(fieldPath.includes("firstName") || fieldPath.includes("lastName"))) {
|
|
269
|
+
examples.push('{ equals: "Smith" }');
|
|
270
|
+
examples.push('{ contains: "Smith" } // names containing "Smith"');
|
|
271
|
+
}
|
|
245
272
|
}
|
|
246
|
-
// Removed generic examples - only include meaningful, field-specific ones
|
|
247
273
|
break;
|
|
274
|
+
}
|
|
248
275
|
case "NumericRangeFilter":
|
|
249
276
|
if (fieldPath.includes("salePropensity")) {
|
|
250
277
|
// Sale propensity is a 0-100 AVM score predicting likelihood to go on market and sell
|
|
@@ -513,7 +540,7 @@ function getSearchCriteriaFilterContext(searchCriteriaPath) {
|
|
|
513
540
|
]
|
|
514
541
|
: []),
|
|
515
542
|
],
|
|
516
|
-
filterGuidance: getFilterGuidance("QuickListValue"),
|
|
543
|
+
filterGuidance: getFilterGuidance("QuickListValue", false), // hasPossibleValues not used for QuickListValue
|
|
517
544
|
};
|
|
518
545
|
}
|
|
519
546
|
const filterType = SEARCH_CRITERIA_FILTER_TYPE_MAP[searchCriteriaPath] || "StringFilter";
|
|
@@ -562,9 +589,14 @@ function getSearchCriteriaFilterContext(searchCriteriaPath) {
|
|
|
562
589
|
const fieldName = parts[parts.length - 1] || searchCriteriaPath;
|
|
563
590
|
description = `Filter on ${fieldName} field`;
|
|
564
591
|
}
|
|
592
|
+
// Get possible values if available
|
|
593
|
+
const possibleValues = propertyMetadata
|
|
594
|
+
? (0, metadata_1.getPossibleValues)(propertyMetadata)
|
|
595
|
+
: undefined;
|
|
596
|
+
const possibleValuesRef = propertyMetadata?.possibleValuesRef;
|
|
565
597
|
// Get examples and guidance
|
|
566
598
|
const examples = getExamples(searchCriteriaPath, filterType, propertyMetadata);
|
|
567
|
-
const filterGuidance = getFilterGuidance(filterType);
|
|
599
|
+
const filterGuidance = getFilterGuidance(filterType, !!possibleValues && possibleValues.length > 0);
|
|
568
600
|
return {
|
|
569
601
|
searchCriteriaPath,
|
|
570
602
|
filterType,
|
|
@@ -573,6 +605,8 @@ function getSearchCriteriaFilterContext(searchCriteriaPath) {
|
|
|
573
605
|
description,
|
|
574
606
|
examples,
|
|
575
607
|
filterGuidance,
|
|
608
|
+
possibleValues,
|
|
609
|
+
possibleValuesRef,
|
|
576
610
|
};
|
|
577
611
|
}
|
|
578
612
|
/**
|
|
@@ -595,6 +629,20 @@ function getSearchCriteriaFilterContextString(searchCriteriaPath) {
|
|
|
595
629
|
if (context.propertyMetadata) {
|
|
596
630
|
parts.push(`Property Field: ${context.propertyPath} (${context.propertyMetadata.dataType})`);
|
|
597
631
|
}
|
|
632
|
+
// Add possible values information if available
|
|
633
|
+
if (context.possibleValues && context.possibleValues.length > 0) {
|
|
634
|
+
parts.push(`\nPossible Values:`);
|
|
635
|
+
if (context.possibleValuesRef) {
|
|
636
|
+
parts.push(` Reference: ${context.possibleValuesRef} (see POSSIBLE VALUES CONSTANTS section for complete list)`);
|
|
637
|
+
}
|
|
638
|
+
// Show first 3-5 values as sample for quick reference
|
|
639
|
+
const sampleSize = Math.min(5, context.possibleValues.length);
|
|
640
|
+
const sampleValues = context.possibleValues.slice(0, sampleSize);
|
|
641
|
+
parts.push(` Sample (${sampleSize} of ${context.possibleValues.length}): ${sampleValues.map((v) => `"${v}"`).join(", ")}${context.possibleValues.length > sampleSize
|
|
642
|
+
? ` ... (see ${context.possibleValuesRef || "constants"} for all ${context.possibleValues.length} values)`
|
|
643
|
+
: ""}`);
|
|
644
|
+
parts.push(` Note: Use inList (match any) or notInList (exclude) for fields with predefined values`);
|
|
645
|
+
}
|
|
598
646
|
parts.push(`\nFilter Guidance:\n${context.filterGuidance}`);
|
|
599
647
|
if (context.examples.length > 0) {
|
|
600
648
|
parts.push(`\nExamples:`);
|
|
@@ -615,6 +663,28 @@ function getQuickListValuesSection() {
|
|
|
615
663
|
}
|
|
616
664
|
return section;
|
|
617
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Get formatted Possible Values Constants section for documentation
|
|
668
|
+
* This defines all possible value constants once, which can then be referenced by fields
|
|
669
|
+
* @returns Formatted string with all possible value constants
|
|
670
|
+
*/
|
|
671
|
+
function getPossibleValuesConstantsSection() {
|
|
672
|
+
let section = `POSSIBLE VALUES CONSTANTS:\n`;
|
|
673
|
+
section += `These constants define allowed values for fields with predefined value sets. Fields reference these constants by name.\n\n`;
|
|
674
|
+
const constants = Object.entries(metadata_1.POSSIBLE_VALUES_CONSTANTS)
|
|
675
|
+
.filter(([constantName]) => constantName !== "StateAbbreviation") // Exclude common knowledge constants
|
|
676
|
+
.sort((a, b) => a[0].localeCompare(b[0]));
|
|
677
|
+
for (const [constantName, values] of constants) {
|
|
678
|
+
const valueArray = values;
|
|
679
|
+
section += `${constantName} (${valueArray.length} values):\n`;
|
|
680
|
+
// Show ALL values - this is the authoritative definition, no truncation
|
|
681
|
+
valueArray.forEach((value) => {
|
|
682
|
+
section += ` - "${value}"\n`;
|
|
683
|
+
});
|
|
684
|
+
section += "\n";
|
|
685
|
+
}
|
|
686
|
+
return section;
|
|
687
|
+
}
|
|
618
688
|
/**
|
|
619
689
|
* Get filter context for all fields in a SearchCriteria domain/group
|
|
620
690
|
* @param domainName The domain name (e.g., "address", "building", "assessment", "quickList")
|
package/package.json
CHANGED