@propertysimple/client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/API.md +27 -73
  2. package/README.md +200 -57
  3. package/client/src/index.d.ts +257 -2422
  4. package/client/src/index.js +6 -69
  5. package/contracts/src/index.d.ts +0 -7
  6. package/contracts/src/index.js +0 -7
  7. package/contracts/src/openapi.js +1 -1
  8. package/contracts/src/operations.d.ts +950 -4678
  9. package/contracts/src/operations.js +6 -36
  10. package/contracts/src/opportunity-events.d.ts +2 -1
  11. package/contracts/src/opportunity-events.js +6 -1
  12. package/contracts/src/property-features.d.ts +2 -2
  13. package/contracts/src/property-features.js +62 -19
  14. package/contracts/src/routes.d.ts +28 -305
  15. package/contracts/src/routes.js +82 -354
  16. package/contracts/src/schemas.d.ts +1114 -1355
  17. package/contracts/src/schemas.js +45 -223
  18. package/contracts/src/search.d.ts +1366 -253
  19. package/contracts/src/search.js +117 -2
  20. package/openapi.json +19288 -38596
  21. package/package.json +1 -9
  22. package/client/src/directory-sync.d.ts +0 -123
  23. package/client/src/directory-sync.js +0 -404
  24. package/client/src/managed-sync.d.ts +0 -129
  25. package/client/src/managed-sync.js +0 -410
  26. package/contracts/src/directory-execution.d.ts +0 -320
  27. package/contracts/src/directory-execution.js +0 -122
  28. package/contracts/src/directory-identity.d.ts +0 -1255
  29. package/contracts/src/directory-identity.js +0 -129
  30. package/contracts/src/directory-sales.d.ts +0 -333
  31. package/contracts/src/directory-sales.js +0 -102
  32. package/contracts/src/directory.d.ts +0 -1088
  33. package/contracts/src/directory.js +0 -179
  34. package/contracts/src/intelligence.d.ts +0 -366
  35. package/contracts/src/intelligence.js +0 -157
  36. package/contracts/src/professional-intelligence.d.ts +0 -256
  37. package/contracts/src/professional-intelligence.js +0 -90
  38. package/contracts/src/publication.d.ts +0 -44
  39. package/contracts/src/publication.js +0 -42
  40. package/contracts/src/smart-feed.d.ts +0 -3731
  41. package/contracts/src/smart-feed.js +0 -109
@@ -1,28 +1,11 @@
1
- import { directoryOperations } from './directory.js';
2
- import { smartFeedOperations } from './smart-feed.js';
3
- import { professionalIntelligenceOperations } from './professional-intelligence.js';
4
- import { directorySalesOperations } from './directory-sales.js';
5
- import { directoryIdentityOperations } from './directory-identity.js';
6
- import { directoryExecutionOperations } from './directory-execution.js';
7
1
  import { searchOperations } from './search.js';
8
- import { PublicationReportRequest, PublicationReceipt, DestinationQuery, DestinationResponse, } from './publication.js';
9
- import { JudgmentRequest, JudgmentSchema, IntelligenceCapabilitiesSchema } from './intelligence.js';
10
- import { Id, Empty, ResourceQuery, ResourcePageQuery, DatasetPageQuery, ListingDetailQuery, ListingSchema, ListingContactsSchema, AgentSchema, ListingQuery, IdentityQuery, IdentitySchema, AgentIdentitySchema, page, CatalogHealthQuery, CatalogHealthSchema, ListingPage, ListingMapQuery, ListingMapSchema, SimilarListingQuery, SimilarListingsSchema, SnapshotRequest, SnapshotSchema, CatalogChangeSchema, DomainEventSchema, ClassificationDefinitionSchema, ActionProfileSchema, OpportunityQuery, OpportunitySchema, PlanRequest, PlanSchema, PlanItemSchema, DispatchRequest, DispatchBatchSchema, AttemptRequest, AttemptSchema, OutcomesRequest, ContextEventsRequest, BatchResultSchema, WebhookRequest, WebhookSchema, CapabilitiesSchema, ReadinessSchema, } from './schemas.js';
11
- /** One registry drives the interface, HTTP validation and SDK response validation. */
2
+ import { Id, Empty, ResourceQuery, DatasetPageQuery, ListingDetailQuery, ListingSchema, ListingContactsSchema, AgentSchema, ListingQuery, IdentityQuery, IdentitySchema, AgentIdentitySchema, AgentEmailSharingSchema, page, CatalogHealthQuery, CatalogHealthSchema, ListingPage, ListingMapQuery, ListingMapSchema, SimilarListingQuery, SimilarListingsSchema, DomainEventSchema, ListingChangeQuery, ListingChangeSchema, CapabilitiesSchema, ReadinessSchema, } from './schemas.js';
3
+ /** One registry drives the interface, HTTP validation and SDK response validation.
4
+ * Retired operations (directory, feed, snapshots, opportunities, judgments) are
5
+ * listed in routes.ts as retiredRoutes and answer 410. */
12
6
  export const operations = {
13
7
  ...searchOperations,
14
- ...directoryOperations,
15
- ...smartFeedOperations,
16
- ...professionalIntelligenceOperations,
17
- ...directorySalesOperations,
18
- ...directoryIdentityOperations,
19
- ...directoryExecutionOperations,
20
- evaluateJudgment: { input: JudgmentRequest, output: JudgmentSchema },
21
- getJudgment: { input: ResourceQuery, output: JudgmentSchema },
22
- intelligenceCapabilities: { input: Empty, output: IntelligenceCapabilitiesSchema },
23
8
  capabilities: { input: Empty, output: CapabilitiesSchema },
24
- reportPublication: { input: PublicationReportRequest, output: PublicationReceipt },
25
- resolveDestination: { input: DestinationQuery, output: DestinationResponse },
26
9
  listListings: { input: ListingQuery, output: ListingPage },
27
10
  getCatalogHealth: { input: CatalogHealthQuery, output: CatalogHealthSchema },
28
11
  getListing: { input: ListingDetailQuery, output: ListingSchema },
@@ -34,23 +17,10 @@ export const operations = {
34
17
  listSimilarListings: { input: SimilarListingQuery, output: SimilarListingsSchema },
35
18
  resolveListingIdentity: { input: IdentityQuery, output: IdentitySchema },
36
19
  resolveAgentIdentity: { input: IdentityQuery, output: AgentIdentitySchema },
20
+ getAgentEmailSharing: { input: IdentityQuery, output: AgentEmailSharingSchema },
37
21
  getAgent: { input: ResourceQuery, output: AgentSchema },
38
22
  listAgentListings: { input: ListingQuery.safeExtend({ id: Id }), output: ListingPage },
39
- classificationSchema: { input: Empty, output: ClassificationDefinitionSchema },
40
- createSnapshot: { input: SnapshotRequest, output: SnapshotSchema },
41
- getSnapshot: { input: ResourceQuery, output: SnapshotSchema },
42
- listSnapshotItems: { input: ResourcePageQuery, output: ListingPage },
43
- listCatalogChanges: { input: DatasetPageQuery, output: page(CatalogChangeSchema) },
44
23
  listEvents: { input: DatasetPageQuery, output: page(DomainEventSchema) },
45
- listActionProfiles: { input: Empty, output: page(ActionProfileSchema) },
46
- listOpportunities: { input: OpportunityQuery, output: page(OpportunitySchema) },
47
- createPlan: { input: PlanRequest, output: PlanSchema },
48
- getPlan: { input: ResourceQuery, output: PlanSchema },
49
- listPlanItems: { input: ResourcePageQuery, output: page(PlanItemSchema) },
50
- claimDispatchBatch: { input: DispatchRequest, output: DispatchBatchSchema },
51
- startAttempt: { input: AttemptRequest, output: AttemptSchema },
52
- recordActionOutcomes: { input: OutcomesRequest, output: BatchResultSchema },
53
- recordProductContextEvents: { input: ContextEventsRequest, output: BatchResultSchema },
54
- createWebhookSubscription: { input: WebhookRequest, output: WebhookSchema },
24
+ listListingChanges: { input: ListingChangeQuery, output: page(ListingChangeSchema) },
55
25
  readiness: { input: Empty, output: ReadinessSchema },
56
26
  };
@@ -11,7 +11,7 @@ export declare const MarketAgeEvidenceSchema: z.ZodObject<{
11
11
  }, z.core.$strict>;
12
12
  /** An observed occasion, never a recommendation, consent, send or order permit.
13
13
  * received_at is the platform observation clock used for activity charts;
14
- * occurred_at preserves the source/calculated boundary for enrollment/history. */
14
+ * occurred_at preserves the source/discovery/calculated boundary for enrollment/history. */
15
15
  export declare const OpportunityObservationSchema: z.ZodObject<{
16
16
  schema_version: z.ZodLiteral<"opportunity-observation-v1">;
17
17
  id: z.ZodString;
@@ -22,6 +22,7 @@ export declare const OpportunityObservationSchema: z.ZodObject<{
22
22
  new_to_market: "new_to_market";
23
23
  price_drop: "price_drop";
24
24
  open_house_changed: "open_house_changed";
25
+ open_house_scheduled: "open_house_scheduled";
25
26
  market_age_milestone: "market_age_milestone";
26
27
  source_removed: "source_removed";
27
28
  }>;
@@ -8,7 +8,7 @@ export const MarketAgeEvidenceSchema = z.strictObject({
8
8
  });
9
9
  /** An observed occasion, never a recommendation, consent, send or order permit.
10
10
  * received_at is the platform observation clock used for activity charts;
11
- * occurred_at preserves the source/calculated boundary for enrollment/history. */
11
+ * occurred_at preserves the source/discovery/calculated boundary for enrollment/history. */
12
12
  export const OpportunityObservationSchema = z
13
13
  .strictObject({
14
14
  schema_version: z.literal('opportunity-observation-v1'),
@@ -20,6 +20,7 @@ export const OpportunityObservationSchema = z
20
20
  'new_to_market',
21
21
  'price_drop',
22
22
  'open_house_changed',
23
+ 'open_house_scheduled',
23
24
  'market_age_milestone',
24
25
  'source_removed',
25
26
  ]),
@@ -44,11 +45,15 @@ export const OpportunityObservationSchema = z
44
45
  * a consumer can mistake for a newly received marketing opportunity. */
45
46
  export function opportunityObservation(input) {
46
47
  const event = DomainEventSchema.parse(input);
48
+ if (event.type === 'open_house_changed' &&
49
+ event.evidence.notification_policy === 'scheduled_separately')
50
+ return null;
47
51
  const type = event.type === 'listing_removed' ? 'source_removed' : event.type;
48
52
  if (![
49
53
  'new_to_market',
50
54
  'price_drop',
51
55
  'open_house_changed',
56
+ 'open_house_scheduled',
52
57
  'market_age_milestone',
53
58
  'source_removed',
54
59
  ].includes(type))
@@ -16,9 +16,9 @@ export declare const PropertyFeatureDefinitionSchema: z.ZodObject<{
16
16
  synonyms: z.ZodArray<z.ZodString>;
17
17
  applicability: z.ZodEnum<{
18
18
  any: "any";
19
- rental: "rental";
20
19
  dwelling: "dwelling";
21
20
  sale: "sale";
21
+ rental: "rental";
22
22
  }>;
23
23
  source_paths: z.ZodArray<z.ZodString>;
24
24
  operators: z.ZodArray<z.ZodEnum<{
@@ -58,9 +58,9 @@ export declare const PropertyFeatureRegistrySchema: z.ZodObject<{
58
58
  synonyms: z.ZodArray<z.ZodString>;
59
59
  applicability: z.ZodEnum<{
60
60
  any: "any";
61
- rental: "rental";
62
61
  dwelling: "dwelling";
63
62
  sale: "sale";
63
+ rental: "rental";
64
64
  }>;
65
65
  source_paths: z.ZodArray<z.ZodString>;
66
66
  operators: z.ZodArray<z.ZodEnum<{
@@ -121,6 +121,18 @@ const offered = {
121
121
  conditional: 'The source explicitly offers this only subject to approval, terms, qualification or another stated condition.',
122
122
  not_offered: 'The source explicitly states this offer is not available.',
123
123
  };
124
+ /** A feature the catalog resolves from structured source fields alone, with no prose
125
+ * or model interpretation (rules in packages/core/src/catalog/source-features.ts).
126
+ * Its source paths are exactly the fields those rules read. AI classification is
127
+ * retired, so these and the exact `source_only` values are the features search can
128
+ * match (docs/search-portal-parity-2026-10-23.md). */
129
+ const sourced = (definition, paths, synonyms = []) => ({
130
+ ...definition,
131
+ source_paths: paths,
132
+ availability: 'source_only',
133
+ synonyms: [...new Set([...definition.synonyms, ...synonyms])],
134
+ });
135
+ const features = (fields) => fields.map((field) => `property_details.features.${field}`);
124
136
  /** Never reuse an existing ID for a narrower claim: the legacy coarse pantry,
125
137
  * laundry and style definitions remain alongside precise discovery attributes. */
126
138
  export const PROPERTY_FEATURE_REGISTRY = [
@@ -153,14 +165,25 @@ export const PROPERTY_FEATURE_REGISTRY = [
153
165
  feature(5, 'single_level', 'Single-level dwelling', 'The entire dwelling is explicitly single-story or single-level; a single room on the ground floor is insufficient.', ['InteriorFeatures', 'Stories']),
154
166
  feature(5, 'main_level_primary_bedroom', 'Main-level primary bedroom', 'An existing primary bedroom explicitly on the main or entry living level; does not establish step-free entry.', ['InteriorFeatures', 'Rooms', 'RoomType']),
155
167
  feature(5, 'main_level_full_bath', 'Main-level full bathroom', 'An existing full bathroom explicitly on the main or entry living level; a powder room is insufficient.', ['InteriorFeatures', 'Rooms', 'RoomType']),
156
- feature(6, 'garage', 'Garage', 'A garage explicitly included with this property; street parking or a carport is insufficient.', ['ParkingFeatures']),
168
+ sourced(feature(6, 'garage', 'Garage', 'A garage explicitly included with this property; street parking or a carport is insufficient.', ['ParkingFeatures']), [
169
+ 'property_details.source_flags.GarageYN',
170
+ 'property_details.source_flags.AttachedGarageYN',
171
+ 'property_details.garage_spaces',
172
+ ...features(['ParkingFeatures']),
173
+ ]),
157
174
  feature(6, 'oversized_garage', 'Oversized garage', 'The source explicitly calls the garage oversized; do not infer this from vehicle capacity.', ['ParkingFeatures']),
158
175
  exact(6, 'garage_capacity', 'Garage capacity', 'Explicit garage vehicle spaces; no capacity inferred from prose or area.', 'number', ['property_details.garage_spaces']),
159
- feature(7, 'private_pool', 'Private pool', 'A swimming pool belonging exclusively to this property, not a shared community pool.', ['PoolFeatures']),
176
+ sourced(feature(7, 'private_pool', 'Private pool', 'A swimming pool belonging exclusively to this property, not a shared community pool.', ['PoolFeatures']), [
177
+ 'property_details.source_flags.PoolPrivateYN',
178
+ ...features(['ExteriorFeatures', 'LotFeatures']),
179
+ ], ['pool', 'swimming pool']),
160
180
  feature(7, 'community_pool', 'Community pool', 'A shared community swimming pool available to this property, not a private pool.', ['CommunityFeatures', 'AssociationAmenities']),
161
- feature(7, 'private_spa', 'Private spa or hot tub', 'An installed private spa or hot tub included with the property; community spa access is insufficient.', ['SpaFeatures']),
162
- feature(8, 'water_frontage', 'Water frontage', 'The property itself directly borders a body of water; a view or nearby access alone is insufficient.', ['WaterfrontFeatures']),
163
- feature(8, 'water_view', 'Water view', 'A water view explicitly described from the property; nearby water alone is insufficient.', ['View']),
181
+ sourced(feature(7, 'private_spa', 'Private spa or hot tub', 'An installed private spa or hot tub included with the property; community spa access is insufficient.', ['SpaFeatures']), [
182
+ 'property_details.source_flags.SpaYN',
183
+ ...features(['ExteriorFeatures', 'InteriorFeatures', 'LotFeatures']),
184
+ ], ['spa', 'hot tub']),
185
+ sourced(feature(8, 'water_frontage', 'Water frontage', 'The property itself directly borders a body of water; a view or nearby access alone is insufficient.', ['WaterfrontFeatures']), ['property_details.source_flags.WaterfrontYN', ...features(['WaterfrontFeatures'])], ['waterfront', 'lakefront', 'oceanfront', 'beachfront', 'riverfront']),
186
+ sourced(feature(8, 'water_view', 'Water view', 'A water view explicitly described from the property; nearby water alone is insufficient.', ['View']), features(['View'])),
164
187
  feature(8, 'water_access', 'Water access', 'Explicit access rights or direct access to water associated with the property; nearby water alone is insufficient and no ownership is implied.', ['WaterfrontFeatures', 'AssociationAmenities'], { ...consequential }),
165
188
  feature(8, 'dock', 'Dock present', 'An existing dock reported for this property; private ownership and access rights are not implied.', ['Dock'], { applicability: 'any' }),
166
189
  feature(8, 'private_dock', 'Private dock', 'An existing dock explicitly for exclusive property use; shared docks, availability and dock permits alone are insufficient.', ['Dock', 'ExteriorFeatures', 'WaterfrontFeatures']),
@@ -171,9 +194,10 @@ export const PROPERTY_FEATURE_REGISTRY = [
171
194
  feature(9, 'separate_entrance', 'Separate entrance', 'An existing separate entrance to a described suite, living space or basement; not merely the dwelling front door.', ['InteriorFeatures', 'ExteriorFeatures', 'Basement']),
172
195
  feature(10, 'fenced_yard', 'Fenced yard', 'An explicitly fenced private yard; a fence elsewhere or suggested fencing is insufficient.', ['Fencing', 'ExteriorFeatures']),
173
196
  feature(10, 'fully_fenced_yard', 'Fully fenced yard', 'The private yard is explicitly fully fenced or enclosed on all sides; partial fencing or a gate alone is insufficient.', ['Fencing', 'ExteriorFeatures']),
174
- feature(11, 'fireplace', 'Fireplace', 'An installed fireplace explicitly part of the property.', [
175
- 'FireplaceFeatures',
176
- ]),
197
+ sourced(feature(11, 'fireplace', 'Fireplace', 'An installed fireplace explicitly part of the property.', ['FireplaceFeatures']), [
198
+ 'property_details.source_flags.FireplaceYN',
199
+ ...features(['FireplaceFeatures', 'InteriorFeatures', 'Heating']),
200
+ ], ['fireplaces']),
177
201
  feature(11, 'wood_stove', 'Wood stove', 'An installed wood-burning stove; a fireplace or wood-stove-ready connection is insufficient.', ['Heating', 'FireplaceFeatures']),
178
202
  feature(12, 'architectural_style', 'Architectural style', 'The architectural style explicitly named in the source; do not guess a style from location, age or price.', ['ArchitecturalStyle'], {
179
203
  values: {
@@ -226,7 +250,15 @@ export const PROPERTY_FEATURE_REGISTRY = [
226
250
  feature(17, 'greenhouse', 'Greenhouse', 'An existing greenhouse on the property; green home or a proposed greenhouse is insufficient.', ['OtherStructures']),
227
251
  feature(17, 'orchard', 'Orchard', 'An existing orchard on the property, not a subdivision or street name.', ['Vegetation', 'LotFeatures']),
228
252
  feature(17, 'fruit_trees', 'Fruit trees', 'Existing fruit-bearing trees on the property; decorative trees or a fruit-themed street name are insufficient.', ['Vegetation', 'LotFeatures', 'ExteriorFeatures']),
229
- feature(18, 'solar_panels', 'Solar panels', 'Installed solar panels explicitly serving the property; suitability or solar-ready wiring is insufficient.', ['GreenEnergyGeneration']),
253
+ sourced(feature(18, 'solar_panels', 'Solar panels', 'Installed solar panels explicitly serving the property; suitability or solar-ready wiring is insufficient.', ['GreenEnergyGeneration']), features([
254
+ 'GreenEnergyGeneration',
255
+ 'Roof',
256
+ 'Electric',
257
+ 'Utilities',
258
+ 'Appliances',
259
+ 'ExteriorFeatures',
260
+ 'InteriorFeatures',
261
+ ]), ['solar']),
230
262
  feature(18, 'solar_ownership', 'Solar ownership', 'The explicitly stated tenure of the installed solar system; presence alone proves no ownership.', ['GreenEnergyGeneration'], {
231
263
  ...consequential,
232
264
  values: {
@@ -239,8 +271,10 @@ export const PROPERTY_FEATURE_REGISTRY = [
239
271
  feature(19, 'exposed_interior_brick', 'Exposed interior brick', 'Visible exposed brick explicitly inside the dwelling; brick exterior is insufficient.', ['InteriorFeatures']),
240
272
  feature(19, 'interior_arches', 'Interior arches', 'Existing architectural arches explicitly inside the dwelling.', ['InteriorFeatures']),
241
273
  feature(19, 'original_vintage_details', 'Original vintage details', 'Explicit original or retained historic interior details; generic charm, age or reproduction finishes are insufficient.', ['InteriorFeatures']),
242
- feature(20, 'central_air_conditioning', 'Central air conditioning', 'Central air conditioning explicitly present; ceiling fans or an unspecified cooling system are insufficient.', ['Cooling']),
243
- feature(20, 'heat_pump', 'Heat pump', 'An installed heat pump for space conditioning; a heat-pump-ready connection or water heater alone is insufficient.', ['Heating', 'Cooling']),
274
+ sourced(feature(19, 'hardwood_floors', 'Hardwood floors', 'Hardwood or solid wood flooring explicitly listed for the property; laminate, vinyl or wood-look flooring is insufficient.', ['Flooring']), features(['Flooring']), ['hardwood', 'wood floors']),
275
+ sourced(feature(20, 'air_conditioning', 'Air conditioning', 'Air conditioning or another installed cooling system as the source states it (a cooling flag or a named system); ceiling or attic fans alone are insufficient.', ['Cooling']), ['property_details.source_flags.CoolingYN', ...features(['Cooling'])], ['a/c', 'air conditioned', 'cooling']),
276
+ sourced(feature(20, 'central_air_conditioning', 'Central air conditioning', 'Central air conditioning explicitly present; ceiling fans or an unspecified cooling system are insufficient.', ['Cooling']), features(['Cooling']), ['central air', 'central ac']),
277
+ sourced(feature(20, 'heat_pump', 'Heat pump', 'An installed heat pump for space conditioning; a heat-pump-ready connection or water heater alone is insufficient.', ['Heating', 'Cooling']), features(['Heating', 'Cooling'])),
244
278
  feature(20, 'zoned_hvac', 'Zoned HVAC', 'An explicitly zoned heating or cooling system; multiple units alone do not establish independent zones.', ['Heating', 'Cooling']),
245
279
  feature(20, 'smart_thermostat', 'Smart thermostat', 'An installed smart or connected thermostat; programmable alone is insufficient.', ['InteriorFeatures', 'GreenEnergyEfficient']),
246
280
  feature(21, 'roof_replaced', 'Roof replaced', 'A completed roof replacement explicitly stated; servicing, a warranty, material type or planned replacement is insufficient.', ['Roof']),
@@ -271,16 +305,18 @@ export const PROPERTY_FEATURE_REGISTRY = [
271
305
  feature(25, 'workshop', 'Workshop', 'An existing workshop on the property; space suitable for a workshop is insufficient.', ['OtherStructures', 'Rooms']),
272
306
  feature(25, 'powered_outbuilding', 'Powered outbuilding', 'An existing outbuilding explicitly supplied with electricity; electric service to the main property alone is insufficient.', ['OtherStructures', 'Utilities']),
273
307
  feature(25, 'insulated_outbuilding', 'Insulated outbuilding', 'An existing outbuilding explicitly insulated; potential conversion is insufficient.', ['OtherStructures']),
274
- feature(26, 'rv_parking', 'RV parking', 'Existing space explicitly designated for RV parking; does not establish hookup or legal permission.', ['ParkingFeatures', 'ExteriorFeatures'], { applicability: 'any' }),
308
+ sourced(feature(26, 'rv_parking', 'RV parking', 'Existing space explicitly designated for RV parking; does not establish hookup or legal permission.', ['ParkingFeatures', 'ExteriorFeatures'], { applicability: 'any' }), features(['ParkingFeatures', 'ExteriorFeatures']), ['rv']),
275
309
  feature(26, 'boat_parking', 'Boat parking', 'Existing space explicitly designated for boat parking; does not establish water access.', ['ParkingFeatures', 'ExteriorFeatures'], { applicability: 'any' }),
276
310
  feature(26, 'covered_vehicle_storage', 'Covered vehicle storage', 'Existing covered RV or boat storage; ordinary garage or uncovered space alone is insufficient.', ['ParkingFeatures', 'OtherStructures'], { applicability: 'any' }),
277
311
  feature(26, 'rv_hookup', 'RV hookup', 'An installed RV utility hookup; parking space or general utilities alone are insufficient.', ['ExteriorFeatures', 'Utilities'], { applicability: 'any' }),
278
- feature(27, 'finished_basement', 'Finished basement', 'Basement explicitly finished in whole or part; no legal bedroom or rental-unit claim.', ['Basement']),
279
- feature(27, 'walkout_basement', 'Walkout basement', 'Basement explicitly has a walkout to the exterior; an interior stair alone is insufficient.', ['Basement']),
280
- feature(28, 'mountain_view', 'Mountain view', 'A mountain view explicitly described from the property; geographic proximity alone is insufficient.', ['View']),
281
- feature(28, 'city_view', 'City view', 'A city or skyline view explicitly described from the property.', ['View']),
282
- feature(28, 'golf_view', 'Golf view', 'A golf course view explicitly from the property; nearby golf is insufficient.', ['View']),
283
- feature(28, 'ocean_view', 'Ocean view', 'An ocean view explicitly from the property; proximity or a generic water view is insufficient.', ['View']),
312
+ sourced(feature(27, 'basement', 'Basement', 'A basement of any kind explicitly part of the property; a crawl space or slab is insufficient.', ['Basement']), ['property_details.source_flags.BasementYN', ...features(['Basement'])]),
313
+ sourced(feature(27, 'finished_basement', 'Finished basement', 'Basement explicitly finished in whole or part; no legal bedroom or rental-unit claim.', ['Basement']), features(['Basement'])),
314
+ sourced(feature(27, 'walkout_basement', 'Walkout basement', 'Basement explicitly has a walkout to the exterior; an interior stair alone is insufficient.', ['Basement']), features(['Basement'])),
315
+ sourced(feature(28, 'view', 'View', 'Any view explicitly listed for the property (water, mountain, city, golf, territorial and the like); proximity alone is insufficient.', ['View']), features(['View']), ['views']),
316
+ sourced(feature(28, 'mountain_view', 'Mountain view', 'A mountain view explicitly described from the property; geographic proximity alone is insufficient.', ['View']), features(['View'])),
317
+ sourced(feature(28, 'city_view', 'City view', 'A city or skyline view explicitly described from the property.', ['View']), features(['View'])),
318
+ sourced(feature(28, 'golf_view', 'Golf view', 'A golf course view explicitly from the property; nearby golf is insufficient.', ['View']), features(['View'])),
319
+ sourced(feature(28, 'ocean_view', 'Ocean view', 'An ocean view explicitly from the property; proximity or a generic water view is insufficient.', ['View']), features(['View'])),
284
320
  feature(29, 'open_layout', 'Open layout', 'An explicitly open-plan or open-concept living layout; an open house event is insufficient.', ['InteriorFeatures']),
285
321
  feature(29, 'high_ceilings', 'High ceilings', 'Explicitly high, vaulted or cathedral ceilings; do not estimate ceiling height from photos.', ['InteriorFeatures']),
286
322
  feature(29, 'skylights', 'Skylights', 'Installed skylights explicitly serving the dwelling; natural-light adjectives alone are insufficient.', ['WindowFeatures', 'InteriorFeatures']),
@@ -298,7 +334,14 @@ export const PROPERTY_FEATURE_REGISTRY = [
298
334
  feature(33, 'outdoor_kitchen', 'Outdoor kitchen', 'An existing private outdoor kitchen; a portable grill alone is insufficient.', ['ExteriorFeatures']),
299
335
  feature(33, 'built_in_grill', 'Built-in grill', 'An existing installed private outdoor grill; portable or communal grills are insufficient.', ['ExteriorFeatures']),
300
336
  feature(33, 'fire_pit', 'Fire pit', 'An existing private fire pit included with the property; communal access and proposed installation are insufficient.', ['ExteriorFeatures']),
301
- feature(34, 'ev_charging', 'Installed EV charging', 'An installed electric vehicle charging facility explicitly available to this property; EV-ready wiring alone is insufficient.', ['ParkingFeatures', 'Electric']),
337
+ sourced(feature(34, 'ev_charging', 'Installed EV charging', 'An installed electric vehicle charging facility explicitly available to this property; EV-ready wiring alone is insufficient.', ['ParkingFeatures', 'Electric']), features([
338
+ 'ParkingFeatures',
339
+ 'Electric',
340
+ 'Utilities',
341
+ 'GreenEnergyEfficient',
342
+ 'ExteriorFeatures',
343
+ 'InteriorFeatures',
344
+ ]), ['ev charger', 'ev charging', 'electric vehicle charger']),
302
345
  feature(34, 'ev_ready_wiring', 'EV-ready wiring', 'Installed dedicated EV-ready wiring or prewiring explicitly stated; no installed charger is implied.', ['Electric', 'ParkingFeatures']),
303
346
  feature(35, 'generator', 'Generator', 'An installed generator included with the property; generator-ready hookup alone is insufficient.', ['Electric', 'OtherEquipment']),
304
347
  feature(35, 'home_battery', 'Home battery', 'An installed home electrical battery storage system; solar alone is insufficient.', ['GreenEnergyGeneration', 'Electric']),