mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-01 12:23:50 +00:00
A house for sale, an apartment to rent, a car. One document per listing, on the seller's own origin, so a directory reads the seller's file instead of licensing somebody else's database or scraping a marketplace that forbids it. **The name `OpenRental` was the obvious one and it is taken.** It already means renting OpenAgent and OpenSwarm members through CoinPay, with a published schema, SDK, validators and fixtures. Its own document records surviving one collision already: it was OpenFleet until 2026-09-13. Reusing the name for housing would have broken a shipped 0.1 spec, so the family is OpenListing instead. Two axes rather than a spec per combination. A house for sale and a house to rent are the same house; only the offer differs. A house for sale and a car for sale are the same offer; only the subject differs. Writing OpenHouseForSale, OpenHouseForRent, OpenCarForSale and the rest produces one spec per cell of a grid, each repeating most of the others. So `offer.type` carries sale/rent/lease/auction/free/wanted and `subject.type` carries property/vehicle, and a subject profile adds only the fields peculiar to its subject. A reader that knows OpenListing but not a profile still reads price, location, offer and dates correctly. `openhouse` is not used as a name: in real estate an open house is a viewing event, not a kind of listing. An actual one is an entry in `showings`. The problem section is grounded rather than asserted: RESO tracks 484 separate MLSs as of August 2026, down from nearly twice that in 2015; each does publish a RESO Web API feed, and each requires a real estate licence, a signed per-MLS data agreement and vendor credentials. Four hundred and eighty-four negotiations for one national view, not redistributable. Vehicle parts are the same shape with different letters (ACES/VCdb, TecDoc). Decisions worth naming. Prices are decimal STRINGS, because 2450.10 is not representable in binary floating point and a listing is a price. Absent means unstated, never zero -- with `bedrooms: 0` called out in the property profile as the one place where zero genuinely differs and a studio is not an unstated bedroom count. `location.precision` lets a seller publish a postal code rather than a street for an occupied home, and forbids a reader from drawing a pin on a building anyway. A closed listing SHOULD stay served for 90 days with `closed_at`, because a directory that learns from a 404 cannot tell "sold" from "outage" -- the failure that makes every coupon site a graveyard. The vehicle profile treats a VIN as a claim rather than a proof, points readers at NHTSA vPIC (free, keyless, public domain) for decoding rather than making sellers restate what the VIN encodes, and puts parts fitment explicitly out of scope: that is ACES/VCdb or TecDoc, and a public spec cannot restate licensed data. Registered as one parent and two blocks under it, using the registry's existing `parent` field the way OpenServer's blocks already do. Flat slugs rather than nested paths because /docs/[slug] is a single dynamic segment, not a catch-all -- a nested slug would not have routed at all. nichedb.dev is named as the reference directory. 50 contract tests pass including spec-discovery and page-metadata. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
78 lines
4.3 KiB
Markdown
78 lines
4.3 KiB
Markdown
# OpenProperty
|
|
|
|
The property profile of [OpenListing](/docs/openlisting). It adds one key, `subject.property`, describing a house, an apartment, a room, a plot of land or a commercial unit. Everything else — the offer, the price, the location, the media, the provenance — is the parent specification and is not restated here.
|
|
|
|
Status: **0.1 draft**, alongside the parent.
|
|
|
|
Slug: `openproperty`
|
|
|
|
## When this profile applies
|
|
|
|
When `subject.type` is `"property"`. A reader that does not know this profile still reads the whole listing correctly; it simply does not know how many bedrooms the thing has.
|
|
|
|
## `subject.property`
|
|
|
|
```json
|
|
"subject": {
|
|
"type": "property",
|
|
"title": "Two-bedroom flat on Elm Street",
|
|
"property": {
|
|
"kind": "apartment",
|
|
"bedrooms": 2,
|
|
"bathrooms": 1.5,
|
|
"floor_area": { "value": 780, "unit": "sqft" },
|
|
"lot_area": { "value": 0.12, "unit": "acre" },
|
|
"year_built": 1974,
|
|
"floors": 1,
|
|
"floor": 3,
|
|
"parking": { "spaces": 1, "kind": "street" },
|
|
"furnished": "unfurnished",
|
|
"heating": "gas",
|
|
"pets": "cats-only",
|
|
"features": ["balcony", "dishwasher"],
|
|
"energy": { "rating": "C", "scheme": "EPC" }
|
|
}
|
|
}
|
|
```
|
|
|
|
### `kind`
|
|
|
|
`house`, `apartment`, `condo`, `townhouse`, `room`, `land`, `commercial`, `parking`, `storage`, `other`.
|
|
|
|
`room` is a room within a dwelling let separately, which is a different thing from a one-bedroom apartment and is routinely mislabelled as one. `land` carries `lot_area` and usually no `floor_area`.
|
|
|
|
### Fields
|
|
|
|
| Field | Notes |
|
|
| --- | --- |
|
|
| `bedrooms` | An integer. A studio is `0`, not absent — this is the one place absent and zero genuinely differ and the difference matters. |
|
|
| `bathrooms` | A number, because half-baths are real and `1.5` is the ordinary way to write one. |
|
|
| `floor_area` | `value` + `unit` (`sqft`, `sqm`). Internal area. |
|
|
| `lot_area` | `value` + `unit` (`sqft`, `sqm`, `acre`, `hectare`). |
|
|
| `year_built` | Four-digit year. |
|
|
| `floors` | How many storeys the dwelling has. |
|
|
| `floor` | Which storey it is on. Distinct from `floors`, and confusing them is the commonest error in property data. Ground floor is `0` in the UK sense and `1` in the US sense, so a publisher SHOULD also set `floor_scheme` to `uk` or `us` when it sets `floor`. |
|
|
| `parking` | `spaces` and `kind` (`garage`, `driveway`, `street`, `permit`, `none`). |
|
|
| `furnished` | `furnished`, `part-furnished`, `unfurnished`. |
|
|
| `heating` | Free text; no controlled vocabulary, because national ones disagree. |
|
|
| `pets` | `allowed`, `none`, `cats-only`, `dogs-only`, `ask`. |
|
|
| `accessibility` | `step_free`, `lift`, `wet_room` and similar, as a list. |
|
|
| `features` | Free-text list. Deliberately unconstrained: this is where everything a controlled vocabulary would lose goes. |
|
|
| `energy` | `rating` and `scheme` (`EPC`, `HES`, `NABERS`). The scheme is required alongside the rating, because a bare "C" means nothing without it. |
|
|
|
|
All are optional. Absent means unstated, exactly as in the parent, with `bedrooms: 0` noted above as the meaningful exception.
|
|
|
|
## Tenure
|
|
|
|
For a sale, `subject.property.tenure` MAY be `freehold`, `leasehold`, `commonhold`, `share-of-freehold` or `other`, with `lease_years_remaining` where it applies. A leasehold with eighty years left is a materially different asset from the same flat freehold, and a listing that omits this is omitting the thing a buyer most needs.
|
|
|
|
## What this profile does not do
|
|
|
|
- **No valuation.** A listing says the asking price. What the thing is worth is somebody else's claim, and [OpenOntology](/docs/openontology) is where a claim with a source belongs.
|
|
- **No condition or survey.** "Needs work" is `description` text, not a field, because every attempt to enumerate condition ends up either useless or misleading.
|
|
- **No floorplan geometry.** A floorplan is media with `kind: "floorplan"`. Describing rooms as geometry is a different specification and a much larger one.
|
|
- **No address normalisation.** The parent's `location` carries what the seller stated, at the precision the seller chose. Normalising it is a consumer's job.
|
|
|
|
## Market context is not a listing
|
|
|
|
Aggregate market data — median sale price for a ZIP, days on market, inventory — is **not** an OpenListing document and MUST NOT be published as one. It describes a market, not a thing on offer, and it has no `offer` and no seller. Directories that carry both keep them apart.
|