A step-by-step, reversible plan to reproduce the full acquistorealestate.com property-search bar — the transaction/status toggles, type tabs, location autocomplete, every quick-filter popover, and the four-tab Advanced Filters — on the PURE site, keeping the same layout and behavior, and reusing the search backend we already run.
Match the live site's search UI and behavior on the PURE site, one reversible checkpoint at a time, without regressing the shell/global-header standard.
Reuse the existing PURE search backend and results page. This is mostly a UI-parity + filter-plumbing effort; the genuinely new work is the data fields several filters depend on. Keep the global header/rail standard intact — the header PSB is a shared component, changed only in pure-appshell.js.
The standalone pure-property-search.html is already strong; the header just hands off a ?tab=&q= URL to it.
| Capability | Where | Status |
|---|---|---|
| Location autocomplete (addresses/cities/zips/subdivisions) | mcp_search_suggest | Have |
| Core search + paging + sort | mcp_search_properties | Have |
| Facets (ISD list, etc.) | mcp_search_facets | Have |
| Gallery / Table / Map (Leaflet) views, detail modal, "Ask Poppy" | results page | Have |
| Role-gating (public sees Active/Pending; pros see Sold/Off-Market) | results page | Have |
| Header PSB chrome (Purchase/Lease, 4 type tabs, location chips, collapse) | drawPSB() in pure-appshell.js | Partial |
Tags below reflect real columns + coverage, not assumptions. Top-half filters map to real typed columns now. The rest (garage, pool, waterfront, HVAC, stories, HOA detail, schools, 3D tour) are NOT absent — they live in listings.raw->'idx' + listing_meta and need promoting to indexed columns. Open House has its own open_houses table. Only the Agents/Brokerages directory is genuinely un-joined.
status, but the column is dirty (Zoho CRM pipeline stages + lowercase dupes) so it needs a normalization map.property_sub_type with spelling variants (Condo≈Condominium, Townhome≈Townhouse, "Multi Family", Mobile≈Manufactured); each chip maps to a value set.levels)org_id/source, not a featured flag)Keep the live site's layout; render it in PURE tokens so it still reads as PURE.
Placement decision (Shattique, 7/15/26) — supersedes CP1's in-page bar.
pure-property-search.html. The page body renders results only (chips row + Map/Gallery/Table + list). No second search bar below the global header.window.PureSearch event/API); pure-property-search.html subscribes and re-runs mcp_search_properties on change. Header search renders on ALL front-end pure-appshell pages (search can be launched from anywhere on the front site) — NOT on admin pages (admin-appshell). Submitting a search from any front page routes to pure-property-search.html with the query state.pure-appshell.js (or a header search partial it loads), so the one header stays identical everywhere it appears.#172E55, gold #DCCAAF, green #117F39, hairline cards, one shadow. Open Sans throughout.buildFilters()) → drives popovers, the bar pills, the URL query, and Save Search. Single source of truth.UI is cheap; these fields are the real work. Anything tagged Data above needs the column present + indexed and wired into mcp_search_properties/mcp_search_facets.
| Area | Needs | Owner |
|---|---|---|
| RESO fields in raw.idx (present, not promoted) | promote garage/pool/waterfront/heating/cooling/stories/HOA/schools from listings.raw->'idx' (or listing_meta) into indexed columns + add filter args | Code |
| Coverage / enrichment | RESO fields exist but are sparsely populated (e.g. pool/waterfront/senior); open-house is its own table open_houses (992 rows). Enrich feed for fuller coverage | Code + feed |
| Freshness badge | expose max(modified) so the bar can say "updated N ago" | Code |
| Geolocation search | lat/lng + radius filter (PostGIS) behind Search-My-Location | Code |
| Agents/Brokerages tab | agent/office directory joined to listings + contact search RPC | Code |
| Save Search | saved_searches table (owner, filter json, name) + save/list/delete, act-as aware | Code + Lane A |
| DOM buckets | derive Coming Soon / Just Listed / Fresh … Super Stale from days-on-market thresholds | Code |
Traced from get-search-properties.php → get_pro_by_search_parameter() / imp_build_school_quarry(). The legacy WordPress MLS table is esa_properties; each search control maps to a real column there. Kept here for future reference so ingestion targets are unambiguous.
The data was migrated — it lives in listings.raw->'idx' (full RESO field set: AttachedGarageYN, GarageSpaces, CarportSpaces, PoolFeatures, WaterfrontFeatures, WaterBodyName, Heating, AssociationYN/FeeFrequency/FeeIncludes, StoriesTotal, SeniorCommunityYN, VirtualTourURLBranded/Unbranded, school fields…) and in listing_meta (96.7M key/value rows). It was not promoted to typed, indexed top-level columns on the listings view. So every filter is queryable now via raw->'idx'->>'<ResoField>'; the real work is promote + index (extract keys into generated columns / expression indexes) plus per-field normalization — not sourcing data. Caveat: RESO coverage is partial (e.g. Heating ~50–78%, Pool/Waterfront sparse), so filters return fewer matches until the feed is enriched.
| Control | Legacy param | esa_properties column | Query pattern | In PURE listings? |
|---|---|---|---|---|
| Status | s_status | mls_status code | IN (codes via get_p_status; public 19,24,106) | text label, needs code map |
| Category (Resi/Comm/Land) | p_type | property_type code | Comm IN(10,11,12,68,70,73,96) · Land IN(26,27,28) · Resi = NOT IN those | text, needs code map |
| Property Type popover | s_sub_type | property_sub_type code | Comm IN(34,53,62,75,76,78,79,82,87,88,89,97,99) · Land IN(52,91,103) · else Resi | text, variant map |
| Price | s_minprice / s_maxprice | list_price | >= / <= (“Any Price” skips max) | list_price |
| Beds (+ exact match) | s_beds / s_exact_match | bedrooms_total | = when exact, else >= | beds |
| Baths (+ exact match) | s_bath | bathrooms_total | = when exact, else >= | baths |
| Number of Stories | s_nstories | number_of_stories text | 1→One · 1+→One and One Half · 2→Two · 2+→Three Or More | not migrated (text lives in levels) |
| Home Size (Sq Ft) | s_sqft_min / s_sqft_max | property_size | >= / <= | sqft |
| Lot Size | s_lotsize_min / s_lotsize_max | lot_area acres | >= / <= (acres) | lot_sqft is sqft, needs acre convert |
| MLS Number | s_mls_number | listing_key | = | mls_number / listing_key |
| Days on ACQUISTO | s_dayinacq | created_at | Just listed −2d · Super Fresh −7d · Fresh −30d · Regular −31..−90d · Stale ≤−91d · Super Stale ≤−180d | listed_at / created_at |
| Year Built | s_ybuilt (+min/max) | year_built | newconstruction=curyear · 2010present ≥2010 · 2000present ≥2000 · before2000 ≤2000 · selectrange min/max | year_built |
| Garage Spaces | s_parking | garage_spaces | >= N | in raw.idx / listing_meta — promote+index |
| Views (city/mtn/park/water) | s_cityv / s_mountainv / s_parkv / s_waterv | property_view | LIKE '%city%' / '%mountain%' / '%park%' / '%lake|pond|river%' | in raw.idx / listing_meta — promote+index |
| Pool | s_pool | pool_private | != '' | in raw.idx / listing_meta — promote+index |
| Spa | s_spa | SpaYN | != '' | in raw.idx / listing_meta — promote+index |
| Waterfront | s_waterfront | water_front | != '' | in raw.idx / listing_meta — promote+index |
| Senior Community | s_community | SeniorCommunity | != '' | in raw.idx / listing_meta — promote+index |
| Heating | s_heating | HeatingYN | != '' | in raw.idx / listing_meta — promote+index |
| Cooling | s_cooling | CoolingYN | != '' | in raw.idx / listing_meta — promote+index |
| Open House | s_openhouse / radioView=open_houses | open_house_status, open_house_type, open_house_end_time | status='Active' AND type='Public' AND end_time > now | in raw.idx / listing_meta — promote+index |
| 3D Tour | s_3dtour | property_tour (/ VirtualTourURLBranded) | != '' | in raw.idx / listing_meta — promote+index |
| ACQUISTO Listing | s_featured | featured | = 1 | use org_id/source |
| School District | s_isd | SchoolDistrict | = | school_district |
| Elementary / Middle / High | s_elementary_school / s_middle_school / s_high_school | ElementarySchool / MiddleSchool / HighSchool | = | in raw.idx / listing_meta — promote+index |
| HOA (type/freq/name/includes/features) | hoa_types / hoa_frequencies / hoa_name / hoa_includes / hoa_feature | Association* fields (mapping detail in search-function.php) | — TBD from search-function.php | missing — only association_name migrated |
Notes. Source of truth: acquisto-api-php · acquistorealestate/allfunction/school-function.php (imp_build_school_quarry) + get-search-properties.php. Legacy stores mls_status/property_type/property_sub_type as numeric lookup codes (via get_p_status/get_p_type/get_p_sub_type in $alsetting); PURE's listings stores text labels, so migration must either bring the code columns or translate. The full HOA + neighborhood-feature SQL lives in the 663KB search-function.php and should be traced before building the Neighborhood tab.
One surface per turn, reversible, each deployed + logged before the next. Front-end-only CPs (1–4, 6) ship immediately against fields we have; data-dependent groups render disabled with a "pending data" note until CP8 lands.
pure-property-search.html (Purchase/Lease + Active/Sold, 4 type tabs, location chips + mcp_search_suggest autocomplete, My location, Reset, Search, live freshness via mcp_listings_freshness). Status: DONE 7/15, but now to be RELOCATED per the 04 placement decision — the same controls move into the PURE appshell header search section and the in-page bar is removed (tracked as CP1b).pure-appshell.js, on all front-end pure-appshell pages (not admin); delete the standalone in-page omni bar; wire header ↔ results via shared search state.saved_searches (act-as aware, per hats rules).mcp_search_properties, facets, freshness timestamp, geolocation, DOM buckets. Largest; unlocks the disabled controls. Codepure-appshell.js), deep-link into results, full QA, register + park nothing.Each CP is an additive commit to one file; the previous pure-property-search.html stays recoverable from git. No CP removes an existing capability. Data CP (8) is gated behind feature flags so disabled controls never break the bar.
This is the plan of record for search parity. Recommended next step: a board epic (parent + CP1–9 children with effort tags) and a contract-first note for Code covering the field list, the extended mcp_search_properties signature, and saved_searches.
PURE MLS Technologies · Search Parity Implementation Plan · prepared by Lane A (Shattique) · 7/15/26