@land-catalyst/batch-data-sdk 1.3.1 → 1.3.3

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.
@@ -1,7 +1,7 @@
1
1
  import { AxiosInstance, AxiosResponse, AxiosError, InternalAxiosRequestConfig } from "axios";
2
2
  import { IBatchDataClient } from "./client.interface";
3
3
  import { ILogger } from "../core/logger.interface";
4
- import { PropertySubscriptionRequest, PropertySubscriptionResponse, PropertyCountRequest, PropertySearchRequest, PropertySearchResponse, PropertyLookupOptions, AddressVerifyRequest, AddressVerifyResponse, AddressAutocompleteRequest, AddressAutocompleteResponse, AddressGeocodeRequest, AddressGeocodeResponse, AddressReverseGeocodeRequest, AddressReverseGeocodeResponse, PropertyLookupRequest, PropertyLookupResponse, PropertyLookupAsyncRequest, PropertyLookupAsyncResponse, PropertySearchAsyncRequest, PropertySearchAsyncResponse, PropertySkipTraceRequest, PropertySkipTraceResponse, PropertySkipTraceAsyncRequest, PropertySkipTraceAsyncResponse, PhoneVerificationRequest, PhoneVerificationResponse, PhoneVerificationAsyncRequest, PhoneVerificationAsyncResponse, PhoneDNCRequest, PhoneDNCResponse, PhoneDNCAsyncRequest, PhoneDNCAsyncResponse, PhoneTCPARequest, PhoneTCPAResponse, PhoneTCPAAsyncRequest, PhoneTCPAAsyncResponse, GetPropertySubscriptionsResponse, GetPropertySubscriptionDetailResponse, DeletePropertySubscriptionResponse, PropertyPermitRequest, PropertyPermitResponse, PropertySkipTraceV3Request, PropertySkipTraceV3Response, PropertySkipTraceV3AsyncRequest, PropertySkipTraceV3AsyncResponse, EventHubConfig, DeliveryConfig } from "../core/types";
4
+ import { PropertySubscriptionRequest, PropertySubscriptionResponse, PropertyCountRequest, PropertySearchRequest, PropertySearchResponse, PropertyLookupOptions, AddressVerifyRequest, AddressVerifyResponse, AddressAutocompleteRequest, AddressAutocompleteResponse, AddressGeocodeRequest, AddressGeocodeResponse, AddressReverseGeocodeRequest, AddressReverseGeocodeResponse, PropertyLookupRequest, PropertyLookupResponse, PropertyLookupAsyncRequest, PropertyLookupAsyncResponse, PropertySearchAsyncRequest, PropertySearchAsyncResponse, PropertySkipTraceRequest, PropertySkipTraceResponse, PropertySkipTraceAsyncRequest, PropertySkipTraceAsyncResponse, PhoneVerificationRequest, PhoneVerificationResponse, PhoneVerificationAsyncRequest, PhoneVerificationAsyncResponse, PhoneDNCRequest, PhoneDNCResponse, PhoneDNCAsyncRequest, PhoneDNCAsyncResponse, PhoneTCPARequest, PhoneTCPAResponse, PhoneTCPAAsyncRequest, PhoneTCPAAsyncResponse, GetPropertySubscriptionsResponse, GetPropertySubscriptionDetailResponse, DeletePropertySubscriptionResponse, PropertyPermitRequest, PropertyPermitResponse, PropertySkipTraceV3Request, PropertySkipTraceV3Response, PropertySkipTraceV3AsyncRequest, PropertySkipTraceV3AsyncResponse, DeliveryConfig } from "../core/types";
5
5
  /**
6
6
  * Middleware function that executes before an HTTP request is sent.
7
7
  * Can modify the request config or perform side effects (logging, metrics, etc.).
@@ -102,16 +102,6 @@ export interface AsyncWebhookConfiguration {
102
102
  * Event Hub configuration for async API requests
103
103
  * Allows specifying Event Hub config per endpoint type, with fallback to defaults
104
104
  */
105
- export interface AsyncEventHubConfiguration {
106
- /**
107
- * Default Event Hub config used as fallback for all async endpoints
108
- */
109
- default?: EventHubConfig;
110
- /**
111
- * Event Hub config for property subscription requests
112
- */
113
- propertySubscription?: EventHubConfig;
114
- }
115
105
  /**
116
106
  * BatchData API Client Options
117
107
  */
@@ -143,15 +133,9 @@ export interface BatchDataClientOptions {
143
133
  * Allows specifying webhook URLs per endpoint type, with fallback to defaults
144
134
  */
145
135
  webhooks?: AsyncWebhookConfiguration;
146
- /**
147
- * Optional Event Hub configuration for async requests
148
- * Allows specifying Event Hub config per endpoint type, with fallback to defaults
149
- */
150
- eventHubs?: AsyncEventHubConfiguration;
151
136
  /**
152
137
  * Optional default delivery configuration for property subscriptions
153
138
  * If provided, this will be used when a subscription request doesn't specify deliveryConfig
154
- * Takes precedence over webhooks and eventHubs configurations
155
139
  *
156
140
  * @example
157
141
  * ```typescript
@@ -214,7 +198,6 @@ export declare class BatchDataClient implements IBatchDataClient {
214
198
  protected readonly axiosInstance: AxiosInstance;
215
199
  protected readonly logger: ILogger;
216
200
  private readonly webhooks?;
217
- private readonly eventHubs?;
218
201
  private readonly defaultDeliveryConfig?;
219
202
  private readonly defaultOptions?;
220
203
  private readonly maxTake?;
@@ -269,20 +252,12 @@ export declare class BatchDataClient implements IBatchDataClient {
269
252
  */
270
253
  private applyDefaultOptions;
271
254
  /**
272
- * Get delivery config from fallbacks (defaultDeliveryConfig first, then Event Hub preferred over webhooks).
273
- * Returns undefined if no fallback configs are available.
274
- *
275
- * @returns Delivery config from fallbacks, or undefined if none available
276
- */
277
- private createDeliveryConfigFromFallbacks;
278
- /**
279
- * Apply default delivery configuration (Event Hub or webhook) to a property subscription request.
280
- * Event Hub takes precedence over webhooks if both are configured.
281
- * If deliveryConfig is not set, it will be created based on available configs (Event Hub preferred).
255
+ * Apply default delivery configuration to a property subscription request.
256
+ * If deliveryConfig is not set in the request, use defaultDeliveryConfig if available.
282
257
  *
283
258
  * @param request The property subscription request to modify
284
259
  * @returns The request with delivery config applied (if needed)
285
- * @throws Error if deliveryConfig type is explicitly set but required config is missing
260
+ * @throws Error if deliveryConfig is not set and no default is available
286
261
  */
287
262
  private applyDeliveryConfigDefaults;
288
263
  /**
@@ -24,7 +24,6 @@ class BatchDataClient {
24
24
  this.v3BaseUrl = options.v3BaseUrl || "https://api.batchdata.com/api/v3";
25
25
  this.logger = options.logger || new logger_interface_1.ConsoleLogger();
26
26
  this.webhooks = options.webhooks;
27
- this.eventHubs = options.eventHubs;
28
27
  this.defaultDeliveryConfig = options.defaultDeliveryConfig;
29
28
  this.defaultOptions = options.defaultOptions;
30
29
  this.maxTake = options.maxTake;
@@ -290,108 +289,21 @@ class BatchDataClient {
290
289
  };
291
290
  }
292
291
  /**
293
- * Get delivery config from fallbacks (defaultDeliveryConfig first, then Event Hub preferred over webhooks).
294
- * Returns undefined if no fallback configs are available.
295
- *
296
- * @returns Delivery config from fallbacks, or undefined if none available
297
- */
298
- createDeliveryConfigFromFallbacks() {
299
- // Explicit defaultDeliveryConfig takes highest precedence
300
- if (this.defaultDeliveryConfig) {
301
- return this.defaultDeliveryConfig;
302
- }
303
- // Determine which Event Hub config to use (property subscription-specific first, then default)
304
- const eventHubConfig = this.eventHubs?.propertySubscription ?? this.eventHubs?.default;
305
- // Determine which webhook URLs to use
306
- let webhookUrl;
307
- let errorWebhookUrl;
308
- // First, try property subscription-specific webhook config
309
- if (this.webhooks?.propertySubscription) {
310
- webhookUrl = this.webhooks.propertySubscription.url;
311
- errorWebhookUrl = this.webhooks.propertySubscription.errorUrl;
312
- }
313
- // Fall back to default webhook config
314
- if (!webhookUrl && this.webhooks?.default) {
315
- webhookUrl = this.webhooks.default.url;
316
- errorWebhookUrl = errorWebhookUrl || this.webhooks.default.errorUrl;
317
- }
318
- // Event Hub takes precedence over webhooks
319
- if (eventHubConfig) {
320
- return {
321
- type: "event-hub",
322
- eventHub: eventHubConfig,
323
- };
324
- }
325
- if (webhookUrl || errorWebhookUrl) {
326
- return {
327
- type: "webhook",
328
- ...(webhookUrl && { url: webhookUrl }),
329
- ...(errorWebhookUrl && { errorUrl: errorWebhookUrl }),
330
- };
331
- }
332
- return undefined;
333
- }
334
- /**
335
- * Apply default delivery configuration (Event Hub or webhook) to a property subscription request.
336
- * Event Hub takes precedence over webhooks if both are configured.
337
- * If deliveryConfig is not set, it will be created based on available configs (Event Hub preferred).
292
+ * Apply default delivery configuration to a property subscription request.
293
+ * If deliveryConfig is not set in the request, use defaultDeliveryConfig if available.
338
294
  *
339
295
  * @param request The property subscription request to modify
340
296
  * @returns The request with delivery config applied (if needed)
341
- * @throws Error if deliveryConfig type is explicitly set but required config is missing
297
+ * @throws Error if deliveryConfig is not set and no default is available
342
298
  */
343
299
  applyDeliveryConfigDefaults(request) {
344
- const fallbackConfig = this.createDeliveryConfigFromFallbacks();
345
- // If deliveryConfig is not set, create it from fallbacks
300
+ // If deliveryConfig is not set, use defaultDeliveryConfig
346
301
  if (!request.deliveryConfig) {
347
- if (fallbackConfig) {
348
- return { ...request, deliveryConfig: fallbackConfig };
302
+ if (this.defaultDeliveryConfig) {
303
+ return { ...request, deliveryConfig: this.defaultDeliveryConfig };
349
304
  }
350
- // If no fallback config is available, throw an error
351
305
  throw new Error("Property subscription requires delivery configuration. " +
352
- "Please provide deliveryConfig in the request, or configure webhooks or eventHubs in BatchDataClient options.");
353
- }
354
- const deliveryConfig = request.deliveryConfig;
355
- // If type is event-hub, use request's eventHub or fallback's eventHub
356
- if (deliveryConfig.type === "event-hub") {
357
- const eventHubConfig = deliveryConfig.eventHub ??
358
- (fallbackConfig?.type === "event-hub"
359
- ? fallbackConfig.eventHub
360
- : undefined);
361
- if (!eventHubConfig) {
362
- throw new Error("Property subscription requires Event Hub configuration, but none is available. " +
363
- "Please provide eventHub in deliveryConfig, or configure eventHubs in BatchDataClient options.");
364
- }
365
- if (!deliveryConfig.eventHub) {
366
- return {
367
- ...request,
368
- deliveryConfig: {
369
- ...deliveryConfig,
370
- eventHub: eventHubConfig,
371
- },
372
- };
373
- }
374
- }
375
- // If type is webhook, use request's URLs or fallback's URLs
376
- if (deliveryConfig.type === "webhook") {
377
- const webhookUrl = deliveryConfig.url ??
378
- (fallbackConfig?.type === "webhook" ? fallbackConfig.url : undefined);
379
- const errorWebhookUrl = deliveryConfig.errorUrl ??
380
- (fallbackConfig?.type === "webhook"
381
- ? fallbackConfig.errorUrl
382
- : undefined);
383
- if ((!deliveryConfig.url && webhookUrl) ||
384
- (!deliveryConfig.errorUrl && errorWebhookUrl)) {
385
- return {
386
- ...request,
387
- deliveryConfig: {
388
- ...deliveryConfig,
389
- ...(webhookUrl && !deliveryConfig.url && { url: webhookUrl }),
390
- ...(errorWebhookUrl &&
391
- !deliveryConfig.errorUrl && { errorUrl: errorWebhookUrl }),
392
- },
393
- };
394
- }
306
+ "Please provide deliveryConfig in the request, or configure defaultDeliveryConfig in BatchDataClient options.");
395
307
  }
396
308
  return request;
397
309
  }
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ 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/display";
15
16
  export * from "./property-field/search-criteria-filter-context";
16
17
  export type { SearchCriteriaFilterContext, SearchCriteriaFilterType, } from "./property-field/search-criteria-filter-context";
17
18
  export * from "./property-field/search-criteria-ai-context";
package/dist/index.js CHANGED
@@ -28,5 +28,6 @@ __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/display"), exports);
31
32
  __exportStar(require("./property-field/search-criteria-filter-context"), exports);
32
33
  __exportStar(require("./property-field/search-criteria-ai-context"), exports);
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Property Field Display Utilities
3
+ *
4
+ * Utilities for formatting property field values for display in the UI.
5
+ * These helpers take raw property data and field metadata and produce
6
+ * human-readable formatted strings.
7
+ */
8
+ import type { Property } from "../core/types";
9
+ import { type PropertyFieldMetadata, type PropertyFieldType } from "./metadata";
10
+ /**
11
+ * Display field entry for rendering property data
12
+ */
13
+ export interface PropertyDisplayField {
14
+ /** The field path (e.g., "building.yearBuilt") */
15
+ fieldPath: string;
16
+ /** Human-readable label */
17
+ label: string;
18
+ /** Formatted display value */
19
+ value: string;
20
+ /** The field's data type */
21
+ dataType: PropertyFieldType;
22
+ }
23
+ /**
24
+ * Format a property field value based on its metadata
25
+ *
26
+ * @param value The raw field value
27
+ * @param metadata The field's metadata
28
+ * @returns Formatted display string, or empty string for null/undefined
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const meta = getPropertyFieldMetadata("valuation.estimatedValue");
33
+ * formatPropertyFieldValue(450000, meta); // "$450,000"
34
+ *
35
+ * const yearMeta = getPropertyFieldMetadata("building.yearBuilt");
36
+ * formatPropertyFieldValue(1985, yearMeta); // "1985"
37
+ * ```
38
+ */
39
+ export declare function formatPropertyFieldValue(value: unknown, metadata: PropertyFieldMetadata): string;
40
+ /**
41
+ * Get the display label for a property field path
42
+ *
43
+ * @param fieldPath The field path (e.g., "building.yearBuilt")
44
+ * @returns Human-readable label (e.g., "Year Built")
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * getPropertyFieldDisplayLabel("building.yearBuilt"); // "Year Built"
49
+ * getPropertyFieldDisplayLabel("owner.mailingAddress.city"); // "City"
50
+ * ```
51
+ */
52
+ export declare function getPropertyFieldDisplayLabel(fieldPath: string): string;
53
+ /**
54
+ * Get the display name for a property response group
55
+ *
56
+ * @param groupName The group name (e.g., "building", "propertyOwnerProfile")
57
+ * @returns Human-readable display name (e.g., "Building", "Owner Portfolio")
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * getPropertyGroupDisplayName("building"); // "Building"
62
+ * getPropertyGroupDisplayName("propertyOwnerProfile"); // "Owner Portfolio"
63
+ * ```
64
+ */
65
+ export declare function getPropertyGroupDisplayName(groupName: string): string;
66
+ /**
67
+ * Get all displayable fields for a property within a specific group
68
+ *
69
+ * Returns formatted field data for rendering in the UI. Fields with
70
+ * null/undefined values are excluded.
71
+ *
72
+ * @param property The property object (can be raw BatchData JSON)
73
+ * @param groupName The response group name (e.g., "building", "valuation")
74
+ * @returns Array of display field objects with label, formatted value, etc.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const buildingFields = getPropertyFieldsForDisplay(property, "building");
79
+ * // Returns:
80
+ * // [
81
+ * // { fieldPath: "building.yearBuilt", label: "Year Built", value: "1985", dataType: "number" },
82
+ * // { fieldPath: "building.bedrooms", label: "Bedrooms", value: "3", dataType: "number" },
83
+ * // ...
84
+ * // ]
85
+ * ```
86
+ */
87
+ export declare function getPropertyFieldsForDisplay(property: Property | Record<string, unknown>, groupName: string): PropertyDisplayField[];
88
+ /**
89
+ * Get all response groups that have displayable data in a property
90
+ *
91
+ * @param property The property object
92
+ * @returns Array of group names that have at least one displayable field
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const groups = getPropertyGroupsWithData(property);
97
+ * // Returns: ["address", "building", "valuation", "owner", ...]
98
+ * ```
99
+ */
100
+ export declare function getPropertyGroupsWithData(property: Property | Record<string, unknown>): string[];
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ /**
3
+ * Property Field Display Utilities
4
+ *
5
+ * Utilities for formatting property field values for display in the UI.
6
+ * These helpers take raw property data and field metadata and produce
7
+ * human-readable formatted strings.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.formatPropertyFieldValue = formatPropertyFieldValue;
11
+ exports.getPropertyFieldDisplayLabel = getPropertyFieldDisplayLabel;
12
+ exports.getPropertyGroupDisplayName = getPropertyGroupDisplayName;
13
+ exports.getPropertyFieldsForDisplay = getPropertyFieldsForDisplay;
14
+ exports.getPropertyGroupsWithData = getPropertyGroupsWithData;
15
+ const metadata_1 = require("./metadata");
16
+ const utils_1 = require("./utils");
17
+ /**
18
+ * Group display name mappings
19
+ */
20
+ const GROUP_DISPLAY_NAMES = {
21
+ address: "Address",
22
+ assessment: "Assessment",
23
+ building: "Building",
24
+ deedHistory: "Deed History",
25
+ demographics: "Demographics",
26
+ foreclosure: "Foreclosure",
27
+ general: "General",
28
+ ids: "Identifiers",
29
+ intel: "Intel",
30
+ involuntaryLien: "Involuntary Liens",
31
+ legal: "Legal",
32
+ listing: "Listing",
33
+ lot: "Lot",
34
+ meta: "Meta",
35
+ mortgageHistory: "Mortgage History",
36
+ openLien: "Open Liens",
37
+ owner: "Owner",
38
+ permit: "Permits",
39
+ propertyOwnerProfile: "Owner Portfolio",
40
+ quickLists: "Quick Lists",
41
+ sale: "Sale History",
42
+ tax: "Tax",
43
+ valuation: "Valuation",
44
+ };
45
+ /**
46
+ * Convert a field name to a display label
47
+ * Handles camelCase and common abbreviations
48
+ */
49
+ function fieldNameToLabel(name) {
50
+ // Handle common abbreviations
51
+ const abbrevs = {
52
+ sqft: "Sq Ft",
53
+ apn: "APN",
54
+ adu: "ADU",
55
+ avm: "AVM",
56
+ hoa: "HOA",
57
+ id: "ID",
58
+ mls: "MLS",
59
+ url: "URL",
60
+ };
61
+ const lower = name.toLowerCase();
62
+ if (abbrevs[lower]) {
63
+ return abbrevs[lower];
64
+ }
65
+ // Split camelCase and capitalize
66
+ return name
67
+ .replace(/([A-Z])/g, " $1")
68
+ .replace(/^./, (c) => c.toUpperCase())
69
+ .trim();
70
+ }
71
+ /**
72
+ * Format a number as currency (USD)
73
+ */
74
+ function formatCurrency(value) {
75
+ return new Intl.NumberFormat("en-US", {
76
+ style: "currency",
77
+ currency: "USD",
78
+ minimumFractionDigits: 0,
79
+ maximumFractionDigits: 0,
80
+ }).format(value);
81
+ }
82
+ /**
83
+ * Format a number with commas
84
+ */
85
+ function formatNumber(value) {
86
+ return new Intl.NumberFormat("en-US").format(value);
87
+ }
88
+ /**
89
+ * Format a date string
90
+ */
91
+ function formatDate(value) {
92
+ try {
93
+ const date = typeof value === "string" ? new Date(value) : value;
94
+ if (isNaN(date.getTime()))
95
+ return String(value);
96
+ return date.toLocaleDateString("en-US", {
97
+ year: "numeric",
98
+ month: "short",
99
+ day: "numeric",
100
+ });
101
+ }
102
+ catch {
103
+ return String(value);
104
+ }
105
+ }
106
+ /**
107
+ * Check if a field name suggests currency formatting
108
+ */
109
+ function isCurrencyField(fieldPath, name) {
110
+ const lower = (fieldPath + name).toLowerCase();
111
+ return (lower.includes("value") ||
112
+ lower.includes("price") ||
113
+ lower.includes("assessment") ||
114
+ lower.includes("amount") ||
115
+ lower.includes("equity") ||
116
+ lower.includes("balance") ||
117
+ lower.includes("cost"));
118
+ }
119
+ /**
120
+ * Check if a field name suggests year formatting
121
+ */
122
+ function isYearField(name) {
123
+ const lower = name.toLowerCase();
124
+ return lower.includes("year") && !lower.includes("years");
125
+ }
126
+ /**
127
+ * Check if a field name suggests square footage formatting
128
+ */
129
+ function isSqftField(name) {
130
+ const lower = name.toLowerCase();
131
+ return (lower.includes("sqft") ||
132
+ lower.includes("area") ||
133
+ lower.includes("squarefeet"));
134
+ }
135
+ /**
136
+ * Check if a field name suggests acreage formatting
137
+ */
138
+ function isAcresField(name) {
139
+ const lower = name.toLowerCase();
140
+ return lower.includes("acre") || lower.includes("acreage");
141
+ }
142
+ /**
143
+ * Format a property field value based on its metadata
144
+ *
145
+ * @param value The raw field value
146
+ * @param metadata The field's metadata
147
+ * @returns Formatted display string, or empty string for null/undefined
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * const meta = getPropertyFieldMetadata("valuation.estimatedValue");
152
+ * formatPropertyFieldValue(450000, meta); // "$450,000"
153
+ *
154
+ * const yearMeta = getPropertyFieldMetadata("building.yearBuilt");
155
+ * formatPropertyFieldValue(1985, yearMeta); // "1985"
156
+ * ```
157
+ */
158
+ function formatPropertyFieldValue(value, metadata) {
159
+ // Handle null/undefined
160
+ if (value == null) {
161
+ return "";
162
+ }
163
+ const { dataType, name, fieldPath, possibleValuesRef } = metadata;
164
+ // Handle boolean
165
+ if (dataType === "boolean") {
166
+ return value ? "Yes" : "No";
167
+ }
168
+ // Handle date
169
+ if (dataType === "date") {
170
+ return formatDate(value);
171
+ }
172
+ // Handle array[string]
173
+ if (dataType === "array[string]") {
174
+ if (Array.isArray(value)) {
175
+ return value.filter((v) => v != null).join(", ");
176
+ }
177
+ return String(value);
178
+ }
179
+ // Handle array[number]
180
+ if (dataType === "array[number]") {
181
+ if (Array.isArray(value)) {
182
+ return value
183
+ .filter((v) => v != null)
184
+ .map(formatNumber)
185
+ .join(", ");
186
+ }
187
+ return String(value);
188
+ }
189
+ // Handle number with formatting rules
190
+ if (dataType === "number" && typeof value === "number") {
191
+ // Year - plain integer
192
+ if (isYearField(name)) {
193
+ return String(Math.round(value));
194
+ }
195
+ // Currency fields
196
+ if (isCurrencyField(fieldPath, name)) {
197
+ return formatCurrency(value);
198
+ }
199
+ // Square footage
200
+ if (isSqftField(name)) {
201
+ return `${formatNumber(Math.round(value))} sq ft`;
202
+ }
203
+ // Acreage
204
+ if (isAcresField(name)) {
205
+ return `${value.toFixed(2)} acres`;
206
+ }
207
+ // Default number formatting
208
+ if (Number.isInteger(value)) {
209
+ return formatNumber(value);
210
+ }
211
+ return value.toFixed(2);
212
+ }
213
+ // Handle string with possibleValues lookup
214
+ if (dataType === "string" && typeof value === "string") {
215
+ // If there's a possibleValuesRef, the value itself is usually the display value
216
+ // in POSSIBLE_VALUES_CONSTANTS (they're already human-readable)
217
+ if (possibleValuesRef) {
218
+ const possibleValues = metadata_1.POSSIBLE_VALUES_CONSTANTS[possibleValuesRef];
219
+ if (possibleValues?.includes(value)) {
220
+ return value; // Already display-friendly
221
+ }
222
+ }
223
+ return value;
224
+ }
225
+ // Handle object - just return stringified for now
226
+ if (dataType === "object" && typeof value === "object") {
227
+ return JSON.stringify(value);
228
+ }
229
+ // Default: convert to string
230
+ return String(value);
231
+ }
232
+ /**
233
+ * Get the display label for a property field path
234
+ *
235
+ * @param fieldPath The field path (e.g., "building.yearBuilt")
236
+ * @returns Human-readable label (e.g., "Year Built")
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * getPropertyFieldDisplayLabel("building.yearBuilt"); // "Year Built"
241
+ * getPropertyFieldDisplayLabel("owner.mailingAddress.city"); // "City"
242
+ * ```
243
+ */
244
+ function getPropertyFieldDisplayLabel(fieldPath) {
245
+ const metadata = metadata_1.PROPERTY_FIELD_METADATA[fieldPath];
246
+ if (metadata) {
247
+ return fieldNameToLabel(metadata.name);
248
+ }
249
+ // Fallback: use the last part of the path
250
+ const parts = fieldPath.split(".");
251
+ const last = parts[parts.length - 1] || fieldPath;
252
+ return fieldNameToLabel(last);
253
+ }
254
+ /**
255
+ * Get the display name for a property response group
256
+ *
257
+ * @param groupName The group name (e.g., "building", "propertyOwnerProfile")
258
+ * @returns Human-readable display name (e.g., "Building", "Owner Portfolio")
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * getPropertyGroupDisplayName("building"); // "Building"
263
+ * getPropertyGroupDisplayName("propertyOwnerProfile"); // "Owner Portfolio"
264
+ * ```
265
+ */
266
+ function getPropertyGroupDisplayName(groupName) {
267
+ return GROUP_DISPLAY_NAMES[groupName] || fieldNameToLabel(groupName);
268
+ }
269
+ /**
270
+ * Get all displayable fields for a property within a specific group
271
+ *
272
+ * Returns formatted field data for rendering in the UI. Fields with
273
+ * null/undefined values are excluded.
274
+ *
275
+ * @param property The property object (can be raw BatchData JSON)
276
+ * @param groupName The response group name (e.g., "building", "valuation")
277
+ * @returns Array of display field objects with label, formatted value, etc.
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * const buildingFields = getPropertyFieldsForDisplay(property, "building");
282
+ * // Returns:
283
+ * // [
284
+ * // { fieldPath: "building.yearBuilt", label: "Year Built", value: "1985", dataType: "number" },
285
+ * // { fieldPath: "building.bedrooms", label: "Bedrooms", value: "3", dataType: "number" },
286
+ * // ...
287
+ * // ]
288
+ * ```
289
+ */
290
+ function getPropertyFieldsForDisplay(property, groupName) {
291
+ const groupFields = (0, utils_1.getPropertyGroupFields)(groupName);
292
+ const results = [];
293
+ for (const metadata of groupFields) {
294
+ // Skip array element placeholders ([n] patterns)
295
+ if (metadata.isArrayElement)
296
+ continue;
297
+ const value = (0, utils_1.getPropertyFieldValue)(property, metadata.fieldPath);
298
+ // Skip null/undefined values
299
+ if (value == null)
300
+ continue;
301
+ // Skip empty strings
302
+ if (typeof value === "string" && value.trim() === "")
303
+ continue;
304
+ // Skip empty arrays
305
+ if (Array.isArray(value) && value.length === 0)
306
+ continue;
307
+ const formatted = formatPropertyFieldValue(value, metadata);
308
+ // Skip if formatting returned empty
309
+ if (!formatted)
310
+ continue;
311
+ results.push({
312
+ fieldPath: metadata.fieldPath,
313
+ label: getPropertyFieldDisplayLabel(metadata.fieldPath),
314
+ value: formatted,
315
+ dataType: metadata.dataType,
316
+ });
317
+ }
318
+ return results;
319
+ }
320
+ /**
321
+ * Get all response groups that have displayable data in a property
322
+ *
323
+ * @param property The property object
324
+ * @returns Array of group names that have at least one displayable field
325
+ *
326
+ * @example
327
+ * ```typescript
328
+ * const groups = getPropertyGroupsWithData(property);
329
+ * // Returns: ["address", "building", "valuation", "owner", ...]
330
+ * ```
331
+ */
332
+ function getPropertyGroupsWithData(property) {
333
+ const allGroups = Object.keys(GROUP_DISPLAY_NAMES);
334
+ return allGroups.filter((group) => {
335
+ const fields = getPropertyFieldsForDisplay(property, group);
336
+ return fields.length > 0;
337
+ });
338
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@land-catalyst/batch-data-sdk",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
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",