logicsrc/docs/openproperty.md
Anthony Ettinger 32bbdeda95
OpenListing 0.1: one file a seller serves about a thing on offer (#213)
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>
2026-09-25 03:14:19 -07:00

4.3 KiB

OpenProperty

The property profile of 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

"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 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.