@land-catalyst/batch-data-sdk 1.1.19 → 1.2.1
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/README.md +19 -1
- package/dist/builders/builders.d.ts +2 -1
- package/dist/builders/builders.js +20 -2
- package/dist/core/types.d.ts +14 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/property-field/search-criteria-ai-context.d.ts +73 -0
- package/dist/property-field/search-criteria-ai-context.js +265 -0
- package/dist/property-field/search-criteria-filter-context.d.ts +92 -0
- package/dist/property-field/search-criteria-filter-context.js +550 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -366,6 +366,23 @@ const subscription = new PropertySubscriptionBuilder()
|
|
|
366
366
|
.build();
|
|
367
367
|
```
|
|
368
368
|
|
|
369
|
+
### Property Subscription with Event Hub
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
const subscription = new PropertySubscriptionBuilder()
|
|
373
|
+
.searchCriteria((c) => c.query("Phoenix, AZ").orQuickLists(["on-market"]))
|
|
374
|
+
.deliveryConfig((dc) =>
|
|
375
|
+
dc.eventHub({
|
|
376
|
+
fullyQualifiedNamespace: "mynamespace.servicebus.windows.net",
|
|
377
|
+
clientId: "12345678-1234-1234-1234-123456789abc",
|
|
378
|
+
tenantId: "87654321-4321-4321-4321-cba987654321",
|
|
379
|
+
clientSecret: "your-client-secret",
|
|
380
|
+
eventHubName: "property-events",
|
|
381
|
+
})
|
|
382
|
+
)
|
|
383
|
+
.build();
|
|
384
|
+
```
|
|
385
|
+
|
|
369
386
|
### Property Lookup by Address
|
|
370
387
|
|
|
371
388
|
```typescript
|
|
@@ -903,7 +920,8 @@ if (response.results?.quicklistCounts) {
|
|
|
903
920
|
- `PropertySubscriptionResponse` - Subscription creation response
|
|
904
921
|
- `PropertySearchResponse` - Property search API response
|
|
905
922
|
- `Property` - Individual property object with all nested data
|
|
906
|
-
- `DeliveryConfig` - Webhook or
|
|
923
|
+
- `DeliveryConfig` - Webhook, Kinesis, or Event Hub delivery configuration
|
|
924
|
+
- `EventHubConfig` - Azure Event Hub configuration
|
|
907
925
|
|
|
908
926
|
### Builders
|
|
909
927
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* .build();
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
import { StringFilter, NumericRangeFilter, DateRangeFilter, GeoLocationDistance, GeoLocationBoundingBox, GeoLocationPolygon, AddressSearchCriteria, AssessmentSearchCriteria, BuildingSearchCriteria, CompAddressSearchCriteria, DemographicsSearchCriteria, ForeclosureSearchCriteria, GeneralSearchCriteria, IdsSearchCriteria, IntelSearchCriteria, InvoluntaryLienSearchCriteria, LegalSearchCriteria, ListingSearchCriteria, LotSearchCriteria, OpenLienSearchCriteria, OwnerSearchCriteria, PermitSearchCriteria, PropertyOwnerProfileSearchCriteria, SaleSearchCriteria, TaxSearchCriteria, ValuationSearchCriteria, OrSearchCriteria, SearchCriteria, DeliveryConfig, PropertySubscriptionRequest, QuickListValueWithNot, PropertyLookupRequest, PropertyLookupRequestItem, PropertyLookupRequestAddress, PropertyLookupOptions, PropertyPermitRequest, PropertySearchRequest, PropertySearchAsyncRequest, PropertyLookupAsyncRequest, GeoPoint } from "../core/types";
|
|
19
|
+
import { StringFilter, NumericRangeFilter, DateRangeFilter, GeoLocationDistance, GeoLocationBoundingBox, GeoLocationPolygon, AddressSearchCriteria, AssessmentSearchCriteria, BuildingSearchCriteria, CompAddressSearchCriteria, DemographicsSearchCriteria, ForeclosureSearchCriteria, GeneralSearchCriteria, IdsSearchCriteria, IntelSearchCriteria, InvoluntaryLienSearchCriteria, LegalSearchCriteria, ListingSearchCriteria, LotSearchCriteria, OpenLienSearchCriteria, OwnerSearchCriteria, PermitSearchCriteria, PropertyOwnerProfileSearchCriteria, SaleSearchCriteria, TaxSearchCriteria, ValuationSearchCriteria, OrSearchCriteria, SearchCriteria, DeliveryConfig, EventHubConfig, PropertySubscriptionRequest, QuickListValueWithNot, PropertyLookupRequest, PropertyLookupRequestItem, PropertyLookupRequestAddress, PropertyLookupOptions, PropertyPermitRequest, PropertySearchRequest, PropertySearchAsyncRequest, PropertyLookupAsyncRequest, GeoPoint } from "../core/types";
|
|
20
20
|
import type { PropertyFieldMetadata } from "../property-field/metadata";
|
|
21
21
|
/**
|
|
22
22
|
* Base interface for all builders
|
|
@@ -735,6 +735,7 @@ export declare class DeliveryConfigBuilder {
|
|
|
735
735
|
static from(config: DeliveryConfig): DeliveryConfigBuilder;
|
|
736
736
|
webhook(url: string, headers?: Record<string, string>, errorUrl?: string): this;
|
|
737
737
|
kinesis(streamName: string, region: string, iamAccessKeyId: string, iamSecretAccessKey: string): this;
|
|
738
|
+
eventHub(config: EventHubConfig): this;
|
|
738
739
|
build(): DeliveryConfig;
|
|
739
740
|
}
|
|
740
741
|
/**
|
|
@@ -90,7 +90,7 @@ class BaseBuilder {
|
|
|
90
90
|
getSearchCriteriaFieldMetadata(searchCriteriaPath) {
|
|
91
91
|
// Dynamic import to avoid circular dependency
|
|
92
92
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
93
|
-
const { getSearchCriteriaFieldMetadata } = require("../property-field/utils");
|
|
93
|
+
const { getSearchCriteriaFieldMetadata, } = require("../property-field/utils");
|
|
94
94
|
return getSearchCriteriaFieldMetadata(searchCriteriaPath);
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
@@ -2095,9 +2095,14 @@ class DeliveryConfigBuilder {
|
|
|
2095
2095
|
this.config.iamSecretAccessKey = iamSecretAccessKey;
|
|
2096
2096
|
return this;
|
|
2097
2097
|
}
|
|
2098
|
+
eventHub(config) {
|
|
2099
|
+
this.config.type = "event-hub";
|
|
2100
|
+
this.config.eventHub = config;
|
|
2101
|
+
return this;
|
|
2102
|
+
}
|
|
2098
2103
|
build() {
|
|
2099
2104
|
if (!this.config.type) {
|
|
2100
|
-
throw new Error("Delivery type (webhook or
|
|
2105
|
+
throw new Error("Delivery type (webhook, kinesis, or event-hub) is required");
|
|
2101
2106
|
}
|
|
2102
2107
|
if (this.config.type === "webhook" && !this.config.url) {
|
|
2103
2108
|
throw new Error("URL is required for webhook delivery");
|
|
@@ -2110,6 +2115,19 @@ class DeliveryConfigBuilder {
|
|
|
2110
2115
|
throw new Error("Stream name, region, IAM access key ID, and secret access key are required for Kinesis delivery");
|
|
2111
2116
|
}
|
|
2112
2117
|
}
|
|
2118
|
+
if (this.config.type === "event-hub") {
|
|
2119
|
+
if (!this.config.eventHub) {
|
|
2120
|
+
throw new Error("Event Hub configuration is required for event-hub delivery");
|
|
2121
|
+
}
|
|
2122
|
+
const { fullyQualifiedNamespace, clientId, tenantId, clientSecret, eventHubName, } = this.config.eventHub;
|
|
2123
|
+
if (!fullyQualifiedNamespace ||
|
|
2124
|
+
!clientId ||
|
|
2125
|
+
!tenantId ||
|
|
2126
|
+
!clientSecret ||
|
|
2127
|
+
!eventHubName) {
|
|
2128
|
+
throw new Error("Fully qualified namespace, client ID, tenant ID, client secret, and event hub name are required for Event Hub delivery");
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2113
2131
|
return this.config;
|
|
2114
2132
|
}
|
|
2115
2133
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -343,7 +343,7 @@ export interface OrSearchCriteria {
|
|
|
343
343
|
* Note: According to API docs, "involuntary-lien" is only listed for quickLists/orQuickLists arrays,
|
|
344
344
|
* but it's included here for consistency. If the API rejects it for quickList, it can be removed.
|
|
345
345
|
*/
|
|
346
|
-
export type QuickListValue = "absentee-owner" | "active-auction" | "active-listing" | "canceled-listing" | "cash-buyer" | "corporate-owned" | "expired-listing" | "failed-listing" | "fix-and-flip" | "free-and-clear" | "for-sale-by-owner" | "has-hoa" | "has-hoa-fees" | "high-equity" | "inherited" | "involuntary-lien" | "in-state-absentee-owner" | "listed-below-market-price" | "low-equity" | "mailing-address-vacant" | "notice-of-default" | "notice-of-lis-pendens" | "notice-of-sale" | "on-market" | "out-of-state-absentee-owner" | "out-of-state-owner" | "owner-occupied" | "pending-listing" | "preforeclosure" | "recently-sold" | "same-property-and-mailing-address" | "tax-default" | "tired-landlord" | "unknown-equity" | "vacant" | "vacant-lot";
|
|
346
|
+
export type QuickListValue = "absentee-owner" | "active-auction" | "active-listing" | "canceled-listing" | "cash-buyer" | "corporate-owned" | "expired-listing" | "failed-listing" | "fix-and-flip" | "free-and-clear" | "for-sale-by-owner" | "has-hoa" | "has-hoa-fees" | "high-equity" | "inherited" | "involuntary-lien" | "in-state-absentee-owner" | "listed-below-market-price" | "low-equity" | "mailing-address-vacant" | "notice-of-default" | "notice-of-lis-pendens" | "notice-of-sale" | "on-market" | "out-of-state-absentee-owner" | "out-of-state-owner" | "owner-occupied" | "pending-listing" | "preforeclosure" | "recently-sold" | "same-property-and-mailing-address" | "tax-default" | "tired-landlord" | "unknown-equity" | "vacant" | "vacant-lot" | "senior-owner" | "trust-owned";
|
|
347
347
|
/**
|
|
348
348
|
* QuickList value that can be prefixed with "not-" to exclude
|
|
349
349
|
*/
|
|
@@ -379,10 +379,20 @@ export interface SearchCriteria {
|
|
|
379
379
|
or?: OrSearchCriteria[];
|
|
380
380
|
}
|
|
381
381
|
/**
|
|
382
|
-
*
|
|
382
|
+
* Event Hub configuration for Azure Event Hubs
|
|
383
|
+
*/
|
|
384
|
+
export interface EventHubConfig {
|
|
385
|
+
fullyQualifiedNamespace: string;
|
|
386
|
+
clientId: string;
|
|
387
|
+
tenantId: string;
|
|
388
|
+
clientSecret: string;
|
|
389
|
+
eventHubName: string;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Delivery configuration for webhook, Kinesis, or Event Hub
|
|
383
393
|
*/
|
|
384
394
|
export interface DeliveryConfig {
|
|
385
|
-
type: "webhook" | "kinesis";
|
|
395
|
+
type: "webhook" | "kinesis" | "event-hub";
|
|
386
396
|
streamName?: string;
|
|
387
397
|
region?: string;
|
|
388
398
|
iamAccessKeyId?: string;
|
|
@@ -390,6 +400,7 @@ export interface DeliveryConfig {
|
|
|
390
400
|
url?: string;
|
|
391
401
|
errorUrl?: string;
|
|
392
402
|
headers?: Record<string, string>;
|
|
403
|
+
eventHub?: EventHubConfig;
|
|
393
404
|
}
|
|
394
405
|
/**
|
|
395
406
|
* Property subscription request payload
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ export * from "./client/client.interface";
|
|
|
12
12
|
export * from "./core/logger.interface";
|
|
13
13
|
export * from "./property-field/metadata";
|
|
14
14
|
export * from "./property-field/utils";
|
|
15
|
+
export * from "./property-field/search-criteria-filter-context";
|
|
16
|
+
export type { SearchCriteriaFilterContext, SearchCriteriaFilterType, } from "./property-field/search-criteria-filter-context";
|
|
17
|
+
export * from "./property-field/search-criteria-ai-context";
|
|
15
18
|
export { type PropertyFieldPathType, type PropertyFieldValueType, type FieldMetadataForPath, } from "./property-field/types";
|
|
16
19
|
export type { SearchCriteriaFieldMapping, SearchCriteriaFieldMetadata, } from "./property-field/utils";
|
|
17
20
|
export type { RequestMiddleware, ResponseMiddleware, ErrorMiddleware, HttpMiddleware, } from "./client/client";
|
package/dist/index.js
CHANGED
|
@@ -28,3 +28,5 @@ __exportStar(require("./client/client.interface"), exports);
|
|
|
28
28
|
__exportStar(require("./core/logger.interface"), exports);
|
|
29
29
|
__exportStar(require("./property-field/metadata"), exports);
|
|
30
30
|
__exportStar(require("./property-field/utils"), exports);
|
|
31
|
+
__exportStar(require("./property-field/search-criteria-filter-context"), exports);
|
|
32
|
+
__exportStar(require("./property-field/search-criteria-ai-context"), exports);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchCriteria AI Context Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds complete context for AI services to generate SearchCriteria from natural language.
|
|
5
|
+
* This includes all domain documentation, filter types, examples, and rules.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Domain metadata with description
|
|
9
|
+
*/
|
|
10
|
+
export interface SearchCriteriaDomain {
|
|
11
|
+
/**
|
|
12
|
+
* Domain name (e.g., "address", "building", "assessment")
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* Response group name used to fetch filter contexts (e.g., "address", "building")
|
|
17
|
+
* If not provided, this domain doesn't have filterable fields
|
|
18
|
+
*/
|
|
19
|
+
responseGroup?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Human-readable description of what this domain filters
|
|
22
|
+
*/
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* All available SearchCriteria domains with their descriptions
|
|
27
|
+
*/
|
|
28
|
+
export declare const SEARCH_CRITERIA_DOMAINS: SearchCriteriaDomain[];
|
|
29
|
+
/**
|
|
30
|
+
* Options for building AI context
|
|
31
|
+
*/
|
|
32
|
+
export interface SearchCriteriaAIContextOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Maximum number of fields to include per domain (to avoid overwhelming the prompt)
|
|
35
|
+
* If not specified, all fields will be included
|
|
36
|
+
*/
|
|
37
|
+
maxFieldsPerDomain?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Maximum number of examples per field
|
|
40
|
+
* If not specified, all examples will be included
|
|
41
|
+
*/
|
|
42
|
+
maxExamplesPerField?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build complete system prompt for AI to generate SearchCriteria from natural language
|
|
46
|
+
* @param options Options for customizing the context
|
|
47
|
+
* @returns Complete system prompt string
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const systemPrompt = buildSearchCriteriaSystemPrompt();
|
|
51
|
+
* const aiService = createAIService(systemPrompt);
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildSearchCriteriaSystemPrompt(options?: SearchCriteriaAIContextOptions): string;
|
|
55
|
+
/**
|
|
56
|
+
* Get context documentation for a specific domain
|
|
57
|
+
* @param domainName The domain name (e.g., "address", "building", "assessment")
|
|
58
|
+
* @param options Options for customizing the context
|
|
59
|
+
* @returns Formatted documentation string for the domain
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const addressContext = getDomainContext("address");
|
|
63
|
+
* // Returns formatted documentation for all address fields
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function getDomainContext(domainName: string, options?: SearchCriteriaAIContextOptions): string;
|
|
67
|
+
/**
|
|
68
|
+
* Build user prompt with existing criteria context
|
|
69
|
+
* @param userPrompt The user's natural language prompt
|
|
70
|
+
* @param existingCriteria Optional existing search criteria to modify
|
|
71
|
+
* @returns Formatted user prompt string
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildSearchCriteriaUserPrompt(userPrompt: string, existingCriteria?: unknown): string;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SearchCriteria AI Context Builder
|
|
4
|
+
*
|
|
5
|
+
* Builds complete context for AI services to generate SearchCriteria from natural language.
|
|
6
|
+
* This includes all domain documentation, filter types, examples, and rules.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SEARCH_CRITERIA_DOMAINS = void 0;
|
|
10
|
+
exports.buildSearchCriteriaSystemPrompt = buildSearchCriteriaSystemPrompt;
|
|
11
|
+
exports.getDomainContext = getDomainContext;
|
|
12
|
+
exports.buildSearchCriteriaUserPrompt = buildSearchCriteriaUserPrompt;
|
|
13
|
+
const search_criteria_filter_context_1 = require("./search-criteria-filter-context");
|
|
14
|
+
/**
|
|
15
|
+
* All available SearchCriteria domains with their descriptions
|
|
16
|
+
*/
|
|
17
|
+
exports.SEARCH_CRITERIA_DOMAINS = [
|
|
18
|
+
{
|
|
19
|
+
name: "query",
|
|
20
|
+
description: 'Geographic scope string (required, e.g., "US", "AZ", "Maricopa County, AZ", "Phoenix, AZ")',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "address",
|
|
24
|
+
responseGroup: "address",
|
|
25
|
+
description: "Address filters for location-based searches",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "assessment",
|
|
29
|
+
responseGroup: "assessment",
|
|
30
|
+
description: "Property assessment and market value filters",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "building",
|
|
34
|
+
responseGroup: "building",
|
|
35
|
+
description: "Building characteristics and features",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "lot",
|
|
39
|
+
responseGroup: "lot",
|
|
40
|
+
description: "Lot characteristics and dimensions",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "owner",
|
|
44
|
+
responseGroup: "owner",
|
|
45
|
+
description: "Owner information and characteristics",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "sale",
|
|
49
|
+
responseGroup: "sale",
|
|
50
|
+
description: "Sale history and transaction information",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "tax",
|
|
54
|
+
responseGroup: "tax",
|
|
55
|
+
description: "Tax information and delinquency status",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "valuation",
|
|
59
|
+
responseGroup: "valuation",
|
|
60
|
+
description: "Property valuation estimates",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "listing",
|
|
64
|
+
responseGroup: "listing",
|
|
65
|
+
description: "Current listing information",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "openLien",
|
|
69
|
+
responseGroup: "openLien",
|
|
70
|
+
description: "Open mortgage and lien information",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "foreclosure",
|
|
74
|
+
responseGroup: "foreclosure",
|
|
75
|
+
description: "Foreclosure status and auction information",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "quickList",
|
|
79
|
+
responseGroup: "quickList",
|
|
80
|
+
description: "Pre-defined quick filters (owner-occupied, vacant, absentee-owner, high-equity, low-equity, free-and-clear, active-listing, recently-sold, preforeclosure, tax-default, etc.). Can be negated with 'not-' prefix.",
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
/**
|
|
84
|
+
* Build complete system prompt for AI to generate SearchCriteria from natural language
|
|
85
|
+
* @param options Options for customizing the context
|
|
86
|
+
* @returns Complete system prompt string
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const systemPrompt = buildSearchCriteriaSystemPrompt();
|
|
90
|
+
* const aiService = createAIService(systemPrompt);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
function buildSearchCriteriaSystemPrompt(options = {}) {
|
|
94
|
+
const { maxFieldsPerDomain, maxExamplesPerField } = options;
|
|
95
|
+
// Build domain documentation
|
|
96
|
+
const domainDocs = buildDomainDocumentation(maxFieldsPerDomain, maxExamplesPerField);
|
|
97
|
+
return `You are an expert at creating property search criteria for real estate professionals. Your task is to convert natural language requests into structured BatchData.io search criteria.
|
|
98
|
+
|
|
99
|
+
${domainDocs}
|
|
100
|
+
|
|
101
|
+
FILTER TYPES:
|
|
102
|
+
- StringFilter: { equals?, contains?, startsWith?, endsWith?, inList?, matches? }
|
|
103
|
+
- NumericRangeFilter: { min?, max? }
|
|
104
|
+
- DateRangeFilter: { minDate?, maxDate? }
|
|
105
|
+
- GeoLocationDistance: { latitude, longitude, distanceMiles }
|
|
106
|
+
- GeoLocationBoundingBox: { minLat, maxLat, minLon, maxLon }
|
|
107
|
+
- GeoLocationPolygon: { geoPoints: [{ latitude, longitude }] }
|
|
108
|
+
|
|
109
|
+
IMPORTANT RULES:
|
|
110
|
+
1. Always include a "query" field with geographic scope (state, county, city, or "US")
|
|
111
|
+
2. Use numeric ranges (min/max) for numeric filters like yearBuilt, bedroomCount, price, etc.
|
|
112
|
+
3. Use string filters (equals, contains, inList) for text fields
|
|
113
|
+
4. Be specific and realistic:
|
|
114
|
+
- "3 bedrooms" = building.bedroomCount: {min: 3, max: 3}
|
|
115
|
+
- "at least 3 bedrooms" = building.bedroomCount: {min: 3}
|
|
116
|
+
- "3-4 bedrooms" = building.bedroomCount: {min: 3, max: 4}
|
|
117
|
+
- "under $400,000" = assessment.totalMarketValue: {max: 400000}
|
|
118
|
+
- "over $500,000" = assessment.totalMarketValue: {min: 500000}
|
|
119
|
+
- "built after 2010" = building.yearBuilt: {min: 2010}
|
|
120
|
+
5. Extract geographic information and populate both query and address filters appropriately
|
|
121
|
+
6. Only include domains that are clearly relevant to the prompt
|
|
122
|
+
7. If modifying existing criteria, merge intelligently - update what's mentioned, keep what's not
|
|
123
|
+
|
|
124
|
+
Return your response as a JSON array. Each object should have:
|
|
125
|
+
- searchCriteria: The complete SearchCriteria object
|
|
126
|
+
- description: A natural language description of what this search criteria finds
|
|
127
|
+
|
|
128
|
+
If the prompt suggests multiple distinct searches, return multiple objects. Otherwise, return a single object.`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get context documentation for a specific domain
|
|
132
|
+
* @param domainName The domain name (e.g., "address", "building", "assessment")
|
|
133
|
+
* @param options Options for customizing the context
|
|
134
|
+
* @returns Formatted documentation string for the domain
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const addressContext = getDomainContext("address");
|
|
138
|
+
* // Returns formatted documentation for all address fields
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
function getDomainContext(domainName, options = {}) {
|
|
142
|
+
const { maxFieldsPerDomain, maxExamplesPerField } = options;
|
|
143
|
+
// Find the domain
|
|
144
|
+
const domain = exports.SEARCH_CRITERIA_DOMAINS.find((d) => d.name === domainName);
|
|
145
|
+
if (!domain) {
|
|
146
|
+
return `Domain "${domainName}" not found.`;
|
|
147
|
+
}
|
|
148
|
+
// Build fields documentation
|
|
149
|
+
const fields = [];
|
|
150
|
+
if (domain.responseGroup) {
|
|
151
|
+
// Get filter contexts for this domain
|
|
152
|
+
const filterContexts = (0, search_criteria_filter_context_1.getDomainFilterContexts)(domain.responseGroup);
|
|
153
|
+
// Apply limits if specified
|
|
154
|
+
const limitedContexts = maxFieldsPerDomain
|
|
155
|
+
? filterContexts.slice(0, maxFieldsPerDomain)
|
|
156
|
+
: filterContexts;
|
|
157
|
+
fields.push(...limitedContexts.map((context) => ({
|
|
158
|
+
name: context.searchCriteriaPath.split(".").pop() ||
|
|
159
|
+
context.searchCriteriaPath,
|
|
160
|
+
type: context.filterType,
|
|
161
|
+
description: context.description,
|
|
162
|
+
filterGuidance: context.filterGuidance,
|
|
163
|
+
examples: maxExamplesPerField
|
|
164
|
+
? context.examples.slice(0, maxExamplesPerField)
|
|
165
|
+
: context.examples,
|
|
166
|
+
})));
|
|
167
|
+
}
|
|
168
|
+
// Build the documentation string
|
|
169
|
+
let doc = `- ${domain.name}: ${domain.description}`;
|
|
170
|
+
if (fields.length > 0) {
|
|
171
|
+
doc += "\n Available fields:\n";
|
|
172
|
+
for (const field of fields) {
|
|
173
|
+
doc += ` - ${field.name} (${field.type}): ${field.description}\n`;
|
|
174
|
+
if (field.filterGuidance) {
|
|
175
|
+
doc += ` Filter: ${field.filterGuidance}\n`;
|
|
176
|
+
}
|
|
177
|
+
if (field.examples && field.examples.length > 0) {
|
|
178
|
+
doc += ` Examples: ${field.examples.join(", ")}\n`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return doc;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Build domain documentation string for all domains
|
|
186
|
+
*/
|
|
187
|
+
function buildDomainDocumentation(maxFieldsPerDomain, maxExamplesPerField) {
|
|
188
|
+
const domains = exports.SEARCH_CRITERIA_DOMAINS.map((domain) => ({
|
|
189
|
+
...domain,
|
|
190
|
+
fields: [],
|
|
191
|
+
}));
|
|
192
|
+
// Populate fields from metadata and filter context for each domain
|
|
193
|
+
for (const domain of domains) {
|
|
194
|
+
if (domain.responseGroup) {
|
|
195
|
+
// Get filter contexts for this domain (includes filter type and examples)
|
|
196
|
+
const filterContexts = (0, search_criteria_filter_context_1.getDomainFilterContexts)(domain.responseGroup);
|
|
197
|
+
// Apply limits if specified
|
|
198
|
+
const limitedContexts = maxFieldsPerDomain
|
|
199
|
+
? filterContexts.slice(0, maxFieldsPerDomain)
|
|
200
|
+
: filterContexts;
|
|
201
|
+
domain.fields = limitedContexts.map((context) => ({
|
|
202
|
+
name: context.searchCriteriaPath.split(".").pop() ||
|
|
203
|
+
context.searchCriteriaPath,
|
|
204
|
+
type: context.filterType,
|
|
205
|
+
description: context.description,
|
|
206
|
+
filterGuidance: context.filterGuidance,
|
|
207
|
+
examples: maxExamplesPerField
|
|
208
|
+
? context.examples.slice(0, maxExamplesPerField)
|
|
209
|
+
: context.examples,
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Build the documentation string
|
|
214
|
+
let doc = "REQUIRED:\n";
|
|
215
|
+
doc += `- query: ${domains[0].description}\n\n`;
|
|
216
|
+
doc += "OPTIONAL DOMAINS:\n";
|
|
217
|
+
for (const domain of domains.slice(1)) {
|
|
218
|
+
doc += `\n- ${domain.name}: ${domain.description}`;
|
|
219
|
+
if (domain.fields.length > 0) {
|
|
220
|
+
doc += "\n Available fields:\n";
|
|
221
|
+
for (const field of domain.fields) {
|
|
222
|
+
doc += ` - ${field.name} (${field.type}): ${field.description}\n`;
|
|
223
|
+
if (field.filterGuidance) {
|
|
224
|
+
doc += ` Filter: ${field.filterGuidance}\n`;
|
|
225
|
+
}
|
|
226
|
+
if (field.examples && field.examples.length > 0) {
|
|
227
|
+
doc += ` Examples: ${field.examples.join(", ")}\n`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return doc;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Build user prompt with existing criteria context
|
|
236
|
+
* @param userPrompt The user's natural language prompt
|
|
237
|
+
* @param existingCriteria Optional existing search criteria to modify
|
|
238
|
+
* @returns Formatted user prompt string
|
|
239
|
+
*/
|
|
240
|
+
function buildSearchCriteriaUserPrompt(userPrompt, existingCriteria) {
|
|
241
|
+
const basePrompt = `Example response format:
|
|
242
|
+
[
|
|
243
|
+
{
|
|
244
|
+
"searchCriteria": {
|
|
245
|
+
"query": "Maricopa County, AZ",
|
|
246
|
+
"building": {
|
|
247
|
+
"bedroomCount": { "min": 3, "max": 3 },
|
|
248
|
+
"yearBuilt": { "min": 2000 }
|
|
249
|
+
},
|
|
250
|
+
"assessment": {
|
|
251
|
+
"totalMarketValue": { "max": 500000 }
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
"description": "3-bedroom homes built after 2000 in Maricopa County, Arizona, with a market value under $500,000"
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
${existingCriteria ? `\nEXISTING SEARCH CRITERIA TO MODIFY:\n${JSON.stringify(existingCriteria, null, 2)}\n\nModify the existing criteria based on the user's prompt. Merge intelligently - update fields mentioned in the prompt, preserve others.` : ""}
|
|
259
|
+
|
|
260
|
+
USER PROMPT:
|
|
261
|
+
${userPrompt}
|
|
262
|
+
|
|
263
|
+
Return only valid JSON, no markdown formatting or code blocks.`;
|
|
264
|
+
return basePrompt;
|
|
265
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchCriteria Filter Context
|
|
3
|
+
*
|
|
4
|
+
* Provides context about how filters should work for each SearchCriteria field.
|
|
5
|
+
* This includes filter type information and usage guidance.
|
|
6
|
+
*/
|
|
7
|
+
import type { PropertyFieldMetadata } from "./metadata";
|
|
8
|
+
/**
|
|
9
|
+
* Filter type for a SearchCriteria field
|
|
10
|
+
*/
|
|
11
|
+
export type SearchCriteriaFilterType = "StringFilter" | "NumericRangeFilter" | "DateRangeFilter" | "QuickListValue";
|
|
12
|
+
/**
|
|
13
|
+
* Context about how a SearchCriteria field should be filtered
|
|
14
|
+
*/
|
|
15
|
+
export interface SearchCriteriaFilterContext {
|
|
16
|
+
/**
|
|
17
|
+
* The SearchCriteria field path (e.g., "address.city", "building.yearBuilt")
|
|
18
|
+
*/
|
|
19
|
+
searchCriteriaPath: string;
|
|
20
|
+
/**
|
|
21
|
+
* The type of filter to use (StringFilter, NumericRangeFilter, DateRangeFilter)
|
|
22
|
+
*/
|
|
23
|
+
filterType: SearchCriteriaFilterType;
|
|
24
|
+
/**
|
|
25
|
+
* The corresponding Property field path, if any
|
|
26
|
+
*/
|
|
27
|
+
propertyPath: string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Property field metadata, if available
|
|
30
|
+
*/
|
|
31
|
+
propertyMetadata: PropertyFieldMetadata | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Human-readable description of the field and how to filter it
|
|
34
|
+
*/
|
|
35
|
+
description: string;
|
|
36
|
+
/**
|
|
37
|
+
* Example filter values or usage patterns
|
|
38
|
+
*/
|
|
39
|
+
examples: string[];
|
|
40
|
+
/**
|
|
41
|
+
* Additional context about how this filter should be used
|
|
42
|
+
*/
|
|
43
|
+
filterGuidance: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get filter context for a SearchCriteria field path
|
|
47
|
+
* @param searchCriteriaPath The SearchCriteria field path (e.g., "address.city", "building.yearBuilt", "quickList")
|
|
48
|
+
* @returns Filter context including filter type, examples, and guidance
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const context = getSearchCriteriaFilterContext("building.bedroomCount");
|
|
52
|
+
* console.log(context.filterType); // "NumericRangeFilter"
|
|
53
|
+
* console.log(context.examples); // ["{ min: 3, max: 3 } // exactly 3", ...]
|
|
54
|
+
* console.log(context.filterGuidance); // "Use NumericRangeFilter with: min, max..."
|
|
55
|
+
*
|
|
56
|
+
* const quickListContext = getSearchCriteriaFilterContext("quickList");
|
|
57
|
+
* console.log(quickListContext.filterType); // "QuickListValue"
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function getSearchCriteriaFilterContext(searchCriteriaPath: string): SearchCriteriaFilterContext;
|
|
61
|
+
/**
|
|
62
|
+
* Get a formatted string with complete filter context for a SearchCriteria field
|
|
63
|
+
* This is useful for generating documentation or AI prompts
|
|
64
|
+
* @param searchCriteriaPath The SearchCriteria field path
|
|
65
|
+
* @returns A formatted string with all filter context information
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const contextString = getSearchCriteriaFilterContextString("building.bedroomCount");
|
|
69
|
+
* // Returns a formatted string with filter type, examples, and guidance
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function getSearchCriteriaFilterContextString(searchCriteriaPath: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Get filter context for all fields in a SearchCriteria domain/group
|
|
75
|
+
* @param domainName The domain name (e.g., "address", "building", "assessment", "quickList")
|
|
76
|
+
* @returns Array of filter contexts for all fields in that domain
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const addressFields = getDomainFilterContexts("address");
|
|
80
|
+
* // Returns filter contexts for all address.* fields
|
|
81
|
+
*
|
|
82
|
+
* const quickListContexts = getDomainFilterContexts("quickList");
|
|
83
|
+
* // Returns contexts for quickList, quickLists, and orQuickLists
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare function getDomainFilterContexts(domainName: string): SearchCriteriaFilterContext[];
|
|
87
|
+
/**
|
|
88
|
+
* Get a formatted documentation string for all fields in a domain
|
|
89
|
+
* @param domainName The domain name
|
|
90
|
+
* @returns A formatted string with filter context for all fields in the domain
|
|
91
|
+
*/
|
|
92
|
+
export declare function getDomainFilterContextDocumentation(domainName: string): string;
|
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SearchCriteria Filter Context
|
|
4
|
+
*
|
|
5
|
+
* Provides context about how filters should work for each SearchCriteria field.
|
|
6
|
+
* This includes filter type information and usage guidance.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getSearchCriteriaFilterContext = getSearchCriteriaFilterContext;
|
|
10
|
+
exports.getSearchCriteriaFilterContextString = getSearchCriteriaFilterContextString;
|
|
11
|
+
exports.getDomainFilterContexts = getDomainFilterContexts;
|
|
12
|
+
exports.getDomainFilterContextDocumentation = getDomainFilterContextDocumentation;
|
|
13
|
+
const metadata_1 = require("./metadata");
|
|
14
|
+
/**
|
|
15
|
+
* Mapping of SearchCriteria field paths to their filter types
|
|
16
|
+
* This is derived from the SearchCriteria type definitions
|
|
17
|
+
*/
|
|
18
|
+
const SEARCH_CRITERIA_FILTER_TYPE_MAP = {
|
|
19
|
+
// Address fields - StringFilter
|
|
20
|
+
"address.street": "StringFilter",
|
|
21
|
+
"address.city": "StringFilter",
|
|
22
|
+
"address.locality": "StringFilter",
|
|
23
|
+
"address.county": "StringFilter",
|
|
24
|
+
"address.countyFipsCode": "StringFilter",
|
|
25
|
+
"address.state": "StringFilter",
|
|
26
|
+
"address.zip": "StringFilter",
|
|
27
|
+
"address.unitType": "StringFilter",
|
|
28
|
+
"address.formattedStreet": "StringFilter",
|
|
29
|
+
"address.streetNoUnit": "StringFilter",
|
|
30
|
+
"address.initialGeoStatus": "StringFilter",
|
|
31
|
+
"address.geoStatus": "StringFilter",
|
|
32
|
+
"address.cityState": "StringFilter",
|
|
33
|
+
"address.countyState": "StringFilter",
|
|
34
|
+
// Assessment fields - NumericRangeFilter
|
|
35
|
+
"assessment.assessmentYear": "NumericRangeFilter",
|
|
36
|
+
"assessment.totalAssessedValue": "NumericRangeFilter",
|
|
37
|
+
"assessment.assessedImprovementValue": "NumericRangeFilter",
|
|
38
|
+
"assessment.assessedLandValue": "NumericRangeFilter",
|
|
39
|
+
"assessment.marketValueYear": "NumericRangeFilter",
|
|
40
|
+
"assessment.landMarketValue": "NumericRangeFilter",
|
|
41
|
+
"assessment.improvementMarketValue": "NumericRangeFilter",
|
|
42
|
+
"assessment.totalMarketValue": "NumericRangeFilter",
|
|
43
|
+
// Building fields - mix of StringFilter and NumericRangeFilter
|
|
44
|
+
"building.airConditioningSource": "StringFilter",
|
|
45
|
+
"building.basementType": "StringFilter",
|
|
46
|
+
"building.totalBuildingAreaSquareFeet": "NumericRangeFilter",
|
|
47
|
+
"building.buildingClass": "StringFilter",
|
|
48
|
+
"building.buildingCondition": "StringFilter",
|
|
49
|
+
"building.buildingQuality": "StringFilter",
|
|
50
|
+
"building.buildingType": "StringFilter",
|
|
51
|
+
"building.driveway": "StringFilter",
|
|
52
|
+
"building.exteriorWalls": "StringFilter",
|
|
53
|
+
"building.floorCover": "StringFilter",
|
|
54
|
+
"building.garageParkingSpaceCount": "NumericRangeFilter",
|
|
55
|
+
"building.garage": "StringFilter",
|
|
56
|
+
"building.heatSource": "StringFilter",
|
|
57
|
+
"building.heatingFuelType": "StringFilter",
|
|
58
|
+
"building.interiorWalls": "StringFilter",
|
|
59
|
+
"building.buildingCount": "NumericRangeFilter",
|
|
60
|
+
"building.bathroomCount": "NumericRangeFilter",
|
|
61
|
+
"building.calculatedBathroomCount": "NumericRangeFilter",
|
|
62
|
+
"building.bedroomCount": "NumericRangeFilter",
|
|
63
|
+
"building.patio": "StringFilter",
|
|
64
|
+
"building.storyCount": "NumericRangeFilter",
|
|
65
|
+
"building.features": "StringFilter",
|
|
66
|
+
"building.residentialUnitCount": "NumericRangeFilter",
|
|
67
|
+
"building.pool": "StringFilter",
|
|
68
|
+
"building.porch": "StringFilter",
|
|
69
|
+
"building.roofCover": "StringFilter",
|
|
70
|
+
"building.roofType": "StringFilter",
|
|
71
|
+
"building.sewer": "StringFilter",
|
|
72
|
+
"building.style": "StringFilter",
|
|
73
|
+
"building.roomCount": "NumericRangeFilter",
|
|
74
|
+
"building.unitCount": "NumericRangeFilter",
|
|
75
|
+
"building.constructionType": "StringFilter",
|
|
76
|
+
"building.waterService": "StringFilter",
|
|
77
|
+
"building.yearBuilt": "NumericRangeFilter",
|
|
78
|
+
// Demographics - mix
|
|
79
|
+
"demographics.age": "NumericRangeFilter",
|
|
80
|
+
"demographics.householdSize": "NumericRangeFilter",
|
|
81
|
+
"demographics.income": "NumericRangeFilter",
|
|
82
|
+
"demographics.netWorth": "NumericRangeFilter",
|
|
83
|
+
"demographics.discretionaryIncome": "NumericRangeFilter",
|
|
84
|
+
"demographics.homeownerRenter": "StringFilter",
|
|
85
|
+
"demographics.businessOwner": "StringFilter",
|
|
86
|
+
"demographics.gender": "StringFilter",
|
|
87
|
+
"demographics.investments": "StringFilter",
|
|
88
|
+
"demographics.demographics": "StringFilter",
|
|
89
|
+
"demographics.religiousAffiliation": "StringFilter",
|
|
90
|
+
// Foreclosure - mix
|
|
91
|
+
"foreclosure.status": "StringFilter",
|
|
92
|
+
"foreclosure.recordingDate": "DateRangeFilter",
|
|
93
|
+
"foreclosure.auctionDate": "DateRangeFilter",
|
|
94
|
+
"foreclosure.releaseDate": "DateRangeFilter",
|
|
95
|
+
"foreclosure.auctionMinimumBidAmount": "NumericRangeFilter",
|
|
96
|
+
"foreclosure.pastDueAmount": "NumericRangeFilter",
|
|
97
|
+
// General - StringFilter
|
|
98
|
+
"general.propertyTypeCategory": "StringFilter",
|
|
99
|
+
"general.propertyTypeDetail": "StringFilter",
|
|
100
|
+
// IDs - StringFilter
|
|
101
|
+
"ids.addressHash": "StringFilter",
|
|
102
|
+
"ids.mailingAddressHash": "StringFilter",
|
|
103
|
+
"ids.fipsCode": "StringFilter",
|
|
104
|
+
"ids.apn": "StringFilter",
|
|
105
|
+
"ids.taxId": "StringFilter",
|
|
106
|
+
// Intel - mix
|
|
107
|
+
"intel.lastSoldDate": "DateRangeFilter",
|
|
108
|
+
"intel.lastSoldPrice": "NumericRangeFilter",
|
|
109
|
+
"intel.salePropensity": "NumericRangeFilter",
|
|
110
|
+
// Involuntary Lien - mix
|
|
111
|
+
"involuntaryLien.lienType": "StringFilter",
|
|
112
|
+
"involuntaryLien.lienTypeCode": "StringFilter",
|
|
113
|
+
"involuntaryLien.documentType": "StringFilter",
|
|
114
|
+
"involuntaryLien.documentTypeCode": "StringFilter",
|
|
115
|
+
"involuntaryLien.recordingDate": "DateRangeFilter",
|
|
116
|
+
"involuntaryLien.filingDate": "DateRangeFilter",
|
|
117
|
+
"involuntaryLien.lienAmount": "NumericRangeFilter",
|
|
118
|
+
// Legal - StringFilter
|
|
119
|
+
"legal.subdivisionName": "StringFilter",
|
|
120
|
+
// Listing - mix
|
|
121
|
+
"listing.description": "StringFilter",
|
|
122
|
+
"listing.price": "NumericRangeFilter",
|
|
123
|
+
"listing.daysOnMarket": "NumericRangeFilter",
|
|
124
|
+
"listing.status": "StringFilter",
|
|
125
|
+
"listing.statusCategory": "StringFilter",
|
|
126
|
+
"listing.failedListingDate": "DateRangeFilter",
|
|
127
|
+
"listing.soldDate": "DateRangeFilter",
|
|
128
|
+
// Lot - mix
|
|
129
|
+
"lot.lotSizeAcres": "NumericRangeFilter",
|
|
130
|
+
"lot.lotDepthFeet": "NumericRangeFilter",
|
|
131
|
+
"lot.lotFrontageFeet": "NumericRangeFilter",
|
|
132
|
+
"lot.lotSizeSquareFeet": "NumericRangeFilter",
|
|
133
|
+
"lot.zoningCode": "StringFilter",
|
|
134
|
+
// Open Lien - mix
|
|
135
|
+
"openLien.allLoanTypes": "StringFilter",
|
|
136
|
+
"openLien.juniorLoanTypes": "StringFilter",
|
|
137
|
+
"openLien.totalOpenLienCount": "NumericRangeFilter",
|
|
138
|
+
"openLien.totalOpenLienBalance": "NumericRangeFilter",
|
|
139
|
+
"openLien.firstLoanLtv": "NumericRangeFilter",
|
|
140
|
+
"openLien.firstLoanInterestRate": "NumericRangeFilter",
|
|
141
|
+
"openLien.secondLoanInterestRate": "NumericRangeFilter",
|
|
142
|
+
"openLien.thirdLoanInterestRate": "NumericRangeFilter",
|
|
143
|
+
"openLien.fourthLoanInterestRate": "NumericRangeFilter",
|
|
144
|
+
"openLien.firstLoanType": "StringFilter",
|
|
145
|
+
"openLien.secondLoanType": "StringFilter",
|
|
146
|
+
"openLien.thirdLoanType": "StringFilter",
|
|
147
|
+
"openLien.fourthLoanType": "StringFilter",
|
|
148
|
+
"openLien.firstLoanRecordingDate": "DateRangeFilter",
|
|
149
|
+
"openLien.lastLoanRecordingDate": "DateRangeFilter",
|
|
150
|
+
// Owner - mix
|
|
151
|
+
"owner.firstName": "StringFilter",
|
|
152
|
+
"owner.lastName": "StringFilter",
|
|
153
|
+
"owner.mailingStreet": "StringFilter",
|
|
154
|
+
"owner.mailingCity": "StringFilter",
|
|
155
|
+
"owner.mailingState": "StringFilter",
|
|
156
|
+
"owner.mailingZip": "StringFilter",
|
|
157
|
+
"owner.ownerStatusType": "StringFilter",
|
|
158
|
+
"owner.lengthOfResidenceMonths": "NumericRangeFilter",
|
|
159
|
+
"owner.lengthOfResidenceYears": "NumericRangeFilter",
|
|
160
|
+
"owner.ownershipStartDate": "DateRangeFilter",
|
|
161
|
+
"owner.mailingAddressHash": "StringFilter",
|
|
162
|
+
// Permit - mix
|
|
163
|
+
"permit.permitCount": "NumericRangeFilter",
|
|
164
|
+
"permit.latestDate": "DateRangeFilter",
|
|
165
|
+
"permit.earliestDate": "DateRangeFilter",
|
|
166
|
+
"permit.totalJobValue": "NumericRangeFilter",
|
|
167
|
+
"permit.allTags": "StringFilter",
|
|
168
|
+
// Property Owner Profile - NumericRangeFilter
|
|
169
|
+
"propertyOwnerProfile.propertiesCount": "NumericRangeFilter",
|
|
170
|
+
"propertyOwnerProfile.propertiesTotalEquity": "NumericRangeFilter",
|
|
171
|
+
"propertyOwnerProfile.propertiesTotalEstimatedValue": "NumericRangeFilter",
|
|
172
|
+
"propertyOwnerProfile.mortgagesTotalBalance": "NumericRangeFilter",
|
|
173
|
+
"propertyOwnerProfile.mortgagesCount": "NumericRangeFilter",
|
|
174
|
+
"propertyOwnerProfile.mortgagesAverageBalance": "NumericRangeFilter",
|
|
175
|
+
// Sale - mix
|
|
176
|
+
"sale.lastSalePrice": "NumericRangeFilter",
|
|
177
|
+
"sale.lastSaleDate": "DateRangeFilter",
|
|
178
|
+
"sale.lastSaleDocumentType": "StringFilter",
|
|
179
|
+
"sale.lastSalePricePerSquareFoot": "NumericRangeFilter",
|
|
180
|
+
"sale.flipLength": "NumericRangeFilter",
|
|
181
|
+
"sale.flipLengthCategory": "NumericRangeFilter",
|
|
182
|
+
"sale.flipProfit": "NumericRangeFilter",
|
|
183
|
+
// Tax - NumericRangeFilter
|
|
184
|
+
"tax.taxDelinquentYear": "NumericRangeFilter",
|
|
185
|
+
// Valuation - NumericRangeFilter
|
|
186
|
+
"valuation.estimatedValue": "NumericRangeFilter",
|
|
187
|
+
"valuation.ltv": "NumericRangeFilter",
|
|
188
|
+
"valuation.equityPercent": "NumericRangeFilter",
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Get filter guidance text based on filter type
|
|
192
|
+
*/
|
|
193
|
+
function getFilterGuidance(filterType) {
|
|
194
|
+
switch (filterType) {
|
|
195
|
+
case "StringFilter":
|
|
196
|
+
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"] }`;
|
|
197
|
+
case "NumericRangeFilter":
|
|
198
|
+
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"`;
|
|
199
|
+
case "DateRangeFilter":
|
|
200
|
+
return `Use DateRangeFilter with: minDate (earliest date), maxDate (latest date), or both. Dates should be in ISO 8601 format (YYYY-MM-DD). Example: { minDate: "2020-01-01" } for "after 2020"`;
|
|
201
|
+
case "QuickListValue":
|
|
202
|
+
return `Use a QuickListValue string. Can be prefixed with "not-" to exclude. Available as: quickList (single value), quickLists (array), orQuickLists (array for OR logic). Example: "owner-occupied" or "not-vacant"`;
|
|
203
|
+
default:
|
|
204
|
+
return "Unknown filter type";
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get example values based on field and filter type
|
|
209
|
+
*/
|
|
210
|
+
function getExamples(fieldPath, filterType, _propertyMetadata) {
|
|
211
|
+
const examples = [];
|
|
212
|
+
switch (filterType) {
|
|
213
|
+
case "StringFilter":
|
|
214
|
+
if (fieldPath.includes("city")) {
|
|
215
|
+
examples.push('{ equals: "Phoenix" }');
|
|
216
|
+
examples.push('{ inList: ["Phoenix", "Tucson", "Mesa"] }');
|
|
217
|
+
}
|
|
218
|
+
else if (fieldPath.includes("state")) {
|
|
219
|
+
examples.push('{ equals: "AZ" }');
|
|
220
|
+
}
|
|
221
|
+
else if (fieldPath.includes("status")) {
|
|
222
|
+
examples.push('{ equals: "active" }');
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
examples.push('{ equals: "value" }');
|
|
226
|
+
examples.push('{ contains: "substring" }');
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
case "NumericRangeFilter":
|
|
230
|
+
if (fieldPath.includes("bedroom") || fieldPath.includes("bathroom")) {
|
|
231
|
+
examples.push("{ min: 3, max: 3 } // exactly 3");
|
|
232
|
+
examples.push("{ min: 3 } // at least 3");
|
|
233
|
+
examples.push("{ min: 3, max: 5 } // between 3 and 5");
|
|
234
|
+
}
|
|
235
|
+
else if (fieldPath.includes("yearBuilt")) {
|
|
236
|
+
examples.push("{ min: 2010 } // built after 2010");
|
|
237
|
+
examples.push("{ max: 2000 } // built before 2000");
|
|
238
|
+
}
|
|
239
|
+
else if (fieldPath.includes("Value") || fieldPath.includes("Price")) {
|
|
240
|
+
examples.push("{ max: 400000 } // under $400,000");
|
|
241
|
+
examples.push("{ min: 500000 } // over $500,000");
|
|
242
|
+
examples.push("{ min: 300000, max: 500000 } // between $300k and $500k");
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
examples.push("{ min: 0 }");
|
|
246
|
+
examples.push("{ max: 100 }");
|
|
247
|
+
examples.push("{ min: 10, max: 50 }");
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
case "DateRangeFilter":
|
|
251
|
+
examples.push('{ minDate: "2020-01-01" } // after January 1, 2020');
|
|
252
|
+
examples.push('{ maxDate: "2023-12-31" } // before December 31, 2023');
|
|
253
|
+
examples.push('{ minDate: "2020-01-01", maxDate: "2023-12-31" } // between dates');
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
return examples;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Valid QuickList values with descriptions (from core/types.ts and metadata)
|
|
260
|
+
*/
|
|
261
|
+
const QUICK_LIST_VALUES_WITH_DESCRIPTIONS = [
|
|
262
|
+
{
|
|
263
|
+
value: "absentee-owner",
|
|
264
|
+
description: "Property owner does not reside at the property",
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
value: "active-auction",
|
|
268
|
+
description: "Property has an auction date greater than the current date",
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
value: "active-listing",
|
|
272
|
+
description: "Property has an active listing",
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
value: "canceled-listing",
|
|
276
|
+
description: "Property has a canceled listing",
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
value: "cash-buyer",
|
|
280
|
+
description: "Property was purchased with cash",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
value: "corporate-owned",
|
|
284
|
+
description: "Property is owned by a corporate entity (i.e., not an individual)",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
value: "expired-listing",
|
|
288
|
+
description: "Property has an expired listing",
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
value: "failed-listing",
|
|
292
|
+
description: "The listing was either cancelled or expired and not sold",
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
value: "fix-and-flip",
|
|
296
|
+
description: "Property has been bought and sold in the last 12 months",
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
value: "free-and-clear",
|
|
300
|
+
description: "Property is free of any mortgage",
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
value: "for-sale-by-owner",
|
|
304
|
+
description: "Property is listed for sale by the owner",
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
value: "has-hoa",
|
|
308
|
+
description: "Property is part of a homeowner association",
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
value: "has-hoa-fees",
|
|
312
|
+
description: "Property has homeowner association fees",
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
value: "high-equity",
|
|
316
|
+
description: "Property owner has more than 20% equity in the property",
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
value: "inherited",
|
|
320
|
+
description: "Property owner has inherited the property",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
value: "involuntary-lien",
|
|
324
|
+
description: "Property has an involuntary lien recorded against it, indicating a legal claim due to unpaid debts or obligations",
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
value: "in-state-absentee-owner",
|
|
328
|
+
description: "Property owner does not reside at the property, and the property address and owner mailing address are in the same state",
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
value: "listed-below-market-price",
|
|
332
|
+
description: "Property is listed below market price",
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
value: "low-equity",
|
|
336
|
+
description: "Property owner has less than 20% equity in the property",
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
value: "mailing-address-vacant",
|
|
340
|
+
description: "Property mailing address is vacant",
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
value: "notice-of-default",
|
|
344
|
+
description: "Property has a default of a loan by the borrower of the mortgage",
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
value: "notice-of-lis-pendens",
|
|
348
|
+
description: "Property has a pending lawsuit",
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
value: "notice-of-sale",
|
|
352
|
+
description: "Property has received a notice of sale",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
value: "on-market",
|
|
356
|
+
description: "Property is available for sale",
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
value: "out-of-state-absentee-owner",
|
|
360
|
+
description: "Property owner does not reside at the property, and the property address and owner mailing address are in different states",
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
value: "out-of-state-owner",
|
|
364
|
+
description: "Property owner resides out of state",
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
value: "owner-occupied",
|
|
368
|
+
description: "Property is occupied by the owner",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
value: "pending-listing",
|
|
372
|
+
description: "Property listing is pending (not yet active)",
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
value: "preforeclosure",
|
|
376
|
+
description: "Property is in pre-foreclosure",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
value: "recently-sold",
|
|
380
|
+
description: "Property was sold in the last 12 months",
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
value: "same-property-and-mailing-address",
|
|
384
|
+
description: "Property address is the same as the mailing address",
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
value: "tax-default",
|
|
388
|
+
description: "Property is in tax default and the first tax default was at least three years ago",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
value: "tired-landlord",
|
|
392
|
+
description: "Property is a single family residence owned for at least 10 years",
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
value: "unknown-equity",
|
|
396
|
+
description: "Property equity is unknown",
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
value: "vacant",
|
|
400
|
+
description: "USPS has flagged the property as vacant",
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
value: "vacant-lot",
|
|
404
|
+
description: "Property is likely to be a vacant lot",
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
value: "senior-owner",
|
|
408
|
+
description: "An individual property owner who resides in the property for 25 or more years, has senior tax exemption, or currently holds an open reverse mortgage",
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
value: "trust-owned",
|
|
412
|
+
description: "Property is owned by a trust",
|
|
413
|
+
},
|
|
414
|
+
];
|
|
415
|
+
/**
|
|
416
|
+
* Get filter context for a SearchCriteria field path
|
|
417
|
+
* @param searchCriteriaPath The SearchCriteria field path (e.g., "address.city", "building.yearBuilt", "quickList")
|
|
418
|
+
* @returns Filter context including filter type, examples, and guidance
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* const context = getSearchCriteriaFilterContext("building.bedroomCount");
|
|
422
|
+
* console.log(context.filterType); // "NumericRangeFilter"
|
|
423
|
+
* console.log(context.examples); // ["{ min: 3, max: 3 } // exactly 3", ...]
|
|
424
|
+
* console.log(context.filterGuidance); // "Use NumericRangeFilter with: min, max..."
|
|
425
|
+
*
|
|
426
|
+
* const quickListContext = getSearchCriteriaFilterContext("quickList");
|
|
427
|
+
* console.log(quickListContext.filterType); // "QuickListValue"
|
|
428
|
+
* ```
|
|
429
|
+
*/
|
|
430
|
+
function getSearchCriteriaFilterContext(searchCriteriaPath) {
|
|
431
|
+
// Handle special quickList fields
|
|
432
|
+
if (searchCriteriaPath === "quickList" ||
|
|
433
|
+
searchCriteriaPath === "quickLists" ||
|
|
434
|
+
searchCriteriaPath === "orQuickLists") {
|
|
435
|
+
return {
|
|
436
|
+
searchCriteriaPath,
|
|
437
|
+
filterType: "QuickListValue",
|
|
438
|
+
propertyPath: undefined,
|
|
439
|
+
propertyMetadata: undefined,
|
|
440
|
+
description: `Pre-defined quick filter${searchCriteriaPath === "quickList" ? "" : "s"} for common property characteristics. Values can be prefixed with "not-" to exclude (e.g., "not-vacant" excludes vacant properties). Use ${searchCriteriaPath === "orQuickLists" ? "orQuickLists array for OR logic" : searchCriteriaPath === "quickLists" ? "quickLists array for multiple values" : "quickList for a single value"}.`,
|
|
441
|
+
examples: [
|
|
442
|
+
...QUICK_LIST_VALUES_WITH_DESCRIPTIONS.slice(0, 5).map((item) => `"${item.value}" // ${item.description}`),
|
|
443
|
+
'"not-vacant" // Exclude vacant properties',
|
|
444
|
+
'"not-owner-occupied" // Exclude owner-occupied properties',
|
|
445
|
+
`\nAll available QuickList values (${QUICK_LIST_VALUES_WITH_DESCRIPTIONS.length} total):\n${QUICK_LIST_VALUES_WITH_DESCRIPTIONS.map((item) => ` - "${item.value}": ${item.description}`).join("\n")}`,
|
|
446
|
+
],
|
|
447
|
+
filterGuidance: getFilterGuidance("QuickListValue"),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
const filterType = SEARCH_CRITERIA_FILTER_TYPE_MAP[searchCriteriaPath] || "StringFilter";
|
|
451
|
+
// Try to get Property field metadata
|
|
452
|
+
const propertyMetadata = (0, metadata_1.getFieldMetadata)(searchCriteriaPath);
|
|
453
|
+
const propertyPath = propertyMetadata ? searchCriteriaPath : undefined;
|
|
454
|
+
// Build description
|
|
455
|
+
let description;
|
|
456
|
+
if (propertyMetadata) {
|
|
457
|
+
description = `Filter on ${propertyMetadata.description}`;
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const parts = searchCriteriaPath.split(".");
|
|
461
|
+
const fieldName = parts[parts.length - 1] || searchCriteriaPath;
|
|
462
|
+
description = `Filter on ${fieldName} field`;
|
|
463
|
+
}
|
|
464
|
+
// Get examples and guidance
|
|
465
|
+
const examples = getExamples(searchCriteriaPath, filterType, propertyMetadata);
|
|
466
|
+
const filterGuidance = getFilterGuidance(filterType);
|
|
467
|
+
return {
|
|
468
|
+
searchCriteriaPath,
|
|
469
|
+
filterType,
|
|
470
|
+
propertyPath,
|
|
471
|
+
propertyMetadata: propertyMetadata || undefined,
|
|
472
|
+
description,
|
|
473
|
+
examples,
|
|
474
|
+
filterGuidance,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Get a formatted string with complete filter context for a SearchCriteria field
|
|
479
|
+
* This is useful for generating documentation or AI prompts
|
|
480
|
+
* @param searchCriteriaPath The SearchCriteria field path
|
|
481
|
+
* @returns A formatted string with all filter context information
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* const contextString = getSearchCriteriaFilterContextString("building.bedroomCount");
|
|
485
|
+
* // Returns a formatted string with filter type, examples, and guidance
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
function getSearchCriteriaFilterContextString(searchCriteriaPath) {
|
|
489
|
+
const context = getSearchCriteriaFilterContext(searchCriteriaPath);
|
|
490
|
+
const parts = [];
|
|
491
|
+
parts.push(`Field: ${context.searchCriteriaPath}`);
|
|
492
|
+
parts.push(`Filter Type: ${context.filterType}`);
|
|
493
|
+
parts.push(`Description: ${context.description}`);
|
|
494
|
+
if (context.propertyMetadata) {
|
|
495
|
+
parts.push(`Property Field: ${context.propertyPath} (${context.propertyMetadata.dataType})`);
|
|
496
|
+
}
|
|
497
|
+
parts.push(`\nFilter Guidance:\n${context.filterGuidance}`);
|
|
498
|
+
if (context.examples.length > 0) {
|
|
499
|
+
parts.push(`\nExamples:`);
|
|
500
|
+
context.examples.forEach((example) => {
|
|
501
|
+
parts.push(` ${example}`);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
return parts.join("\n");
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Get filter context for all fields in a SearchCriteria domain/group
|
|
508
|
+
* @param domainName The domain name (e.g., "address", "building", "assessment", "quickList")
|
|
509
|
+
* @returns Array of filter contexts for all fields in that domain
|
|
510
|
+
* @example
|
|
511
|
+
* ```typescript
|
|
512
|
+
* const addressFields = getDomainFilterContexts("address");
|
|
513
|
+
* // Returns filter contexts for all address.* fields
|
|
514
|
+
*
|
|
515
|
+
* const quickListContexts = getDomainFilterContexts("quickList");
|
|
516
|
+
* // Returns contexts for quickList, quickLists, and orQuickLists
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
function getDomainFilterContexts(domainName) {
|
|
520
|
+
const contexts = [];
|
|
521
|
+
// Handle special quickList domain
|
|
522
|
+
if (domainName === "quickList") {
|
|
523
|
+
contexts.push(getSearchCriteriaFilterContext("quickList"));
|
|
524
|
+
contexts.push(getSearchCriteriaFilterContext("quickLists"));
|
|
525
|
+
contexts.push(getSearchCriteriaFilterContext("orQuickLists"));
|
|
526
|
+
return contexts;
|
|
527
|
+
}
|
|
528
|
+
// Find all fields that start with the domain name
|
|
529
|
+
for (const fieldPath in SEARCH_CRITERIA_FILTER_TYPE_MAP) {
|
|
530
|
+
if (fieldPath.startsWith(`${domainName}.`)) {
|
|
531
|
+
contexts.push(getSearchCriteriaFilterContext(fieldPath));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return contexts.sort((a, b) => a.searchCriteriaPath.localeCompare(b.searchCriteriaPath));
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Get a formatted documentation string for all fields in a domain
|
|
538
|
+
* @param domainName The domain name
|
|
539
|
+
* @returns A formatted string with filter context for all fields in the domain
|
|
540
|
+
*/
|
|
541
|
+
function getDomainFilterContextDocumentation(domainName) {
|
|
542
|
+
const contexts = getDomainFilterContexts(domainName);
|
|
543
|
+
const parts = [];
|
|
544
|
+
parts.push(`\n${domainName.toUpperCase()} DOMAIN:`);
|
|
545
|
+
parts.push("=".repeat(50));
|
|
546
|
+
for (const context of contexts) {
|
|
547
|
+
parts.push(`\n${getSearchCriteriaFilterContextString(context.searchCriteriaPath)}`);
|
|
548
|
+
}
|
|
549
|
+
return parts.join("\n");
|
|
550
|
+
}
|
package/package.json
CHANGED