# PropertyLab research data

This module queries normalized MySQL research tables through `ResearchRepository`. The Advisor owns private conversations and stored immutable results. The repository exposes `toolDefinitions()`, `execute(name, arguments, context = [])`, `allSchemes()` and `capabilities()`. `execute` validates a bounded structured tool schema; it never accepts SQL, executable chart code or arbitrary provider URLs.

The map index preserves existing scheme IDs and normalizes labels to `id,name,cls,lat,lng,state,district,mukim,median,psf,n`. It is cached by immutable snapshot ID. Evidence results include source-qualified rows, typed metrics, claims, chart/series references, snapshot dates, eligibility counts, exclusions and warnings.

## Import

The converter reads the original SQLite archive in read-only mode using Python's standard library. PHP does not need SQLite support.

```sh
python3 scripts/propertylab/prepare_research_import.py \
  --sqlite /path/to/propertylab.sqlite \
  --output storage/app/propertylab-research-2026-09-07
php artisan propertylab:import-research \
  --source=storage/app/propertylab-research-2026-09-07 --activate
```

Run migrations and imports against the intended configured database. This development work used an isolated database. The importer verifies file checksums and counts, imports into an invisible staging snapshot in transactional chunks, and activates only a complete snapshot. Reruns preserve existing identities and resume an interrupted matching snapshot. A changed manifest cannot overwrite an existing snapshot identity. Omitting `--activate` leaves the current snapshot active.

Source record IDs, page hashes, page row positions, source URLs and archive SHA-256 are retained. Exact source-name matching gates sale links. Repeated-looking sale rows remain separate source observations; they are never counted as verified unique transactions. Canonical scheme hashes are collision-checked; renamed source schemes need an explicit reviewed crosswalk rather than silent reassignment. The neutral source archive remains the ultimate provenance record and is not modified.

## Evidence semantics

- Search and sale-cohort eligibility are evaluated before display pagination. Nearby price analysis searches the complete matching category/radius universe, independently of the map viewport or nearest twelve schemes.
- Source sale area basis is unknown. The imported archive therefore supports descriptive price and reported-PSF evidence, but cannot create a compatible asking-PSF benchmark until source area basis is verified. Buyer-entered area basis does not establish the source basis.
- Price and reported PSF remain independent. Missing, nonpositive or invalid monthly values stay null, and missing periods are inserted as gaps. Source `n` retains its unconfirmed-window meaning.
- Quartiles use linear interpolation at `(n-1)p`. Fewer than five compatible observations have no distribution band. No formal valuation, investment forecast, flood-depth model or live unit availability is inferred.
- Amenity proximity is straight-line distance from recorded scheme coordinates. Only rail stations marked open are included in proximity results. Missing place evidence stays unknown.

## Provider adapters

`RoutingAdapter` supports Mapbox walking/driving estimates from property to destination. Scheduled departure/arrival requests are explicitly unsupported; they do not silently become traffic-aware or timed estimates. An absent route/provider stays unknown and never becomes a fabricated journey.

`GeocodingAdapter` uses Mapbox v6 forward geocoding, country `my`, bounded results and optional state/district context. Candidates preserve provider IDs, accuracy and address context. Every candidate needs buyer confirmation before routing. It requests `permanent=true` because results are saved; provider refusal never falls back to storing temporary responses. See [Mapbox geocoding storage rules](https://docs.mapbox.com/api/search/geocoding/#storing-geocoding-results) and [Directions documentation](https://docs.mapbox.com/api/navigation/directions/).

Routing/geocoding use the configured PropertyLab token, falling back to `services.mapbox.token`. They are gated by `propertylab.routing.enabled`; geocoding optionally overrides that with `propertylab.geocoding.enabled`. Tokens never appear in result payloads. Automated adapter tests fake HTTP and prohibit unexpected network requests.
