{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://lingerinlondon.github.io/schema/place.schema.json",
  "title": "Central London Third Places — place properties, v0",
  "description": "Single source of truth for fields. The validator, the contribution forms and the site's filters all derive from this file; nothing may hand-maintain a second list of fields elsewhere.",

  "$comment": "A RULE FOR ADDING FIELDS, learned twice the hard way: describe the room, not who is in it today. If the honest answer changes with the hour, the day or the season, the field is describing an event rather than a place, and events are not places. Both `conversation` and `activity` began with a third value about the crowd — whether other people were talking, whether other people were working — and both were dropped for this reason. `activity` was then dropped entirely. Across a real corpus every entry took the same value, and not by accident: the kind of place that clears all four gates is flexible by nature, so listening to music, reading a book, sketching, typing, reading the paper and watching the world go by are all simply fine. There was no distinction left for the field to draw. The rare exception — a church, a memorial garden — is better served by the one `why` sentence, which can say so more precisely than an enum, and in the place people actually read. A space that is a quiet study room by day and sociable in the evening because events are held in it is a quiet study room with an events programme. PROVISIONAL, and written before the survey has happened. There is no `verified` field on purpose: every entry comes from someone who has sat in the place and says so, whether that is the curator or a contributor filling in the form, and a flag that is true of everything carries no information. What varies is WHEN, which is last_checked. Four fields are GATES: they describe what a place must be to belong here at all, so their enums are pinned to the passing values and a place that fails one is not a badly-described place, it is a place that belongs somewhere else. The gates are payment_to_enter, payment_to_sit, time_pressure and seating. They are kept as fields rather than dropped so that anyone who downloads this data knows what a row guarantees without reading the README, and so that loosening a gate later is a one-line enum change rather than a re-collection. Everything else is description, and only descriptive fields carry x-filter — filtering on a gate would return the entire corpus.",

  "$defs": {
    "common": {
      "type": "object",
      "unevaluatedProperties": false,
      "properties": {
        "id": {
          "type": "string",
          "description": "The OSM element the place sits in or on. Unique together with spot, not on its own: one library can hold two entries if they are genuinely different places to sit.",
          "pattern": "^(osm:(node|way|relation)/[0-9]+|gers:[0-9A-Za-z_-]{8,})$",
          "examples": ["osm:way/123456", "osm:node/98765"]
        },
        "spot": {
          "type": ["string", "null"],
          "maxLength": 60,
          "description": "Which part of that element, when the whole of it is not the point — 'top floor, by the window', 'the benches under the canopy'. Null means the place as a whole. This names a location and nothing else: it is not a second why sentence, and it carries no judgement.",
          "examples": ["top floor, by the window", "the benches under the canopy"]
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120,
          "description": "The name the place is known by. Not a description."
        },

        "payment_to_enter": {
          "type": ["string", "null"],
          "enum": ["none", "optional", null],
          "description": "GATE. Nothing may be required to get through the door. A donation box passes: it is an option to support the place, not a price.",
          "x-gate": {
            "confirm": "There is nothing you have to pay to get in.",
            "excluded": "required",
            "excluded_reason": "This project only lists places you can walk into without paying. A ticket price is not a mark against a place — it just puts it outside what this list is for."
          }
        },
        "payment_to_sit": {
          "type": ["string", "null"],
          "enum": ["none", "optional", null],
          "description": "GATE. Nothing may be required to occupy a seat. 'optional' is the case where a cafe exists but sitting without buying is genuinely accepted — rarer than it sounds, and worth being honest about.",
          "x-gate": {
            "confirm": "You can sit down without buying anything.",
            "excluded": "required",
            "excluded_reason": "If the seating is for customers, the place asks something of you before you can stay, which is the thing this list exists to find the absence of."
          }
        },
        "time_pressure": {
          "type": ["string", "null"],
          "enum": ["none", null],
          "description": "GATE, and the strictest one. No stated limit, and nobody moves you on. 'implied' pressure — the feeling that you have been there a while — fails, because staying a while is the entire premise. Loosen by adding 'implied' to this enum if the survey says that is too strict.",
          "x-gate": {
            "confirm": "You could stay for hours without anyone minding.",
            "excluded": "implied, enforced",
            "excluded_reason": "A place you feel you should leave is not a place you can stay in, even when nobody says so out loud."
          }
        },
        "seating": {
          "type": ["string", "null"],
          "enum": ["bench", "upright", "soft", null],
          "description": "GATE and description at once. There must be somewhere to sit, so 'none' is not a value here; which kind it is remains useful. The three values are one ladder, about how it feels to sit there for a couple of hours. They were once fixed/movable/soft, which mixed two axes — mobility and comfort — and left an ordinary upright chair at a desk with nowhere to go, despite being the commonest seating in any working space. Mobility was dropped rather than added to: it was justified as letting a group form, and nobody has ever chosen where to sit because the chairs move. NOT A CENSUS: a large building has all three kinds, and answering 'all of the above' tells a reader nothing. Name the seating you would point someone at — this is a curated list and the `why` sentence is already a recommendation rather than a description, so this should be too. That also settles what to do when seats and tables pair up differently in different corners: you are not claiming every chair meets every table, you are naming the corner worth going to. This field describes the seat and not what is in front of it; whether there is a usable surface is the separate `table` field.",
          "x-gate": {
            "confirm": "There is somewhere to sit down.",
            "excluded": "nowhere to sit",
            "excluded_reason": "Somewhere you can only stand is not somewhere you can stay a while, however welcoming it is otherwise."
          },
          "x-filter": {
            "label": "Seating",
            "values": {
              "bench": { "label": "benches", "help": "hard seating, often backless or bolted in place" },
              "upright": { "label": "upright chairs", "help": "ordinary chairs at desks or tables — comfortable enough for a couple of hours" },
              "soft": { "label": "soft seating", "help": "sofas and armchairs" }
            }
          }
        },

        "conversation": {
          "type": ["string", "null"],
          "enum": ["discouraged", "possible", null],
          "description": "Whether talking is permitted. Not whether it happens. There was a third value, `expected`, for somewhere sitting in silence would be conspicuous. Nowhere turned out to qualify: every candidate was either an event held in a room rather than the room itself, or somewhere you can perfectly well sit alone with a cup of tea and be left be. It was kept for a while on the grounds of being a useful warning, which only works if the thing warned of exists.",
          "x-filter": {
            "label": "Conversation",
            "values": {
              "discouraged": { "label": "a quiet room", "help": "people keep their voices down, or do not speak at all. Come here to not be talked to." },
              "possible": { "label": "talking is fine", "help": "you can hold a conversation without being out of place, whether or not anyone actually is" }
            }
          }
        },
        "setting": {
          "type": ["string", "null"],
          "enum": ["indoor", "outdoor", "covered", null],
          "description": "Shelter, which decides whether a place works in bad weather.",
          "x-filter": {
            "label": "Setting",
            "values": {
              "indoor": { "label": "indoor", "help": "inside a building" },
              "outdoor": { "label": "outdoor", "help": "open air, so weather decides whether it works" },
              "covered": { "label": "covered", "help": "outdoors but under cover — an arcade, a colonnade, a canopy" }
            }
          }
        },

        "table": {
          "type": ["string", "null"],
          "enum": ["none", "low_only", "a_few", "plenty", null],
          "description": "Whether there is a surface you could actually use, and how much of one. Height matters more than count, which an earlier none/some/most version could not say: a low coffee table is not a smaller quantity of table, it is a different kind, fine for a cup or a book and ergonomically wrong for a laptop. The values run as a ladder from nothing to plenty, and everything from `a_few` upwards means tables at a height you can work at. Separate from `seating` because the two vary independently — an armchair with a side table and an upright chair at a desk are different propositions and neither is described by the seat alone.",
          "x-filter": {
            "label": "Tables",
            "values": {
              "none": { "label": "no tables", "help": "nothing to put anything on but your lap" },
              "low_only": { "label": "low tables only", "help": "coffee-table height: fine for a cup or a book, awkward for a laptop" },
              "a_few": { "label": "a few tables you can work at", "help": "tables at a height you can work at, but not many of them" },
              "plenty": { "label": "plenty of tables you can work at", "help": "most seats come with a table you can work at" }
            }
          }
        },

        "support_options": {
          "type": ["array", "null"],
          "description": "Ways to give the place money or time IF YOU WANT TO. Every one of these must be optional: a place where you have to buy something to stay fails the payment gates and does not belong here at all. DISPLAY ONLY — never scored, never sorted on, never used to order anything. A place with none is not worse than a place with four.",
          "uniqueItems": true,
          "items": {
            "type": "string",
            "enum": ["cafe_or_bar", "shop", "donation", "membership", "ticketed_events", "volunteering"]
          },
          "x-filter": { "label": "Ways to support", "values": {} }
        },

        "why": {
          "type": "string",
          "minLength": 1,
          "maxLength": 350,
          "description": "Why this place is here. The only free text in the schema, capped so it stays a recommendation rather than a review."
        },

        "last_checked": {
          "type": "string",
          "format": "date",
          "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
          "description": "The date someone was last in this place. Required: every entry comes from someone who has been, so there is always a date, and a place nobody can date is a place nobody can vouch for. Decay logic is off and an old date never fails validation — that would erode a sparse list faster than it fills. Contributions older than a year are turned away at the form instead."
        },
        "suggested_by": {
          "type": ["string", "null"],
          "maxLength": 60,
          "description": "How a contributor wishes to be credited, if they want to be. A name or handle, nothing else. Null for places found without a suggestion."
        }
      },
      "required": ["id", "name", "why", "last_checked"]
    },

    "place": { "$ref": "#/$defs/common" }
  },

  "$ref": "#/$defs/place"
}
