@roastbook/open-coffee-schema 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,577 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schema.roastbook.coffee/coffee-places/coffee-places.schema.v0.4.0.json",
4
+ "title": "Open Coffee places dataset",
5
+ "description": "One file per city listing cafés, coffee bars and other places that serve coffee. Part of Roastbook Open Coffee.",
6
+ "type": "object",
7
+ "required": [
8
+ "$schema",
9
+ "meta",
10
+ "places"
11
+ ],
12
+ "additionalProperties": false,
13
+ "properties": {
14
+ "$schema": {
15
+ "description": "Canonical URL of the schema version this file follows.",
16
+ "type": "string",
17
+ "pattern": "^https://schema\\.roastbook\\.coffee/coffee-places/coffee-places\\.schema\\.v[0-9]+\\.[0-9]+\\.[0-9]+\\.json$"
18
+ },
19
+ "meta": {
20
+ "$ref": "#/$defs/meta"
21
+ },
22
+ "places": {
23
+ "type": "array",
24
+ "items": {
25
+ "$ref": "#/$defs/place"
26
+ }
27
+ }
28
+ },
29
+ "$defs": {
30
+ "meta": {
31
+ "title": "File metadata",
32
+ "type": "object",
33
+ "allOf": [
34
+ {
35
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/dataset_meta"
36
+ }
37
+ ],
38
+ "required": [
39
+ "country_code",
40
+ "city"
41
+ ],
42
+ "properties": {
43
+ "country_code": {
44
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/country_code"
45
+ },
46
+ "city": {
47
+ "description": "City slug, matching the file name.",
48
+ "type": "string",
49
+ "pattern": "^[a-z0-9-]+$"
50
+ },
51
+ "city_name": {
52
+ "type": "string",
53
+ "description": "City name in the local language."
54
+ },
55
+ "licence": true
56
+ },
57
+ "unevaluatedProperties": false
58
+ },
59
+ "tier": {
60
+ "title": "Specialty tier",
61
+ "description": "A: coffee is the core product; the place roasts its own beans or serves named specialty roasters and usually offers filter coffee. B: food or something else leads, but the place serves a specialty roaster. none: not a specialty place (kept in the dataset). unknown: not yet assessed.",
62
+ "enum": [
63
+ "A",
64
+ "B",
65
+ "none",
66
+ "unknown"
67
+ ]
68
+ },
69
+ "serving": {
70
+ "title": "Serving",
71
+ "description": "Coffee preparation offered. true: offered; false: confirmed not offered; absent: unknown.",
72
+ "type": "object",
73
+ "additionalProperties": false,
74
+ "properties": {
75
+ "espresso": {
76
+ "type": "boolean",
77
+ "description": "Espresso and espresso-based drinks."
78
+ },
79
+ "filter": {
80
+ "type": "boolean",
81
+ "description": "Any filter coffee."
82
+ },
83
+ "pour_over": {
84
+ "type": "boolean",
85
+ "description": "Hand-brewed filter (V60, Kalita, Chemex and similar)."
86
+ },
87
+ "batch_brew": {
88
+ "type": "boolean",
89
+ "description": "Filter coffee from a batch brewer."
90
+ },
91
+ "cold_brew": {
92
+ "type": "boolean",
93
+ "description": "Coffee brewed with cold water over many hours. true: offered; false: confirmed not offered; absent: unknown."
94
+ },
95
+ "decaf": {
96
+ "type": "boolean",
97
+ "description": "Decaffeinated coffee. true: offered; false: confirmed not offered; absent: unknown."
98
+ },
99
+ "plant_milk": {
100
+ "type": "boolean",
101
+ "description": "At least one plant-based milk."
102
+ }
103
+ },
104
+ "allOf": [
105
+ {
106
+ "if": {
107
+ "properties": {
108
+ "pour_over": {
109
+ "const": true
110
+ }
111
+ },
112
+ "required": [
113
+ "pour_over"
114
+ ]
115
+ },
116
+ "then": {
117
+ "properties": {
118
+ "filter": {
119
+ "const": true
120
+ }
121
+ }
122
+ }
123
+ }
124
+ ]
125
+ },
126
+ "amenities": {
127
+ "title": "Amenities",
128
+ "description": "true: available; false: confirmed not available; absent: unknown.",
129
+ "type": "object",
130
+ "additionalProperties": false,
131
+ "properties": {
132
+ "wifi": {
133
+ "type": "boolean",
134
+ "description": "Wi-Fi for guests. true: available; false: confirmed not available; absent: unknown."
135
+ },
136
+ "laptop_friendly": {
137
+ "type": "boolean",
138
+ "description": "Laptops are welcome (no laptop ban, seats with sockets or tables)."
139
+ },
140
+ "kids_friendly": {
141
+ "type": "boolean",
142
+ "description": "Suitable for children, for example with high chairs, space for prams or a kids' menu. true: yes; false: confirmed not suitable; absent: unknown."
143
+ },
144
+ "vegan_options": {
145
+ "type": "boolean",
146
+ "description": "Vegan food beyond plant milk."
147
+ },
148
+ "dogs_allowed": {
149
+ "type": "boolean",
150
+ "description": "Dogs may come inside. true: allowed; false: confirmed not allowed; absent: unknown."
151
+ },
152
+ "outdoor_seating": {
153
+ "type": "boolean",
154
+ "description": "Seats outside, such as a terrace or tables on the pavement. true: available; false: confirmed none; absent: unknown."
155
+ },
156
+ "card_payment": {
157
+ "type": "boolean",
158
+ "description": "Payment by debit or credit card. true: accepted; false: confirmed not accepted; absent: unknown."
159
+ },
160
+ "cash_only": {
161
+ "type": "boolean",
162
+ "description": "Only cash is accepted. true: cash only; false: other payment methods are accepted; absent: unknown."
163
+ },
164
+ "wheelchair_accessible": {
165
+ "type": "boolean",
166
+ "description": "Step-free entrance."
167
+ }
168
+ },
169
+ "allOf": [
170
+ {
171
+ "if": {
172
+ "properties": {
173
+ "cash_only": {
174
+ "const": true
175
+ }
176
+ },
177
+ "required": [
178
+ "cash_only"
179
+ ]
180
+ },
181
+ "then": {
182
+ "properties": {
183
+ "card_payment": {
184
+ "const": false
185
+ }
186
+ }
187
+ }
188
+ }
189
+ ]
190
+ },
191
+ "food": {
192
+ "title": "Food",
193
+ "type": "object",
194
+ "additionalProperties": false,
195
+ "properties": {
196
+ "focus": {
197
+ "description": "coffee_led: coffee is the main product; food_led: food or a restaurant concept leads; balanced: both matter equally.",
198
+ "enum": [
199
+ "coffee_led",
200
+ "food_led",
201
+ "balanced"
202
+ ]
203
+ },
204
+ "breakfast": {
205
+ "type": "boolean",
206
+ "description": "Breakfast is served. true: served; false: confirmed not served; absent: unknown."
207
+ },
208
+ "lunch": {
209
+ "type": "boolean",
210
+ "description": "Lunch dishes are served. true: served; false: confirmed not served; absent: unknown."
211
+ },
212
+ "cakes_baked_in_house": {
213
+ "type": "boolean",
214
+ "description": "Cakes or pastries are baked by the place itself. true: baked in house; false: confirmed bought in; absent: unknown."
215
+ }
216
+ }
217
+ },
218
+ "links": {
219
+ "title": "Links",
220
+ "type": "object",
221
+ "additionalProperties": false,
222
+ "properties": {
223
+ "website": {
224
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/url"
225
+ },
226
+ "instagram": {
227
+ "type": "string",
228
+ "pattern": "^https://(www\\.)?instagram\\.com/(?!(?:p|reel|reels|explore|stories|tv|accounts)(?:[/?]|$))[A-Za-z0-9_.]+/?(\\?[^#\\s]*)?$",
229
+ "description": "Address of the place's own Instagram profile. Write it as https://www.instagram.com/<handle>/ with the handle in lower case and no query string, for example https://www.instagram.com/example.coffee/. Posts, reels, stories and other non-profile pages are rejected."
230
+ },
231
+ "facebook": {
232
+ "type": "string",
233
+ "pattern": "^https://(www\\.|m\\.)?facebook\\.com/.+",
234
+ "description": "Address of the place's own Facebook page."
235
+ }
236
+ }
237
+ },
238
+ "xref": {
239
+ "title": "Identifiers in other datasets",
240
+ "description": "Crosswalk to other datasets. google_place_id is the only Google data we may store.",
241
+ "type": "object",
242
+ "additionalProperties": false,
243
+ "properties": {
244
+ "overture": {
245
+ "type": "string",
246
+ "minLength": 1,
247
+ "description": "Overture Maps place GERS id."
248
+ },
249
+ "foursquare": {
250
+ "type": "string",
251
+ "minLength": 1,
252
+ "description": "Foursquare Open Source Places fsq_place_id."
253
+ },
254
+ "wikidata": {
255
+ "type": "string",
256
+ "pattern": "^Q[1-9][0-9]*$",
257
+ "description": "Wikidata item id of the place, for example Q42."
258
+ },
259
+ "google_place_id": {
260
+ "type": "string",
261
+ "minLength": 1,
262
+ "description": "Google Maps place id, stored only so a known place can be matched. No other Google data is stored."
263
+ }
264
+ }
265
+ },
266
+ "roaster_ref": {
267
+ "title": "Roaster served",
268
+ "type": "object",
269
+ "required": [
270
+ "name"
271
+ ],
272
+ "additionalProperties": false,
273
+ "properties": {
274
+ "name": {
275
+ "type": "string",
276
+ "minLength": 1,
277
+ "description": "Name of the roastery whose coffee is served."
278
+ },
279
+ "roaster_id": {
280
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/id",
281
+ "description": "Reserved for the future Open Coffee roasters dataset."
282
+ },
283
+ "url": {
284
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/url"
285
+ },
286
+ "own": {
287
+ "type": "boolean",
288
+ "description": "The place roasts this coffee itself."
289
+ }
290
+ }
291
+ },
292
+ "award": {
293
+ "title": "Award",
294
+ "type": "object",
295
+ "required": [
296
+ "name"
297
+ ],
298
+ "additionalProperties": false,
299
+ "properties": {
300
+ "name": {
301
+ "type": "string",
302
+ "minLength": 1,
303
+ "description": "Name of the award or ranking, for example a national barista championship or a city guide's list."
304
+ },
305
+ "year": {
306
+ "type": "integer",
307
+ "minimum": 1900,
308
+ "maximum": 2100,
309
+ "description": "Year the award was given, for example 2025."
310
+ },
311
+ "url": {
312
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/url"
313
+ }
314
+ }
315
+ },
316
+ "price": {
317
+ "title": "Observed price",
318
+ "type": "object",
319
+ "required": [
320
+ "amount",
321
+ "currency",
322
+ "observed_on"
323
+ ],
324
+ "additionalProperties": false,
325
+ "properties": {
326
+ "amount": {
327
+ "type": "number",
328
+ "exclusiveMinimum": 0,
329
+ "description": "Price paid for one standard drink, in the currency given by currency, for example 3.9."
330
+ },
331
+ "currency": {
332
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/currency"
333
+ },
334
+ "observed_on": {
335
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/date"
336
+ },
337
+ "source_url": {
338
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/url"
339
+ }
340
+ }
341
+ },
342
+ "operator": {
343
+ "title": "Operator",
344
+ "description": "The legal entity running the place, taken from its Impressum or legal notice. Only companies and other legal entities; never the name of a natural person (sole traders are omitted).",
345
+ "type": "object",
346
+ "required": [
347
+ "name"
348
+ ],
349
+ "additionalProperties": false,
350
+ "properties": {
351
+ "name": {
352
+ "type": "string",
353
+ "minLength": 1,
354
+ "description": "Registered name of the company that runs the place, as written in its legal notice."
355
+ },
356
+ "legal_form": {
357
+ "type": "string",
358
+ "description": "For example GmbH, UG (haftungsbeschränkt), KG, e.K."
359
+ },
360
+ "register_id": {
361
+ "type": "string",
362
+ "description": "Commercial register number, for example HRB 123456 (Amtsgericht München)."
363
+ }
364
+ }
365
+ },
366
+ "good_for": {
367
+ "title": "Good for",
368
+ "enum": [
369
+ "working",
370
+ "meetings",
371
+ "quick_espresso",
372
+ "takeaway",
373
+ "brunch",
374
+ "groups",
375
+ "reading",
376
+ "date",
377
+ "coffee_nerds",
378
+ "families"
379
+ ]
380
+ },
381
+ "place": {
382
+ "title": "Coffee place",
383
+ "type": "object",
384
+ "required": [
385
+ "id",
386
+ "type",
387
+ "name",
388
+ "status",
389
+ "location",
390
+ "address",
391
+ "specialty_tier",
392
+ "checked_at"
393
+ ],
394
+ "additionalProperties": false,
395
+ "properties": {
396
+ "id": {
397
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/id"
398
+ },
399
+ "type": {
400
+ "description": "Kind of place, as a schema.org type. Every place is a CafeOrCoffeeShop for now; JSON-LD processors read it as the record's @type.",
401
+ "enum": [
402
+ "CafeOrCoffeeShop"
403
+ ]
404
+ },
405
+ "name": {
406
+ "type": "string",
407
+ "minLength": 1,
408
+ "maxLength": 200,
409
+ "description": "The name the place uses on its sign or website."
410
+ },
411
+ "status": {
412
+ "description": "open; temporarily_closed (renovation, holiday longer than a month); closed (permanently); unverified (found but not yet confirmed to exist).",
413
+ "enum": [
414
+ "open",
415
+ "temporarily_closed",
416
+ "closed",
417
+ "unverified"
418
+ ]
419
+ },
420
+ "merged_into": {
421
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/id",
422
+ "description": "Set when this record turned out to be a duplicate: the id of the record that is kept. The record stays as a tombstone so its id is never reused or lost; its status must not be open."
423
+ },
424
+ "opened_on": {
425
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/date"
426
+ },
427
+ "closed_on": {
428
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/date"
429
+ },
430
+ "last_verified_at": {
431
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/datetime",
432
+ "description": "When a primary source last confirmed that the place exists with this status."
433
+ },
434
+ "checked_at": {
435
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/datetime",
436
+ "description": "When the record as a whole was last reviewed or enriched."
437
+ },
438
+ "address": {
439
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/address"
440
+ },
441
+ "location": {
442
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/location"
443
+ },
444
+ "opening_hours": {
445
+ "description": "Opening hours in OpenStreetMap opening_hours syntax (used as a format only; no OSM data), for example \"Mo-Fr 08:00-18:00; Sa,Su 09:00-17:00\".",
446
+ "type": "string",
447
+ "minLength": 2,
448
+ "maxLength": 255
449
+ },
450
+ "description": {
451
+ "description": "Short English description written only from the facts stored in this record. Never copied from the place's own texts.",
452
+ "type": "string",
453
+ "minLength": 20,
454
+ "maxLength": 600
455
+ },
456
+ "specialty_tier": {
457
+ "$ref": "#/$defs/tier"
458
+ },
459
+ "roasts_own_beans": {
460
+ "type": "boolean",
461
+ "description": "Whether the place roasts coffee itself. true: roasts its own beans; false: confirmed not roasting; absent: unknown."
462
+ },
463
+ "roasters_served": {
464
+ "type": "array",
465
+ "items": {
466
+ "$ref": "#/$defs/roaster_ref"
467
+ }
468
+ },
469
+ "serving": {
470
+ "$ref": "#/$defs/serving"
471
+ },
472
+ "amenities": {
473
+ "$ref": "#/$defs/amenities"
474
+ },
475
+ "food": {
476
+ "$ref": "#/$defs/food"
477
+ },
478
+ "sells_beans": {
479
+ "type": "boolean",
480
+ "description": "Whether whole beans or ground coffee are sold to take home. true: sold; false: confirmed not sold; absent: unknown."
481
+ },
482
+ "sells_equipment": {
483
+ "type": "boolean",
484
+ "description": "Whether brewing equipment such as grinders, drippers or kettles is sold. true: sold; false: confirmed not sold; absent: unknown."
485
+ },
486
+ "offers_courses": {
487
+ "type": "boolean",
488
+ "description": "Barista courses, brewing classes or public cuppings."
489
+ },
490
+ "reusable_cup_systems": {
491
+ "description": "Deposit or reusable cup schemes accepted, for example recup, vytal, own_cup.",
492
+ "type": "array",
493
+ "uniqueItems": true,
494
+ "items": {
495
+ "type": "string",
496
+ "pattern": "^[a-z0-9_-]+$"
497
+ }
498
+ },
499
+ "good_for": {
500
+ "type": "array",
501
+ "uniqueItems": true,
502
+ "items": {
503
+ "$ref": "#/$defs/good_for"
504
+ }
505
+ },
506
+ "awards": {
507
+ "type": "array",
508
+ "items": {
509
+ "$ref": "#/$defs/award"
510
+ }
511
+ },
512
+ "prices": {
513
+ "type": "object",
514
+ "additionalProperties": false,
515
+ "properties": {
516
+ "cappuccino": {
517
+ "$ref": "#/$defs/price"
518
+ }
519
+ },
520
+ "description": "Prices we observed, each with the date and page where we saw it. Currently only the cappuccino price, as a comparable reference."
521
+ },
522
+ "operator": {
523
+ "$ref": "#/$defs/operator"
524
+ },
525
+ "links": {
526
+ "$ref": "#/$defs/links"
527
+ },
528
+ "image": {
529
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/image"
530
+ },
531
+ "xref": {
532
+ "$ref": "#/$defs/xref"
533
+ },
534
+ "external_ratings": {
535
+ "description": "References to rating pages elsewhere. Rating values are never stored.",
536
+ "type": "array",
537
+ "items": {
538
+ "$ref": "https://schema.roastbook.coffee/common/common.schema.v0.4.0.json#/$defs/external_ref"
539
+ }
540
+ }
541
+ },
542
+ "allOf": [
543
+ {
544
+ "if": {
545
+ "properties": {
546
+ "status": {
547
+ "const": "closed"
548
+ }
549
+ },
550
+ "required": [
551
+ "status"
552
+ ]
553
+ },
554
+ "else": {
555
+ "properties": {
556
+ "closed_on": false
557
+ }
558
+ }
559
+ }
560
+ ],
561
+ "dependentSchemas": {
562
+ "merged_into": {
563
+ "properties": {
564
+ "status": {
565
+ "enum": [
566
+ "temporarily_closed",
567
+ "closed",
568
+ "unverified"
569
+ ]
570
+ }
571
+ }
572
+ }
573
+ },
574
+ "description": "One place that serves coffee. In JSON-LD it is a schema.org CafeOrCoffeeShop whose IRI is https://data.roastbook.coffee/places/<id>."
575
+ }
576
+ }
577
+ }