@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 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 Kinesis delivery configuration
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 kinesis) is required");
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
  }
@@ -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
- * Delivery configuration for webhook or Kinesis
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@land-catalyst/batch-data-sdk",
3
- "version": "1.1.19",
3
+ "version": "1.2.1",
4
4
  "description": "TypeScript SDK for BatchData.io Property API - Types, Builders, and Utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",