{
"file_path": "/home/david/projects/play/richhall/README.md",
"offset": 60,
"limit": 30
}60 | `--sort` | `time` (default) or `distance` |
61 | `--limit` | max rows (0 = all) |
62
63 ### optional config
64
65 `~/.config/richhall/config.yml` can set defaults so you don't repeat flags:
66
67 ```yaml
68 festival: fringe
69 year: 2026
70 centre: "55.9497,-3.1875" # your preferred "middle of town"
71 # api-key: ${FRINGE_API_KEY}
72 # api-secret: ${FRINGE_API_SECRET}
73 ```
74
75 ## How it works
76
77 - **Auth**: each request is signed with HMAC-SHA1 over the path+query string
78 (including the key), per the Fringe API docs.
79 - **Storage**: SQLite holds one row per *performance* (the natural grain for
80 picking a specific showing), plus events and venues, and the raw JSON.
81 - **Distance**: haversine from a configurable centre using each venue's
82 latitude/longitude from the API — no geocoding needed.
83
84 ## Development
85
86 ```bash
87 make dev # uv sync
88 make test # pytest
89 make lint # ruff check + format checkI'll add a dedicated note about the database location. The README is the natural place a user looks:
### optional config `~/.config/richhall/config.yml` can set defaults so you don't repeat flags:
### where the data is stored The downloaded cache is a single SQLite file at: ``` ~/.local/share/richhall/fringe.db ``` `fetch` prints this path when it finishes, and `fringe info` shows it any time. To keep it elsewhere, set `db-path` in the config file below (or delete the file to start fresh — a re-`fetch` recreates it). ### optional config `~/.config/richhall/config.yml` can set defaults so you don't repeat flags:
The file /home/david/projects/play/richhall/README.md has been updated successfully. (file state is current in your context — no need to Read it back)
Let me also surface db-path in the config example the note refers to, so it's discoverable:
festival: fringe
year: 2026
centre: "55.9497,-3.1875" # your preferred "middle of town"
# api-key: ${FRINGE_API_KEY}
# api-secret: ${FRINGE_API_SECRET}festival: fringe
year: 2026
centre: "55.9497,-3.1875" # your preferred "middle of town"
# db-path: ~/.local/share/richhall/fringe.db # override the cache location
# api-key: ${FRINGE_API_KEY}
# api-secret: ${FRINGE_API_SECRET}The file /home/david/projects/play/richhall/README.md has been updated successfully. (file state is current in your context — no need to Read it back)
Done. Added a "where the data is stored" note to the README:
~/.local/share/richhall/fringe.dbfetch prints the path on completion and fringe info shows it any timedb-path in config) or reset it (delete + re-fetch)db-path key to the config example so it's discoverableNo code changes needed — the runtime already surfaces the path in both fetch and info output, so this just documents it clearly. Nothing committed.
Does the ingested data include the country where the artist is from? I'm interested in being able to filter on country such as finding acts from the United States.
Yes — it's already ingested. The events schema has a country field (artist/company origin), and the fetch command stores it in the events.country column. Let me confirm what's actually in your cache and how usable it is for a "United States" filter:
python3 - <<'PY'
import sqlite3, os
db = os.path.expanduser("~/.local/share/richhall/fringe.db")
c = sqlite3.connect(db); c.row_factory = sqlite3.Row
total = c.execute("SELECT COUNT(*) FROM events").fetchone()[0]
nonnull = c.execute("SELECT COUNT(*) FROM events WHERE country IS NOT NULL AND country != ''").fetchone()[0]
print(f"events: {total} with country set: {nonnull}")
print("\ntop countries:")
for r in c.execute("SELECT country, COUNT(*) n FROM events WHERE country != '' GROUP BY country ORDER BY n DESC LIMIT 15"):
print(f" {r['n']:5d} {r['country']!r}")
print("\nUSA-looking values:")
for r in c.execute("SELECT DISTINCT country FROM events WHERE lower(country) LIKE '%united states%' OR lower(country) LIKE '%usa%' OR country='US'"):
print(f" {r['country']!r}")
PYevents: 4113 with country set: 4113
top countries:
1697 'UNITED KINGDOM - ENGLAND'
1057 'UNITED KINGDOM - SCOTLAND'
572 'UNITED STATES'
181 'AUSTRALIA'
101 'IRELAND'
60 'CANADA'
44 'UNITED KINGDOM - WALES'
33 'CHINA'
32 'NEW ZEALAND'
29 'GERMANY'
27 'ITALY'
24 'JAPAN'
22 'UNITED KINGDOM - NORTHERN IRELAND'
17 'SPAIN'
15 'NETHERLANDS'
USA-looking values:
'UNITED STATES'
'UNITED STATES MINOR OUTLYING ISLANDS'It's all there and fully populated — every one of the 4,113 events has a country, and 572 are UNITED STATES. Two things worth knowing about the values:
UNITED STATES (there's also a tiny UNITED STATES MINOR OUTLYING ISLANDS).UNITED KINGDOM - ENGLAND, ... - SCOTLAND, etc. — so a substring match is the right approach (united kingdom catches all four home nations; england catches just England).I stored the field at fetch time but never wired it into search. Let me add a --country filter (substring, case-insensitive, matching your existing --genre/--artist style) and show the country in the output.
" e.title AS title, e.artist AS artist, e.genre AS genre,",
" e.venue_name AS venue_name, e.latitude AS lat, e.longitude AS lon,",
" e.url AS url", " e.title AS title, e.artist AS artist, e.genre AS genre,",
" e.country AS country,",
" e.venue_name AS venue_name, e.latitude AS lat, e.longitude AS lon,",
" e.url AS url",The file /home/david/projects/play/richhall/richhall/subcommands/search.py has been updated successfully. (file state is current in your context — no need to Read it back)
if args["artist"]:
sql.append("AND lower(e.artist) LIKE ?")
params.append(f"%{args['artist'].lower()}%")
return " ".join(sql), params if args["artist"]:
sql.append("AND lower(e.artist) LIKE ?")
params.append(f"%{args['artist'].lower()}%")
if args["country"]:
sql.append("AND lower(e.country) LIKE ?")
params.append(f"%{args['country'].lower()}%")
return " ".join(sql), paramsThe file /home/david/projects/play/richhall/richhall/subcommands/search.py has been updated successfully. (file state is current in your context — no need to Read it back)
output(f"{when} {dist_s} {r['title']}")
meta = " · ".join(x for x in [r["venue_name"], r["genre"], dur, price] if x) output(f"{when} {dist_s} {r['title']}")
meta = " · ".join(x for x in [r["venue_name"], r["genre"], r["country"], dur, price] if x)The file /home/david/projects/play/richhall/richhall/subcommands/search.py has been updated successfully. (file state is current in your context — no need to Read it back)
@click.option("--genre", metavar="<TEXT>", help="Genre substring, e.g. comedy")
@click.option("--title", metavar="<TEXT>", help="Title substring")
@click.option("--artist", metavar="<TEXT>", help="Artist substring")
@click.option("--sort", type=click.Choice(["time", "distance"]), default="time", help="Sort order")
@click.option("--limit", type=int, default=50, help="Max rows (0 = all)")
@click.pass_context
def command(context, day, date_from, date_to, after, before, max_distance, centre, genre, title, artist, sort, limit):@click.option("--genre", metavar="<TEXT>", help="Genre substring, e.g. comedy")
@click.option("--title", metavar="<TEXT>", help="Title substring")
@click.option("--artist", metavar="<TEXT>", help="Artist substring")
@click.option("--country", metavar="<TEXT>", help="Artist country substring, e.g. 'united states'")
@click.option("--sort", type=click.Choice(["time", "distance"]), default="time", help="Sort order")
@click.option("--limit", type=int, default=50, help="Max rows (0 = all)")
@click.pass_context
def command(context, day, date_from, date_to, after, before, max_distance, centre, genre, title, artist, country, sort, limit):The file /home/david/projects/play/richhall/richhall/subcommands/search.py has been updated successfully. (file state is current in your context — no need to Read it back)
dict(day=day, date_from=date_from, date_to=date_to, genre=genre, title=title, artist=artist)
dict(day=day, date_from=date_from, date_to=date_to, genre=genre, title=title, artist=artist, country=country)
The file /home/david/projects/play/richhall/richhall/subcommands/search.py has been updated successfully. (file state is current in your context — no need to Read it back)
uv run ruff check richhall/ 2>&1 | tail -2 echo "=== US acts, 15 Aug, before 8pm, within 2km ===" uv run fringe search --day 2026-08-15 --before 20:00 --max-distance 2km --country "united states" --sort distance --limit 6 2>&1
All checks passed!
=== US acts, 15 Aug, before 8pm, within 2km ===
Sat 15 Aug 16:30 0.03km Aditya Mayya Pretends to Be Sad
Just The Tonic at The Hive · Comedy · UNITED STATES · 55m · £9
Sat 15 Aug 18:25 0.04km Holy Shit Improv
Monkey Barrel Comedy · Comedy · UNITED STATES · 60m · £13.50 (12.50)
Sat 15 Aug 19:50 0.04km Geoffrey Asmus: Patriautism
Monkey Barrel Comedy · Comedy · UNITED STATES · 60m · £14
Sat 15 Aug 09:25 0.04km Places I Go from My Bed
theSpace @ Niddry St · Children's Shows · UNITED STATES · 40m · £10 (7)
Sat 15 Aug 15:15 0.04km Imagine
theSpace @ Niddry St · Musicals and Opera · UNITED STATES · 50m · £12 (8)
Sat 15 Aug 16:05 0.04km atticwife
theSpace @ Niddry St · Theatre · UNITED STATES · 50m · £12
6 of 275 matching performances (showing first 6)Works — 275 US performances match that day/time/distance window, country shown inline. Let me document the new flag in the README:
| `--genre` | substring match, e.g. `comedy` | | `--title`/`--artist` | substring match |
| `--genre` | substring match, e.g. `comedy` | | `--title`/`--artist` | substring match | | `--country` | artist origin substring, e.g. `united states` |
The file /home/david/projects/play/richhall/README.md has been updated successfully. (file state is current in your context — no need to Read it back)
| `--sort` | `time` (default) or `distance` | | `--limit` | max rows (0 = all) |
| `--sort` | `time` (default) or `distance` | | `--limit` | max rows (0 = all) | `--country` matches on the artist's country of origin, which the API stores in upper case and subdivides the UK (`UNITED KINGDOM - ENGLAND`, `... - SCOTLAND`, …). Because the match is a case-insensitive substring: `--country "united states"` finds US acts, `--country "united kingdom"` finds all four UK nations, and `--country scotland` narrows to Scottish acts.
The file /home/david/projects/play/richhall/README.md has been updated successfully. (file state is current in your context — no need to Read it back)
Done. The country data was already ingested (every event has it — 572 US acts in your cache); it just wasn't queryable. Now it is.
What I added:
- --country filter on search — case-insensitive substring match on the artist's country of origin, matching your existing --genre/--artist style.
- Country shown in results — it now appears in each result's detail line.
- README — new --country row in the filter table plus a note on the value format (upper-case; UK subdivided by nation), so --country "united states" finds US acts and --country "united kingdom" catches all four home nations.
Verified: lint clean, and fringe search --day 2026-08-15 --before 20:00 --max-distance 2km --country "united states" returns 275 matching performances with the country displayed.
One thing to note: this is a pure query-layer change — no re-fetch needed, since the data was already stored. Nothing committed.
Want me to add a quick unit test for the country filter while I'm here, or leave it?