# PropertyLab Conversational Buyer Workspace

> **Delivery annotation — 8 September 2026.** The existing Laravel portal repository has since been located and the Property Research module implemented in an isolated checkout. OpenAI is selected; its local configured credential was validated during integration. MySQL research import and scoped automated fixtures have run locally. No public or production deployment has occurred. The planning text below is retained for scope/acceptance traceability: statements that the repository was absent, AI was unconnected or implementation had not begun describe the original drafting state. See [the current module handbook](../readMe.md) for implemented behavior and outstanding release gates. No milestone is marked complete by copying this design.


**Development specification v1.1 · 8 September 2026 · Laravel / Vue portal target**

**Document status:** Implementation specification based on the agreed product direction. Describes a proposed build; it does not claim the AI backend, routing or collaborative accounts are already implemented. Values identified as proposed defaults are engineering/product decisions to validate during implementation. No claim of being first in the world is made.

**Architecture revision:** The production target is the user’s existing Laravel/PHP, Vue/Inertia and MySQL portal. This supersedes the earlier proposal to extend the Sites/Vinext server and provision D1/R2. The current React Site remains the verified prototype; this document does not claim it has been migrated. Portal versions below are user-reported and await repository/lockfile verification.

**Primary audience:** Product design, frontend/backend engineering, data engineering, QA and the product owner.

## 1. Product summary

PropertyLab will integrate free-form AI conversation with the existing property explorer. Buyers ask questions on the left; the right-hand map and linked charts reveal the evidence behind the answers. The workspace focuses on **price position** and **daily-life fit**.

The product promise is: **Understand the price. Test the location. See what changes your decision.**

The core loop is: ask or select → clarify only consequential ambiguity → run research tools → show a concise explanation and linked evidence → change assumptions → compare alternatives → save a decision brief.

Guidance is optional support inside the conversation, never a mandatory questionnaire. The assistant must accept arbitrary relevant property questions and follow-ups. It must acknowledge unsupported questions honestly rather than falling back to fabricated or scripted results.

The release must let a buyer understand a conclusion, inspect its evidence, change an assumption and restore the previous comparison without assistance.

## 2. Existing baseline and gaps

Inventory verified against the current local portal and database on the document date.

| Area | Current baseline | New work |
|---|---|---|
| Hosting/application | Private PropertyLab Research Site; React 19, TypeScript, Vinext, Cloudflare-compatible runtime | Implement a PropertyLab Research module in the existing Laravel portal; use the Site as the parity reference |
| Presentation | Base UI/shadcn React primitives, Recharts, Mapbox GL JS | Vue components using the portal design system, Chart.js evidence renderers and shared map/chart state |
| Property index | 18,917 scheme locations | Queryable backend index, name resolution, explicit geography and entity precision |
| Source archive | 643,210 records across 18 datasets in the working SQLite database | Versioned ingestion and normalized research tables for the required subset |
| Monthly observations | 186,259 scheme-month records; price and source PSF | Provenance, observation IDs, methodology checks and query service |
| Places | 52,544 amenity records | Source-qualified place IDs and query tools |
| Rail | 398 records, including 361 marked open | Preserve operational status and category distinctions |
| Sale evidence | Limited displayed sale samples; exact-name guard; 1,249 history links withheld | Preserve source row IDs and exclusion reasons; improve cohort selection |
| Current comparisons | Nearest 12 schemes before sale filtering | Filter a declared eligible universe before ranking/pagination; disclose any computational cap |
| Shortlist | Up to five schemes, local to this browser | Decision notes, user-entered offers, scenarios and optional persistent user storage |
| AI | No live AI conversation endpoint | Real server-side model connection, validated tools and grounding checks |
| Travel | Straight-line proximity | Geocoding, route estimates and travel comparisons |
| Data freshness | Collection 7 September 2026; source sales through March 2026 | Per-result dates, refresh pipeline and immutable snapshots |
| Accounts/storage | No D1/R2 bindings configured in current hosting file | Reuse Laravel users, session authentication and authorization; add MySQL research/workflow tables and verify Redis/Horizon |

Current source modules include `app/page.tsx`, `components/research/comparables.tsx`, `lib/research.ts`, `lib/comparables.ts`, `lib/map-layers.ts`, `lib/map-3d.ts`, `lib/map-config.ts` and `scripts/export_data.py`.

The working database is `/home/ubuntu/propertylab-analysis/propertylab.sqlite`. Existing neutral archive tables (`records`, `source_responses`, `parsed`) are retained as source evidence. New namespaces, routes, database objects and code use PropertyLab naming. Source evidence and legally required attribution must remain truthful; rebranding must not invent provenance.

### Production target supplied by the user

| Layer | Reported portal version | Implementation decision |
|---|---|---|
| Backend | Laravel 13.18.1; PHP 8.4.19; Composer root constraint `^8.3` | Laravel controllers, Form Requests, domain services, policies, Eloquent/query builder and Artisan imports |
| Page bridge | `inertiajs/inertia-laravel` 2.0.24; Vue adapter reported as 3.3 | Audit both lockfiles before selecting adapter APIs; reconcile the reported major-version mismatch |
| Frontend | Vue 3.5; Vite 6.4; Tailwind CSS 4.3; Node 22 for builds | Vue single-file components and Composition API; reuse portal layouts and UI primitives |
| Data | MySQL 8 | Normalized versioned research data and private workflow persistence |
| Background work | Horizon 5.47.1 | Use existing Redis-backed infrastructure after configuration verification; isolate research imports/exports from interactive work |
| Roles | spatie/laravel-permission 6.25.0 | Reuse current role conventions, with policies enforcing record ownership/tenant scope |
| Map | Mapbox GL JS 3.7.0 from CDN | One pinned script/CSS pair and shared loader; verify reused map code against this version |
| Charts | Chart.js 4.4 | Rebuild evidence charts in Vue with stable entity references and accessible table equivalents |
| Avatar | three.js 0.166 | Preserve the existing avatar integration; optional decoration that does not gate chat or compete with map rendering |

The supplied portal source is not present in the current workspace. Exact patch versions, PHP extensions, dependency constraints, authentication, tenant conventions and deployment topology must be verified from its repository. Do not silently upgrade the portal to match this document. The [official Inertia v3 upgrade guide](https://inertiajs.com/docs/v3/getting-started/upgrade-guide) upgrades both client and Laravel adapters to v3; the reported v2/v3 combination is a compatibility investigation, not proof that the running portal is broken.

The live prototype uses Mapbox GL JS 3.30.0, whereas the portal reports 3.7.0. Treat the move as a version-specific port and test terrain, extrusions, style restoration and camera behavior. Do not load both versions in one page. Node is the frontend build toolchain; Laravel/PHP is the proposed application backend. SSR would require its own decision and runtime configuration.

## 3. Scope and release boundary

### Full v1 scope

- Real free-form buyer chat and contextual follow-ups.
- Search, resolve and inspect schemes; keep search distinct from current listing availability.
- Buyer-entered asking price, area and area basis.
- Price-position analysis with explicit peer definitions, source dates and sample limitations.
- Price/PSF history, scheme comparisons, sale-sample distributions and data tables.
- Bidirectional chart/card/map highlighting; near-bar hover, deliberate focus, keyboard and touch equivalents.
- Daily-life priorities, saved destinations, walking/driving estimates and supported amenity proximity.
- Editable buyer brief, explicit geographic scope and contextual follow-ups.
- Scenarios, undo, scene restore, shortlists, viewing notes and decision checklists.
- Responsive, accessible workspace; cancellation, retry, partial results and fallback to conventional exploration.
- Private persistence and export that respects the deployed audience.

### Explicitly outside full v1

Live available-unit search without a listing feed; automated formal valuations; appreciation or investment-return forecasts; lender eligibility/preapproval; automated offers or messages to agents; resident-demographic ranking; inferred crime/safety or school-quality scores; public-transport journey guarantees; interior 3D tours without source media; unrestricted AI-generated charts/code; modeled flood-depth reinstatement. Existing district flood history remains secondary evidence with district-level limits.

Mortgage/ownership-cost tools, live listings, shared household collaboration and public transit are subsequent extensions. They should not delay the agreed price-position and daily-life-fit experience.

## 4. Design principles and buyer guidance

1. **Free conversation first.** Examples and suggested actions help users start; the composer is always available.
2. **Ask only what matters now.** A specific price assessment needs the subject and relevant price/area information. Browsing does not require a completed profile.
3. **One understandable result.** Default answer structure: conclusion → supporting visual → material limitation → one primary next action, with at most two secondary suggestions.
4. **Visible context.** A selected property, comparison set and search geography are always named. “This one” must resolve to an explicit current selection.
5. **Explainable evidence.** Dates, sample units, assumptions and exclusions are available through a consistent View evidence action.
6. **Reversible exploration.** Filters, scenarios and map changes support restore/undo. The user can stop generation without losing the last valid scene.
7. **No hidden suitability score.** Show individual criteria, priorities, reasons and unknowns. Sorting is transparent.
8. **Preserve visual meaning.** Property category colors are stable across the map, cards and charts. Analysis markers use outlines, letters and separate legends.
9. **Guidance at the decision point.** Explain “asking PSF”, “observed median” or “straight-line distance” where relevant. Avoid a long onboarding tutorial.
10. **Research remains usable without AI.** Search, charts, filters and shortlists do not depend on a successful model response.

Example starter prompts: “Check this asking price”, “Compare the properties I selected”, “Find locations that fit my day”. Use “schemes” or “locations” when the data cannot establish available homes.

## 5. Screen layout and navigation

### Desktop

Proposed initial split: chat 35%, spatial/evidence workspace 65%, with a keyboard-operable divider and min-width constraints. Suggested starting minima are 340 CSS px for conversation and 480 CSS px for the map; stack when both cannot fit.

- Header: PropertyLab brand, Advisor/Explore navigation, shortlist and brief access.
- Buyer brief strip: budget basis, types, size, places, destinations, required/preferred criteria and scenario name. Overflow goes into an accessible disclosure.
- Left conversation: messages, property chips, answer cards and composer. The composer includes the current selection/scope summary, Send and Stop while running.
- Right: Mapbox explorer. Relevant chart/compare evidence expands below the map; it can be enlarged without losing the current scene.
- Source drawer: exact supporting rows, methodology, dates and exclusions. Opening it does not discard the answer.
- Each completed analytical answer offers Restore this view and View evidence.

### Required map defaults

On a fresh session, use Satellite Streets (`satellite-streets-v12`), 3D enabled, pitch 70°, zoom 16, bearing −20°, configured Klang Valley center `[101.66, 3.12]`. Map appearance and property-type legend start collapsed. These are initial defaults; restored scenes and explicit camera adjustments retain their own state. Navigation may fit requested results while retaining 3D/style unless the user changes them. It must not repeatedly reset to the opening camera.

Preserve all five basemaps, 2D/3D controls, rotation, terrain/buildings, seven property categories, multi-checkbox type filters, and distinct rail/amenity legends. Reinstall every custom layer, selection and hover source after a style reload.

### Tablet/mobile

Use Chat / Map / Evidence tabs when panes cannot remain readable. Switching tabs preserves draft, result, scope, selected entities and scenario. Chart taps select the entity and expose Show on map. No important action relies on hovering, dragging or clicking a WebGL object. Provide a searchable property list equivalent.

Support 320 CSS px width, text enlargement and reduced motion. Touch actions should have approximately 44px effective targets without overlapping adjacent actions. Follow WCAG 2.2 AA as the conformance target, verified rather than assumed. [WCAG 2.2](https://www.w3.org/TR/WCAG22/)

## 6. Primary user journeys

### J1 — First use and exploratory chat

1. Show the live map, short invitation and composer immediately; keep auxiliary controls collapsed.
2. Buyer types a request or chooses a starter.
3. Extract explicit preferences into the buyer brief; mark inferred/ambiguous values as provisional.
4. Resolve geography and property identity. Ask one targeted clarification if it changes the outcome materially.
5. Show the interpreted scope, run supported tools and display a coherent result.
6. Offer an action relevant to the result: inspect a scheme, refine a condition or compare selections.

No results must name the constraining conditions. Offer specific relaxations; never silently broaden budget, geography or required criteria.

### J2 — Asking-price position

1. Resolve the scheme and distinguish scheme evidence from a specific unit.
2. Request asking price and area only if needed. Confirm built-up/land/unknown basis; for ambiguous units such as “1,200 feet”, ask rather than assume.
3. Show the normalized asking PSF and its input values.
4. Construct a transparent comparison cohort. Return exclusions and data-quality limitations alongside metrics.
5. Display the asking marker against the sample, price/PSF history and key differences.
6. Let the buyer change the cohort or asking assumption and see a new result with a change explanation.
7. Save the assumption and outstanding verification questions to a shortlist item.

If a fair comparison cannot be formed, show the supported facts and missing inputs; do not generate a market-value conclusion.

### J3 — Daily-life fit

1. Ask which destination or amenity matters. Support work, school and custom labels without inferring sensitive personal traits.
2. Resolve an exact destination; show geocoding alternatives and ask the buyer to confirm.
3. Obtain mode and departure/arrival intent. Translate relative dates using Asia/Kuala_Lumpur, then show the explicit date/time used.
4. Run supported routing or clearly labeled straight-line proximity. Never substitute one for the other silently.
5. Show each property's journey estimate or criterion separately, with source/time/origin precision.
6. Explain trade-offs and which hard conditions are met, failed or unknown.
7. Add another destination or compare an alternative scenario.

“Near a school” is proximity, not admission eligibility, catchment membership or school quality. “Near rail” is not an integrated transit journey.

### J4 — Compare alternatives

Select two to five schemes through chat, map, cards or charts. Align the comparison’s unit, metric basis, cohort period and travel assumptions. Show price/PSF, available sample, journey criteria and material unknowns in a compact table and appropriate linked charts. A changed sort must explain its basis. The existing five-property cap remains the v1 comparison limit and is visible.

### J5 — Scenario exploration

Save A; duplicate into B; edit one or more explicit assumptions. A remains immutable. Show changed inputs, eligible/rejected/unknown outcomes and relevant metric deltas. Use the same evidence snapshot by default. Route results must show their time assumptions and computation timestamp; mismatches are explicit. Apply B makes it active and creates an undo entry. Restore A does not rerun the AI or quietly refresh the data.

### J6 — Before deciding

Each saved property has editable notes and a checklist generated only from applicable issues: area basis, unit details, source freshness, condition, fees, route verification and other unknowns. Separate system-detected gaps from user notes. Checking an item records buyer acknowledgement; it does not convert missing source evidence into a verified fact.

## 7. Conversation requirements

| ID | Requirement |
|---|---|
| CHAT-01 | Accept free text, multiline drafts, follow-ups and corrections. Do not restrict interaction to canned prompts. |
| CHAT-02 | Keep selected scheme IDs, visible result IDs, brief revision and explicit query scope available to intent resolution. |
| CHAT-03 | Ask for clarification when names, unit/area basis, destinations or pronouns are materially ambiguous. |
| CHAT-04 | Show explicit preferences separately from assistant-proposed interpretations; a proposal cannot silently become a hard constraint. |
| CHAT-05 | Retain the last valid answer/scene during loading. Show actual tool status and Cancel. Never simulate fabricated progress stages. |
| CHAT-06 | Distinguish research answers, clarification, unsupported requests, partial evidence and service errors. |
| CHAT-07 | Suggestions are contextual, dismissible and optional. Editable inputs can appear inside a response when they simplify precision. |
| CHAT-08 | Allow undo, replay and editing assumptions; previous messages retain the result they originally referenced. |
| CHAT-09 | Support English, Bahasa Malaysia and Mandarin, including code-switching, as a full-v1 quality target. Numbers, identities and units must be invariant across translations. |
| CHAT-10 | Do not claim current availability, unit condition, commute time, legal outcomes or future returns absent a supporting tool result. |
| CHAT-11 | Never expose internal instructions, hidden reasoning or raw secrets. User-facing reasoning means evidence, calculations and selection rules. |
| CHAT-12 | User-directed shortlist/scenario changes get visible acknowledgements and undo; no agent contact, booking or external sharing occurs as an incidental chat action. |

## 8. Price-position methodology

### Inputs and identities

Subject scheme ID; optional buyer-offer ID; asking amount in RM; area and source unit; area basis (`built_up`, `land`, `unknown`); supplied unit characteristics and the source of each assertion. Treat user-entered values as assumptions, not verified source facts.

Canonical calculations use square metres internally and display the selected unit. Use 1 ft² = 0.09290304 m². Asking PSF is asking RM divided by area ft². Source-provided PSF must retain its original method/basis metadata. Do not silently replace it with a derived value or mix derived and reported PSF in one benchmark. For a conflict, flag it and choose an explicitly documented basis or exclude the row.

### Cohort formation

Proposed starting controls: same property category, radius 3 km, previous 24 months relative to the dataset's supported end month, and ±20% of the supplied area where area basis is compatible. These are adjustable product defaults, not valuation standards. Display them before interpreting the result.

Apply eligibility filters across the declared search universe before choosing displayed rows. Same-scheme and nearby-scheme cohorts are separate evidence groups; the buyer can switch or explicitly combine them. The current nearest-12-first shortcut is not adequate for a claim about all matching evidence and must be removed or prominently presented as a restricted sample.

Exclude wrong/unverified scheme links, invalid dates, nonpositive price/area, incompatible area basis and values missing the requested metric. Preserve legitimate outliers with flags; do not delete them merely to improve the result. Do not collapse identical-looking rows into purported unique transactions without reliable identity evidence.

### Output and language

Show included displayed-row count, scheme count, period, excluded count by reason, source dates and geographic scope. The count means displayed observations, not full transaction volume. Retain source aggregate `n` with its unconfirmed-window caveat; do not add it to sample counts or silently treat it as unique monthly transactions.

Compute sample median and quartiles deterministically. Use linear interpolation at index `(n-1)p` for quartiles and label them as sample spread, not statistical confidence or a valuation interval. Ask-position difference is `(asking_psf / sample_median_psf - 1) × 100` only for a positive compatible benchmark. Keep full precision internally; round money and percentages only for display.

Proposed presentation rule: with fewer than five compatible observations, show individual values and disclose a small sample rather than presenting a distribution band or categorical price verdict. Five observations is an interface threshold, not a claim that the evidence is statistically representative. Always expose date spread, scheme composition and unknown characteristics.

Use “X% above the observed sample median” rather than “X% overpriced”. No automatic fair-value estimate, investment recommendation, liquidity score or market-appreciation claim is in scope.

### Trend semantics

Use source monthly price and PSF independently. Missing or invalid values remain null; lines break at missing periods. Distinguish observations from transaction events. Explain that changing property/sale mixes can move medians. Do not interpret the line as a repeat-sales index. Support price and PSF toggles, with both exact values in the evidence table. Cross-property comparisons must disclose missing/misaligned periods rather than quietly carrying values forward.

## 9. Daily-life methodology

### Criteria model

A criterion has an ID, user label, kind, destination/category, mode, threshold/unit, required/preferred status, optional user priority, time intent, provenance and evaluation status. Statuses are `met`, `not_met`, `unknown` or `not_applicable`. Unknown is never converted to a failure, success or zero.

For a hard constraint, only known passing candidates enter “Meets all required criteria”. Unknowns appear in a separate “Needs checking” set that users can include. Preferences order candidates through an explicit chosen priority, such as shortest journey then asking price where provided. No hidden blended score or demographic suitability inference.

### Routing and geography

Use Mapbox Directions for a selected route; use Matrix for batching only where its profile/time semantics match the request. Use travel-time contours for exploration after validating provider support. Mapbox documents walking, cycling, driving and traffic-aware driving; integrated public transit is a separate data dependency. Country/route-specific quality must be tested. [Directions](https://docs.mapbox.com/api/navigation/directions/), [Matrix](https://docs.mapbox.com/api/navigation/matrix/), [Isochrone](https://docs.mapbox.com/api/navigation/isochrone/)

Do not infer an inbound commute from an outbound contour around a workplace: roads and time-dependent journeys can be asymmetric. Final eligibility must use property-to-destination direction and the stated time. A geographic prefilter is a performance aid, not proof that every feasible candidate was searched; disclose bounded candidate sets.

Each route result records provider, origin/destination coordinates and precision, resolved timezone, mode, departure/arrival interpretation, computed timestamp, distance, duration, geometry and warnings. Durations are estimates, not guarantees. Show one declared comparison basis for all candidates. If an assumed departure is used, state it before ranking. If the provider cannot support the requested arrival-time/traffic condition, ask or report that limitation.

Current scheme coordinates may be centroids rather than entrances. Let the buyer confirm an entrance or map point where useful; label buyer-adjusted origins. Do not draw a route from a scheme centroid as a verified door-to-door journey. No-route results remain unknown; never manufacture a straight line and label it a walking route.

For two destinations, show both journey times and, if requested, total or worst journey using an explicit formula. The buyer controls the trade-off. Amenities use recorded category/name/location only; proximity does not imply opening hours, service quality or admission rights.

## 10. Visualisation catalogue

| ID / visual | Question | Required data and interaction |
|---|---|---|
| VIZ-01 Price/PSF line | How have observations changed? | Scheme-month series, units, null gaps; hover/focus exposes period/value/n and highlights the scheme |
| VIZ-02 Horizontal bars | How do selected schemes compare? | Same metric/basis and disclosed dates; whole row is a hit target; data table equivalent |
| VIZ-03 Sample distribution | Where does the asking PSF sit? | Compatible source-row values and buyer assumption; sample thresholds; source row drilldown |
| VIZ-04 Price–journey scatter | What trade-off does more travel buy? | Matched candidates with compatible price basis and routed times; unknowns listed outside plot |
| VIZ-05 Journey bars/routes | Which daily trip is shorter? | Per-destination estimates with identical mode/time assumptions; route selected by property ID |
| VIZ-06 Comparison table | Which option fits my priorities? | Two to five stable entities, evidence basis, requirement statuses and unknowns |
| VIZ-07 Geographic filters | Which areas are worth exploring? | Verified boundaries/routes/contours; scope legend and source method |
| VIZ-08 Scenario delta | What changed from A to B? | Aligned scenario results and input differences; no false comparable delta across incompatible snapshots |

The renderer chooses only supported components. The model may request a visualization purpose, but validated result metadata determines whether it is possible. Do not render decorative graphs for a simple fact. Axes, units, source period, point meaning and important unknowns must be visible. Use a common zero baseline for magnitude bars; any nonzero line-axis baseline remains explicit. Avoid dual axes for price and PSF. Histograms link to sets of observations/schemes, not fictitious unit coordinates.

## 11. Linked chart, answer and map interactions

### Identity and state

Every interactive datum carries `entityRefs` and, where relevant, `saleRowId`/`observationId`. References distinguish scheme, place, station and user-supplied offer. Display labels A/B/C remain stable within a saved comparison; never use array position or translated display name as identity.

Keep `hoveredRefs`, `selectedRefs`, `pinnedComparisonRefs`, `activeResultId` and camera state separate. Hover does not mutate a shortlist, filter, scenario or persistent selection.

### Near-bar requirement

For horizontal bars, make the full labeled row/band interactive within chart bounds. For isolated marks, expand the hit area by a proposed 8 CSS px and use nearest-mark resolution when areas overlap. Do not activate beyond the chart or select a neighboring series across a band boundary. Touch/keyboard targets must be independently usable even when the visual mark is small.

When the pointer is close to Property A's bar:

1. Emphasize that bar/row and its label.
2. Set transient hover to Property A's canonical reference.
3. Show a contrast-safe map halo and matching label; preserve the property-type color.
4. Keep the camera steady. If offscreen, show a compact Bring A into view action.
5. Clear transient hover on exit, Escape, chart unmount or committed result replacement; restore the persistent selected appearance.

Reverse hover from a map marker highlights all corresponding visible data marks without scrolling the conversation automatically. Keyboard focus creates the equivalent highlight. Deliberate activation selects the entity; Show on map or chart click can focus it with a bounded transition. On mobile, first tap selects and exposes details/Show on map. Respect reduced motion.

A monthly aggregate highlights its scheme, not a purported individual transaction. A histogram bin highlights its scheme set and reports the aggregation. Hidden layers are not silently enabled by hover. A cluster containing A can show a containing-cluster highlight and an explicit focus action.

## 12. Answer, result and scene contracts

### One authoritative research result

Each analytical execution produces an immutable result used by the model explanation, cards, charts and map. Required fields:

| Field | Meaning |
|---|---|
| `schemaVersion`, `resultId`, `createdAt` | Contract and immutable identity |
| `conversationId`, `turnId`, `briefRevision` | Ownership/context association |
| `status` | complete, partial, no_match, needs_input, unsupported, failed |
| `snapshotIds`, `methodVersion` | Exact evidence and calculation versions |
| `normalizedQuery` | Resolved subject, geography, filters, date/area/price basis and ranking |
| `entities` | Known IDs, display names, category and precision |
| `metrics`, `series`, `mapFeatures` | Server-computed values and supported spatial evidence |
| `coverage` | Universe, eligible/included/displayed counts, cap/pagination, date coverage and missingness |
| `evidenceRefs`, `claims` | Traceable source observations and permitted quantitative claims |
| `assumptions`, `limitations`, `exclusions` | Buyer inputs and evidence boundaries |
| `nextActions` | Allowlisted actions with validated arguments |

Narrative numerical claims must reference a permitted claim or metric ID. Important displayed numbers are rendered from result fields rather than copied from free prose. Post-validation catches unsupported entities, values and overstatements; failure uses a deterministic evidence summary and a visible limitation.

### Scene snapshot

A scene stores result references, brief revision, geographic scope, filters, selected entities, comparison set, chart configuration, map layers/style/dimension/camera, scenario assumptions and data snapshot IDs. It excludes hover and authentication secrets. Restore is a deterministic state operation without a model call. Refresh is separate and creates a new result/scene. If provider terms prohibit storing particular route/geocode responses, retain the allowed metadata and clearly mark that external evidence requires recomputation; never claim exact replay of unstored data.

### Example contract shapes

```ts
type EntityRef = { kind: 'scheme' | 'place' | 'station' | 'buyer_offer'; id: string };
type EvidenceRef = {
  snapshotId: string; dataset: string; sourceRecordId: string;
  observationKind: 'displayed_sale_row' | 'scheme_month' | 'scheme_aggregate' | 'place' | 'route';
};
type Metric = {
  id: string; value: number | null; unit: string; basis: string;
  entityRefs: EntityRef[]; evidenceRefs: EvidenceRef[]; assumptions: string[];
};
type ChartSpec = {
  id: string; kind: 'line' | 'bar' | 'distribution' | 'scatter' | 'table';
  title: string; seriesRefs: string[]; xUnit: string; yUnit: string;
  metricBasis: string; linkMode: 'entity' | 'entity_set';
};
```

These are design shapes, not an implemented SDK. Production schemas must reject unknown keys, invalid enums, nonfinite numbers, unknown IDs and unsupported combinations.

## 13. AI orchestration and tool interface

Implement a single server-side orchestrator as a Laravel/PHP service initially. It interprets the request, resolves context, plans bounded tool calls, executes them and explains the returned result. Separate deterministic domain functions are sufficient; a multi-agent runtime is not a launch requirement. Select a model/provider through the evaluation set, keep the model ID configurable, and store its credential only server-side. Existing public Mapbox credentials do not provide language-model access.

| Tool | Core inputs | Required output |
|---|---|---|
| `resolve_schemes` | text, optional geography, limit | Candidates, IDs, matching basis and ambiguity |
| `search_schemes` | explicit geography, type array, budget basis, filters, cursor | Eligible entities, counts, exclusions and coverage |
| `get_scheme_evidence` | IDs, period, metrics | Aggregate metadata, independent price/PSF series, source rows |
| `get_sale_sample` | IDs/cohort, date/area/price filters, cursor | Displayed-row evidence with row IDs and exclusions |
| `analyse_price_position` | subject/offer ID, cohort policy | Deterministic asking comparison, sample spread and limitations |
| `compare_schemes` | 2–5 IDs, metric/time bases | Aligned table/series and unknowns |
| `get_nearby_places` | scheme/origin, categories, radius | Places, straight-line method and precision |
| `resolve_destination` | user text, geography | Geocode alternatives and precision; no silent ambiguous choice |
| `calculate_journeys` | origins, destination IDs, mode/time | Provider estimates, directions, warnings and timestamp |
| `evaluate_life_fit` | criteria, candidate IDs, evidence refs | Met/not_met/unknown results and transparent ordering |
| `run_scenario` | base scene/result, input changes | New immutable result and assumption/result delta |
| `update_shortlist` | explicit operation and known IDs | Acknowledgement, revision and undo action |
| `save_scene` / `restore_scene` | result/scene references | Authorized scene state; no research regeneration on restore |

Tools receive structured arguments, never arbitrary model-generated SQL, JavaScript, HTML or network destinations. Use server-side schema validation, parameterized queries, authorized result lookup and result-size limits. The model cannot declare success for a tool that did not complete.

Proposed safeguards to tune by evaluation: 4,000-character user messages; 8 tool invocations and 4 orchestration rounds per turn; 5 compared properties; 20 journey origins per interactive request with pagination; 45-second turn timeout and individual provider timeouts. These are application budgets, not claims about provider API limits. Exhaustion produces an explicit partial result and next action.

## 14. HTTP and streaming contracts

Laravel renders the workspace page through Inertia at a proposed `/research/advisor` route. Inertia handles page navigation and initial bounded props. Chat streaming and research JSON use ordinary authenticated HTTP endpoints; they are not Inertia page visits. The `/api/advisor` prefix below is a logical route namespace and does not imply Laravel’s default stateless `api` middleware. For integration into the same portal, register these endpoints with its session authentication, CSRF protection for mutations, permission checks and throttling. Adopt the portal’s existing API guard only if its repository requires that pattern.

Proposed endpoints:

- `POST /api/advisor/turns`: `{conversationId, clientTurnId, text, briefRevision, sceneId, selectionRefs, scope}`. Validate access and idempotency, then stream events.
- `POST /api/advisor/turns/:id/cancel`: cancel authorized turn; idempotent.
- `GET /api/advisor/results/:id`: authorized immutable research result.
- `GET /api/advisor/results/:id/evidence?cursor=...`: bounded evidence pages.
- `GET/POST/PATCH/DELETE /api/advisor/conversations...`: authenticated persistence with revision checks.
- `POST /api/advisor/scenarios`: save/duplicate/apply explicit scenario operations.
- `GET /api/advisor/capabilities`: available data versions, AI/routing availability and supported modes.

Use a Laravel streamed response for interactive turns, consumed through browser Fetch/ReadableStream with same-origin credentials, the portal’s CSRF mechanism and AbortController. [Laravel documents streamed responses and SSE](https://laravel.com/docs/13.x/responses#streamed-responses). SSE-format event payloads are acceptable without assuming browser EventSource supports POST. Do not send `X-Inertia` for these stream requests. Return structured HTTP errors before streaming begins; after headers are sent, report failures as stream events. A decoder must handle split UTF-8 characters, split event frames and multiple events in one network chunk. Every event contains conversation ID, turn ID, monotonically increasing sequence, event ID and relevant result ID.

Events: `accepted`, `clarification`, `tool_started`, `tool_completed`, `result_ready`, `answer_delta`, `turn_completed`, `turn_cancelled`, `turn_failed`.

Commit a validated `result_ready` bundle as one coherent visual revision. Analytical text referring to that result can then stream; earlier status text must not assert unverified results. Retain the last valid scene until replacement. If text generation fails after a valid result, show the evidence with an incomplete explanation state and Retry explanation.

Only one active research turn per conversation in v1. A new question or context-changing action supersedes the current generation with visible state. Client and server reject late events from obsolete turns. Stop aborts providers where possible and always blocks late visual and shortlist mutations. Hover is local only and never triggers a model call.

Error envelopes distinguish invalid input, ambiguity, no match, unsupported capability, insufficient evidence, provider timeout, rate limit, unauthorized access and internal failure. Do not expose stack traces or credentials.

### Laravel execution and background work

The first implementation runs bounded interactive model/research work in the streamed request. Persist the accepted turn and reserve the conversation revision in a short transaction before opening the stream. Do not hold a database transaction or session lock across a model/network call; the separate cancellation request must be able to run concurrently. Before every result commit or requested mutation, check the current turn revision and cancellation state. Client disconnect handling and cooperative provider cancellation reduce wasted work; they do not guarantee upstream billing stops immediately.

Verify PHP/web-server/proxy buffering, compression behavior, request/idle timeouts and available worker capacity in the actual staging deployment. A long-lived stream occupies capacity; measure concurrent usage and normal-page latency. If that model exceeds capacity, move turn execution to a dedicated queue and use a separate authorized event delivery endpoint with durable sequencing/replay. A Horizon job cannot directly write into a browser request in another process. Reverb/WebSockets are optional only if the chosen delivery architecture needs them.

Use Horizon for imports, refresh preparation, reports and permitted cache warming. [Horizon requires Redis queues](https://laravel.com/docs/13.x/horizon); MySQL remains the durable research/workflow store. Jobs carry IDs and snapshot references, recheck ownership for private outputs, and use idempotency, bounded retries and separate worker capacity. Keep the cancellation/turn state authoritative and do not let a retried job apply a stale scene or duplicate a user action.

## 15. Data model and ingestion

### Research tables (logical schema)

- `propertylab_snapshots`: ID, collected_at, coverage_start/end, manifest hash, source inventory, ingestion/method version and attribution.
- `propertylab_schemes`: scheme_id, display/canonical name, category, geography, centroid, precision and identity crosswalk.
- `propertylab_scheme_snapshot_values`: scheme_id + snapshot_id, source aggregate median/PSF/n, history status and raw source reference.
- `propertylab_sale_observations`: sale_row_id, snapshot_id, scheme_id, source record/page references, displayed month, area/basis, price, reported PSF and quality flags.
- `propertylab_monthly_observations`: observation_id, snapshot_id, scheme_id, month, n, price, PSF and method flags.
- `propertylab_places`: place_id, source ID where available, snapshot, name, category, geometry and rail status.
- `propertylab_source_crosswalks`: canonical IDs mapped to source dataset/record IDs with match status and audit notes.

Retain current scheme IDs (`SHA-256(name)` first 16 hex characters) for compatibility, but introduce a persisted identity crosswalk before accepting renamed/new upstream schemes. Check collisions; never regenerate existing IDs because a display name changed. Source row IDs are snapshot-qualified ingestion-record identities, not verified unique transaction IDs. Similar-looking sales remain identifiable observations; any deduplication rule must be separately documented and auditable.

Index scheme geography/category/name, observation scheme+period, and source IDs. Use bounding boxes or spatial cells for candidate retrieval plus exact distance checks. Verify locality ambiguity and cross-region names. Do not inject the full archive into prompts.

### User/workflow tables

`propertylab_conversations`, `propertylab_messages`, `propertylab_buyer_briefs`, `propertylab_destinations`, `propertylab_buyer_offers`, `propertylab_research_results`, `propertylab_scenes`, `propertylab_scenarios`, `propertylab_shortlists`, `propertylab_shortlist_items`, `propertylab_checklist_items` and `propertylab_turn_runs`.

Every private row is tenant/owner scoped using the existing Laravel authenticated user and the portal’s verified tenant model. Reuse its user table; do not create a second identity system. Roles permit capabilities, while policies and scoped queries protect each conversation, result, destination and export. Scenario/brief changes use revision checks to prevent lost updates. Research results are immutable.

Use MySQL 8 for normalized query data and private workflow records. Index the actual geography, scheme/period and owner/revision access paths, and benchmark query plans. Store exact prices/areas using documented decimal precision and serialize analytical decimals consistently across PHP and JavaScript; do not silently truncate PSF. Use bounded JSON for validated immutable result/scene documents and normalized columns for frequently queried fields. Large permitted exports may use the portal’s configured private Laravel filesystem disk, with authorized download handlers. Redis provides bounded caches/queues and ephemeral coordination, not the sole copy of saved research.

New table names use the `propertylab_` prefix shown above. Fit migrations and foreign keys to the portal’s existing user/tenant key types and naming conventions. Neutral source archive tables and snapshots remain provenance evidence; do not destructively rename shared portal tables. Optional MySQL spatial indexes require explicit SRID/axis handling and measured query plans; bounding-box retrieval plus exact distance checks is a valid initial approach.

### Ingestion gates

Build an idempotent Artisan import from the existing SQLite source or its versioned export. Validate counts, coordinate ranges, IDs, source dates, schema changes, exact-name guards, source units and duplicate observation identities. Preserve names under explicit UTF-8/collation rules; case-insensitive database matching must not weaken the exact-name source guard. Import into a staging snapshot in chunks and retain the original archive/checksums. Create new snapshots atomically, produce a change report, run regression fixtures, then switch the active pointer. Never mutate saved evidence results to appear current. Refresh cadence depends on an authorized source/feed; do not advertise live refresh without one.

## 16. Laravel and Vue implementation plan

Implement a cohesive Research module inside the portal, following its existing directory casing and conventions. Proposed structure (relative to the future portal repository):

```text
routes/web.php                                  # authenticated workspace and HTTP route groups
app/Http/Controllers/PropertyLab/                # Workspace, Turn, Evidence, Scenario controllers
app/Http/Requests/PropertyLab/                   # validation and authorization inputs
app/Policies/PropertyLab/                        # owner/tenant and capability policies
app/Services/PropertyLab/Advisor/                # orchestrator, tool registry, claim validator
app/Services/PropertyLab/Research/               # repository, price, fit, routing, provenance
app/Data/PropertyLab/                           # validated result, scene and event DTOs
app/Models/PropertyLab/                         # research and private workflow models
app/Jobs/PropertyLab/                           # imports, exports, refresh preparation
app/Console/Commands/PropertyLab/                # import and verification commands
config/propertylab.php                          # feature, provider and budget configuration
database/migrations/                            # additive propertylab_* schema changes
resources/js/Pages/PropertyLab/Advisor.vue
resources/js/Components/PropertyLab/Advisor/     # conversation, brief, cards, drawer, scenarios
resources/js/Components/PropertyLab/Maps/        # map, controls, legends
resources/js/Components/PropertyLab/Research/    # Chart.js renderers, source/comparison tables
resources/js/composables/propertylab/            # shared scene state, map lifecycle, stream client
resources/js/lib/propertylab/                    # portable map helpers, contracts and formatting
```

Reuse the portal’s Vue/Tailwind components, typography, layouts and form conventions. React components, React hooks, Base UI React primitives and Recharts cannot be used directly as Vue components. Translate them into Vue single-file components and Chart.js renderers. Audit portable TypeScript map helpers, palette definitions, numeric fixtures and contracts for reuse; authoritative research calculations move to PHP and require parity tests.

Use one Composition API store scoped to the workspace, or the portal’s existing Pinia convention if already installed. Do not add a global singleton that can leak state across users during SSR. Keep hover, selection, camera and committed research distinct. Core state events remain `SELECT_ENTITY`, `HOVER_ENTITY`, `CLEAR_HOVER`, `UPDATE_BRIEF`, `START_TURN`, `COMMIT_RESULT`, `CANCEL_TURN`, `SET_CAMERA`, `SET_LAYERS`, `RESTORE_SCENE`, `APPLY_SCENARIO`, `UNDO`.

Keep Mapbox and Chart.js instances outside deep Vue reactivity, using shallow references or equivalent lifecycle ownership. Initialize browser APIs on mount, clean up handlers on unmount, and preserve a single map during conversation updates. Keep explicit container sizing and ResizeObserver for pane resizing and mobile visibility changes. Style changes restore custom sources, layers, terrain and highlights after the style loads; register handlers once.

Chart.js datasets carry canonical `schemeId`/entity references independently from rendered indices. A nearest-point mode alone is not the specified near-bar behavior: bound selection to the plot or labeled row band and the declared proximity threshold, with deterministic overlap resolution. [Chart.js supports configurable and custom interaction modes](https://www.chartjs.org/docs/latest/configuration/interactions.html). Map-origin highlighting updates chart active state without triggering a feedback loop. Provide semantic DOM controls and data tables for keyboard/screen-reader access; a canvas tooltip alone is insufficient.

Lazy-load chart/evidence components. Stream deltas update the relevant answer, without rebuilding all chart datasets or the map. If the existing avatar is displayed, animate only on actual turn states, honor reduced motion, stop rendering when hidden and avoid taking WebGL resources needed by the map. It is not a prerequisite for a complete research answer.

## 17. Privacy, security and trust

- The current Site is private. Keep this audience during development; a public/multi-user launch is a separate release decision.
- Reuse the Laravel portal’s session guard, CSRF handling and Spatie role conventions after repository verification. Enforce per-record policies and tenant/owner query scopes on every endpoint, job and cache lookup; role membership alone does not authorize another user’s result. Do not trust client-supplied owner IDs. Keep the Research module behind a permission/feature flag during staged integration.
- Keep AI credentials and server provider credentials in runtime secrets, never client bundles or Git. Domain-restrict browser-safe Mapbox tokens where supported.
- Treat user text, listings, source payloads, URLs and database strings as untrusted data. Embedded instructions cannot override tool rules or authorization.
- Validate all tool inputs and output references; use parameterized queries and bounded operations. No arbitrary URL-fetch tool in v1; a supplied property link may be stored as a user reference but must not imply its contents were fetched.
- Sanitize rendered Markdown and allowlisted links. No model-authored executable UI or HTML.
- Exact workplaces, schools, private notes and offer amounts are private. Include only the minimum needed in provider requests and explain destination-provider use near setup.
- Proposed retention: unsaved session state for the session; user-saved conversations until deletion; diagnostic logs 30 days with text/location redaction. Validate provider retention separately before launch. Export excludes private destinations and notes unless explicitly selected. Removal must propagate through private records and caches within a documented operational policy.
- Sharing is explicit and off by default; do not make a private Site or conversation public through an export button. Household collaboration requires its own authorization and sharing model.
- User-directed property fit uses chosen needs and objective evidence, not protected characteristics or inferred neighborhood demographics.

## 18. Quality targets and acceptance tests

Targets below are proposed launch gates, not measured current performance. Establish a benchmark environment and traffic profile before sign-off.

### Performance and operability targets

- Hover-to-linked-highlight p95 below 100 ms once data is rendered; zero AI/network calls on hover.
- Acknowledge a submitted request within 500 ms; show actual work state while retaining the last scene.
- Target first useful research result within 5 seconds for cached/local-data questions and 12 seconds for a bounded routing question under the documented test profile. Track model/provider time separately.
- Cancel state acknowledged within 300 ms on the client; obsolete events cannot change committed state.
- Stay responsive with 18,917 map schemes using clustering and bounded chart/table rendering.
- Record turn latency, tool success, rejected claims, missing-evidence states and estimated provider usage without logging private raw prompts by default.

### Acceptance suite

| Test | Passing outcome |
|---|---|
| QA-01 Fresh entry | Required satellite/3D camera and collapsed controls; composer usable before profile completion |
| QA-02 Free-form equivalence | Relevant paraphrases normalize to equivalent research queries and calculations |
| QA-03 Ambiguous entity | Same-name schemes in different places trigger clarification; no wrong-property answer |
| QA-04 Scope integrity | Empty results disclose constraints; no hidden radius/type/budget relaxation |
| QA-05 Numeric grounding | Every displayed analytical value equals its referenced deterministic metric after documented rounding |
| QA-06 Cohort transparency | Filtering precedes display pagination; restricted universes/caps and exclusions are visible |
| QA-07 Sale identity | Repeated-looking displayed rows are not called unique transactions; source-row references remain inspectable |
| QA-08 Price and area basis | Unknown/incompatible land vs built-up area cannot generate a misleading PSF benchmark |
| QA-09 Missing observations | Missing/zero/invalid PSF and missing months remain gaps, independently of absolute price |
| QA-10 Near-bar hover | Bar-band/expanded hit area highlights the correct entity; overlap resolves predictably; camera stays steady |
| QA-11 Reverse linking | Map hover highlights corresponding chart/card; entity-set data highlights a set rather than a fictitious unit |
| QA-12 Selection/accessibility | Click, keyboard and mobile equivalents identify the same entity; focus is visible; offscreen focus is deliberate |
| QA-13 Style and layout changes | Custom layers/hover/selection survive all five style switches, pane resizing and mobile tab changes |
| QA-14 Cancel/race | Stop or a newer question prevents late results/actions from changing scene or shortlist |
| QA-15 Partial failure | Failed routes retain valid price evidence; user sees which part is missing and can retry |
| QA-16 Scene replay | Restore reproduces stored state without calling the model; refresh creates a distinct result |
| QA-17 Scenario independence | Editing B leaves A unchanged; deltas disclose input and evidence-version differences |
| QA-18 Journey semantics | Origin/destination direction, time, mode and precision are explicit; no straight-line fallback described as a route |
| QA-19 Fit unknowns | Unknown requirements remain a separate state, never silently pass/fail |
| QA-20 User-data isolation | One account cannot read or mutate another's messages, offers, destinations, results or scenes |
| QA-21 Injection resistance | Instructions embedded in property names/payloads cannot execute tools or alter policies |
| QA-22 Unsupported request | Live inventory, valuation, transit and unsupported financial conclusions are handled honestly |
| QA-23 Multilingual parity | English/BM/Mandarin preserve IDs, numerical results, units and scope through follow-ups |
| QA-24 Regression | Existing explorer filters, cards, compare, 3D, type colors, rail and amenities remain functional |
| QA-25 Guided usability | First-time buyers complete ask → inspect → change → restore and identify one important limitation without moderator help |

Build a minimum 60-question evaluation set balanced across price, fit, ambiguity, unsupported requests, follow-ups, empty/partial data and scenarios. Include every supported language and mixed-language cases. Each fixture specifies expected tools, resolved entities, numerical oracle and required limitations. Exact numerical/identity/authorization checks are gates; qualitative language quality is human-reviewed. Conduct moderated sessions with at least five representative buyers as a proposed initial usability check; record task completion and misunderstandings, not just visual preference.

## 19. Delivery sequence and work packages

| Milestone | Deliverable | Exit gate |
|---|---|---|
| M0 — Portal foundation and explorer parity | Audit actual Laravel/Vue lockfiles and auth conventions; add module routes, MySQL imports, Vue map/charts, scene store and deployment checks | Existing portal builds; explorer behavior matches the reference; identity and decimal fixtures pass; stream infrastructure smoke test passes |
| M1 — Real chat + price research | Model endpoint, resolution/search/price tools, free chat, brief, linked charts, evidence drilldown, cancellation | QA-02–14, 20–22 pass for price workflows; users can ask beyond starter buttons |
| M2 — Daily-life fit | Destination confirmation, walking/driving integration, criterion states, routes and comparisons | Route semantics and local coverage checked; partial/unknown behavior passes |
| M3 — Decisions and scenarios | Persistent private brief/shortlist, scene restore, A/B scenarios, checklist and explicit export | Replay, isolation and scenario tests pass; retention behavior documented |
| M4 — Launch quality | Responsive/mobile/keyboard, language evaluation, performance, abuse limits, observability, regression and buyer sessions | Full-v1 acceptance set and usability findings resolved |

M1 is a useful private alpha, not the finished two-focus product. Full v1 includes M0–M4. Delivery dates require engineering estimates after portal repository review and provider/source decisions; this document does not invent a timeline or budget.

Implementation backlog is supplied separately with requirement and test references. Avoid treating static example dialogue or an LLM that cannot call data tools as completion of M1.

## 20. Decisions and prerequisites

| Item | Proposed decision / required resolution |
|---|---|
| Production home | Existing Laravel/Vue portal, as a Research module; current private Site serves as the parity reference |
| Model | Provider/model configurable; choose through tool-use, multilingual, grounding, latency and cost evaluation |
| AI credential | Must be obtained/configured through the approved runtime mechanism; not currently present in the app |
| Authentication | Reuse verified portal sessions, role conventions and owner/tenant policies; no duplicate identity system |
| Database/storage | MySQL 8; existing private filesystem for permitted large exports; verify Redis and dedicated Horizon capacity |
| Dependency baseline | Read Composer/npm lockfiles; resolve reported Inertia v2/v3 mismatch; verify Mapbox 3.7 compatibility, PHP extensions and exact package constraints |
| Portal repository/deployment | Source and staging topology are not yet supplied; PHP hosting, queue supervision, secrets and streaming must be verified before implementation/cutover |
| Source entitlement | Confirm allowed production use, attribution and refresh access before wider release |
| Area/metric semantics | Resolve built-up vs land and source PSF methodology; otherwise restrict comparable conclusions |
| Routing | Verify account access, Malaysian route quality, time-profile support, caching permissions and spend controls |
| Languages | English/BM/Mandarin full-v1 target; do not claim launch support before the eval suite passes |
| Public availability | No public launch implied by this spec; define audience/account policy separately |
| Feature limits | Initial caps/cohort defaults in this spec are explicit proposals to validate, not evidence thresholds or provider limits |

Data contracts, source fixtures and migration design can proceed now. Actual portal integration requires its repository and conventions; implement the Vue explorer parity phase there before adding the full AI workspace. It must not ship simulated AI answers or route times as real capabilities.

## 21. Sources and positioning

These are design/technical references, not claims that all advertised competitor capabilities were independently tested. No market-exclusivity claim is supported.

- [Redfin conversational search](https://www.redfin.com/news/redfin-debuts-conversational-search/): natural-language refinement integrated into search/map/listing surfaces.
- [Zillow AI mode](https://www.zillow.com/news/zillow-debuts-ai-mode/): conversational discovery and actions; the March 2026 announcement describes a limited beta.
- [PropertyGuru Malaysia assistant](https://www.propertyguru.com.my/property-guides/ai-property-assistant-80462): a local multilingual conversational competitor.
- [Mapbox Directions](https://docs.mapbox.com/api/navigation/directions/), [Matrix](https://docs.mapbox.com/api/navigation/matrix/) and [Isochrone](https://docs.mapbox.com/api/navigation/isochrone/): validate current profile, geographic, time and retention constraints at implementation.
- [WCAG 2.2](https://www.w3.org/TR/WCAG22/): accessibility target.
- [Inertia v3 upgrade guide](https://inertiajs.com/docs/v3/getting-started/upgrade-guide): align client/server adapters before depending on v3 APIs.
- [Laravel streamed responses](https://laravel.com/docs/13.x/responses#streamed-responses) and [Horizon](https://laravel.com/docs/13.x/horizon): stream transport and Redis-backed background processing.
- [Chart.js interactions](https://www.chartjs.org/docs/latest/configuration/interactions.html): chart hover and custom hit-testing foundations.
- Current application source, `public/data/meta.json`, exported JSON and the working PropertyLab SQLite database: baseline inventory and limitations.

## 22. Migration and integration gates

1. **Verify the portal baseline.** Inspect `composer.json`, `composer.lock`, frontend manifest/lockfile, Vite configuration, Inertia setup, user/tenant models, routes, permission conventions and deployment configuration. Reproduce its current build and relevant tests. Record the reported-versus-resolved package matrix; make any necessary adapter alignment a separately reviewable dependency change.
2. **Introduce the module behind a feature flag.** Use additive routes, permissions and schema migrations. Reuse existing login and navigation. Keep ordinary portal pages working independently of research imports, model availability and background jobs.
3. **Import and reconcile the data.** Preserve source snapshots and canonical IDs. Compare scheme/observation counts, withheld history links, null periods, source units, representative prices/PSF and deterministic calculations with the current evidence. Test MySQL strict behavior, collation, precision and query performance. Switch only the active dataset pointer after validation.
4. **Port the conventional explorer first.** Recreate the satellite 3D defaults, collapsed panels, multi-type checkboxes, palettes, rail/amenity categories, shortlist and price/PSF trends in Vue. Preserve 18,917-scheme clustering, ResizeObserver sizing and style restoration. Rebuild Recharts visuals with Chart.js and accessible tables. Mapbox version differences must be resolved with parity evidence.
5. **Implement real AI and daily-life services.** Follow M1–M3 with Laravel tools and the agreed immutable result/scene contracts. Prove authenticated streaming and cancellation through the actual proxy stack. Keep queued imports away from interactive capacity; never queue one job per token or hover.
6. **Verify integration and release privately.** Run the existing portal suite plus QA-01–25. Add integration checks for adapter navigation, per-record authorization, same-session concurrent cancellation, split stream frames, proxy buffering, duplicate job delivery, queued revision checks, source import rollback and a missing CDN/AI provider. Test actual Safari/Chrome/mobile behavior, 3D styles and Chart.js keyboard/touch equivalents before production rollout.
7. **Cut over with rollback available.** Enable the module for a small authorized cohort first. Keep the previous application release and dataset pointer available. Roll back through the feature flag/application release and data pointer; avoid destructive down-migrations once users have saved research. Preserve/export private user records and use forward-compatible schema changes. Retire the reference Site only through an explicit later release decision.

A backend-only Laravel API could serve the current React UI, but that would leave a second frontend stack to integrate later. The chosen specification targets Vue/Inertia now because the user’s destination portal already uses them. The user experience, analytical definitions and data can carry over; the application framework and chart components require deliberate implementation work. No conversion or deployment is represented as completed by this document revision.
