# OSASI mapping profile format

A mapping profile records, once, everything needed to translate one MLS
system's export into the standard schema. Profiles are JSON. They contain
**schema knowledge only** — column names, conventions, quirks — never sales
records.

## Example

```json
{
  "id": "rmls_portland_matrix",
  "name": "RMLS",
  "system": "Matrix export",
  "region": "Portland OR, USA",
  "schema_version": 1,
  "status": "new",
  "signature": ["MLS#", "Close Price", "Close Date", "Total Baths",
                "Total Upper Baths", "SF Up+Mn", "Neighborhood/Building"],
  "distinctive": ["MLS#", "SF Up+Mn", "Total Upper Baths"],
  "fields": {
    "ListingId":   { "from": "MLS#" },
    "ClosePrice":  { "from": "Close Price" },
    "CloseDate":   { "from": "Close Date", "format": "M/D/YYYY" },
    "LivingArea":  { "from": "Total SF" },
    "YearBuilt":   { "from": "Year Built" },
    "GarageSpaces":{ "from": "# Garage" },
    "BathroomsFull": { "from": "Total Baths", "transform": "bath_full_partial.full" },
    "BathroomsHalf": { "from": "Total Baths", "transform": "bath_full_partial.partial" },
    "x-cvalr:Neighbourhood": { "from": "Neighborhood/Building" }
  },
  "conventions": {
    "baths": {
      "notation": "full.partial",
      "meaning": "2.1 = 2 full + 1 partial",
      "evidence": "verified across 259 rows: .1 fractions occur throughout, .5 never"
    },
    "encoding": "windows-1252",
    "dates": "M/D/YYYY"
  },
  "known_issues": [
    { "type": "gap",   "note": "No original-list-price column in the export." },
    { "type": "quirk", "note": "Windows-1252 encoding; curly quotes in remarks break naive UTF-8 readers." }
  ]
}
```

## Field reference

| Key | Meaning |
|---|---|
| `id` | Stable slug. Never reuse or rename. |
| `signature` | Columns that characterise this export. Recognition = fraction of these present in a candidate header (case-insensitive). |
| `distinctive` | Signature columns rarely seen elsewhere. Require ≥2 hits before claiming a match. |
| `fields` | Map of **target → source**. Targets are RESO Data Dictionary names; appraisal extensions use the `x-cvalr:` prefix. `transform` names a documented value transformation. |
| `conventions` | Value-level facts a mapper must know: bath notation, date format, text encoding, unit of lot size. These come from inspecting values, not headers. |
| `known_issues` | Typed notes (`gap` / `quirk` / `watch` / `good`) shown to users at recognition time. |
| `status` | `requested` → `new` → `field` (field-tested) → `verified`. |

## Rules

1. **Targets are RESO names.** If RESO has a field for it, use RESO's name.
   Only genuinely uncovered appraisal concepts go in `x-cvalr:`.
2. **A decimal bath total is lossy.** Decompose baths only when the profile
   declares the convention (`full.partial`) or the feed carries the split
   natively. A total-only feed maps to `BathroomsTotalInteger` and the
   composition stays null — do not invent one.
3. **Conventions need evidence.** State how the convention was established
   (e.g. the `.5`-absence test across N rows) so the next person can re-check.
4. **Profiles are versioned by accretion.** Export templates drift; add
   alternate source names rather than deleting old ones.
