coreprinciple Claude Opus 4.8 commited on
Commit
330c7c6
Β·
1 Parent(s): f72500e

Submission: finalize field notes + wire all judge links

Browse files

- FIELD_NOTES.md rewritten to current build (MiniCPM5-1B, 9 cities,
grounding gazetteer, save-taste + Maps/GPX export); numbers verified
against data (30,357 POIs, 77,454 nodes / 221,688 edges)
- README links block: add GitHub source, demo video, social posts, HF blog

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (2) hide show
  1. FIELD_NOTES.md +54 -30
  2. README.md +5 -6
FIELD_NOTES.md CHANGED
@@ -1,69 +1,93 @@
1
  # Field Notes: Building WanderLust
2
 
3
- > **Draft** for the Build Small Hackathon's `achievement:fieldnotes` (Track 1 β€” Backyard AI).
 
 
 
 
 
 
4
 
5
  ## The inversion
6
 
7
- Every navigation app I've used solves the same problem: get me there in the minimum time. DiscoverRoute starts from the opposite premise. If you're walking across Paris and you'd happily spend fifteen extra minutes, that surplus is a *budget* β€” and the interesting question is what to spend it on.
8
 
9
- So the app takes a start, a destination, a free-text vibe, and an "adventurousness" level, and returns a walkable or bikeable route that deliberately detours past places matching your taste β€” within a hard travel-time budget β€” plus a narrated itinerary explaining why each place is on the path. The route never exceeds `(1 + budget) Γ—` the direct time, and budget 0 simply gives you the plain route. It runs on small (≀32B) open models and OpenStreetMap data, single city: Paris.
10
 
11
- ## Walking skeleton first, AI last
 
 
12
 
13
- The strategy I committed to in the build log's first line: *walking skeleton first; scariest plumbing early; AI added only after a manual-weight router already works.* Each brick had a definition-of-done and a test, and wasn't left until green.
14
 
15
- That meant the first four bricks contained no AI at all.
16
 
17
- **Brick 0** was the boring-but-scary plumbing: download the Paris walk network via OSMnx into a graph of **77,454 nodes and 221,688 edges** (a 90 MB GraphML file), geocode inputs, run Dijkstra, render the polyline on a Folium map inside a Gradio shell. Seven tests, including a sanity check that République→Luxembourg is at least 2 km and that bike beats walk (travel time is derived per mode from one graph: walk 4.8 km/h, bike 15 km/h).
18
 
19
- **Brick 1** built the POI layer: **30,589 Paris POIs across 17 curated categories**, cached to a 1.2 MB parquet file, each with greenness/quietness priors and a *confidence* score derived from OSM tag richness.
20
 
21
- **Bricks 2–3** were the heart of the system, and they're classical algorithms: a budgeted **submodular orienteering solver**. The key modeling insight is diversity by design. A naive scorer that just sums point values will happily route you past five cafΓ©s, because cafΓ©s are everywhere and score fine individually. The submodular reward applies *diminishing returns per category*: the first cafΓ© is worth full value, the second much less, the third almost nothing. So a park + a viewpoint + a bookshop beats five cafΓ©s β€” not because of a hand-tuned penalty, but because the objective itself says variety is worth more than repetition. The solver is a better-of-two greedy (picking by raw gain *and* by reward-per-added-time, keeping the better result), tested against a known-optimal synthetic instance, with an explicit diversity-beats-repetition test.
22
 
23
  Only once that manual-weight router produced real discovery routes on a real map did any model enter the picture.
24
 
25
  ## What the small models actually do
26
 
27
- Two models, both deliberately small, each load-bearing in exactly one place:
28
 
29
- - **`BAAI/bge-small-en-v1.5` (~33M parameters, CPU-only)** turns your free-text vibe into category affinities. Each of the 17 categories has a short gloss; the vibe is embedded and matched by cosine similarity to those glosses, min-max rescaled into a usable weight range. "Quiet green wander" and a contrasting vibe produce measurably different waypoint sets on the same A/B pair β€” that's an end-to-end test, not a hope.
30
- - **`Qwen/Qwen3.5-9B` (Apache 2.0, thinking mode off)** is an *optional* narration enhancer. The default itinerary text comes from a deterministic template that is grounded by construction; the LLM only rewrites it, on GPU (ZeroGPU on the Space), and is gated by a verifier (more on that below). bf16 at ~18 GB it sits comfortably on ZeroGPU. I considered the whole ≀32B ladder β€” Qwen3.5-4B (lighter), 9B (chosen), 27B (needs quantization on 40 GB) β€” and excluded Qwen3.5-35B-A3B because 35B breaks the hackathon's 32B cap.
31
 
32
- Small-is-the-point here, not a constraint to grumble about. Routing is pure classical algorithms; the model is load-bearing only in interpretation and narration. The skeleton runs CPU-only and offline with the rule-based fallback β€” which means the template-narration mode runs on nothing but the 33M embedder. Your taste profile never needs to leave your device (it's persisted per-device via browser state, no accounts), and the whole interpretation stack fits on a laptop.
33
 
34
  ## The zero-hallucination gate
35
 
36
- LLMs narrating a route is exactly the place hallucination hurts most: the model will cheerfully invent a charming bistro that doesn't exist. So narration sits behind a **fail-closed grounding verifier**: it extracts capitalized place-name spans from the generated text (handling multi-word names and French "de la" chains) and passes only if *every* mention maps to an allowed name β€” the route's waypoints, the start/end, or "Paris". Any violation, and the system silently falls back to the deterministic template. The release-gate test plants a hallucinated "Eiffel Tower" in narration and verifies the gate catches it; the final end-to-end test asserts the shipped narration is grounded.
37
 
38
- Then the adversarial review pass found a real hole in it. The old check accepted an allowed name being a *substring* of a longer mention β€” so if "CafΓ© de la Paix" was a real waypoint, the invented "CafΓ© de la Paix sur Seine" sailed through. The fix inverts the containment: strip common words from a mention, then require the *core* to be a substring of an allowed name, not the reverse. The same pass also fixed mention extraction, which had been gluing "RΓ©publique, Paris and Jardin…" into one span by treating "and"/"et" as name-internal. Both attack shapes β€” appended qualifier and shortened reference β€” now have regression tests.
 
 
 
 
 
 
 
39
 
40
  ## War stories
41
 
42
- A few things that broke, and what fixing them taught me:
 
 
 
 
 
 
 
 
 
43
 
44
- **Overpass timed out.** The combined POI query for all of Paris was too much for the Overpass API. Fix: fetch one tag key at a time with a 300s timeout β€” amenity (77k raw), shop (29k), tourism (7.5k), leisure (6k), historic (2.5k), natural β€” then filter down to the 30,589 classified POIs.
45
 
46
- **Per-pair routing was the first latency wall.** Computing travel times between candidate POIs pair-by-pair put warm requests at **8–14 seconds**. Replacing it with **SciPy multi-source Dijkstra** β€” one C call over a cached CSR adjacency matrix β€” brought warm per-request latency to **~1 second**.
47
 
48
- **The 635ms hiding in a loop.** During the adversarial review, the performance reviewer reported alarming numbers β€” which turned out to be a red herring: their machine was thrashing. Re-measured on a clean machine, the suggested fix (porting route-stitching to CSR) was needless β€” stitch was only 59ms. The real bottleneck was `build_matrix` at **~635ms, recomputed three times** inside the alternatives loop. Hoisting the corridor selection and matrix out of the loop (compute once, reuse) dropped `n_alternatives=3` from **~2.1s to ~1.3s** β€” the same cost as a single route. An STRtree took corridor selection from 87ms to ~5ms for good measure. Lesson: measure on a quiet machine before believing a profile, and look for repeated work before clever work.
49
 
50
- **The review fixed product lies, too.** The alternatives label showed *total* minutes where it claimed to show detour minutes; the vibe's budget hint was displayed but silently discarded; off-domain vibes ("tax deadline") manufactured false preferences until a minimum-similarity-span guard mapped them to neutral. And error handling got hardened so disconnected nodes or a corrupt parquet degrade to the plain route instead of a traceback. After the pass: 42 tests passing.
51
 
52
- ## The design handoff port
53
 
54
- The default Gradio look got replaced wholesale from a design handoff: a low-poly "clay sticker" aesthetic β€” cream paper background, cobalt/grass/coral/sun palette, Fredoka display type. The port landed as a theme plus CSS (sticker cards, a coral CTA that visibly depresses, springy sliders, a framed map window with a titlebar), a results bounce-in observer, and a friendly empty/no-detour state.
 
 
55
 
56
- The fiddliest part: the map is a Folium iframe, and **an iframe can't be animated from the parent page**. So the route draw-on and the staggered POI marker pops are injected as JavaScript *inside* the iframe's own document, keyed off CSS class names attached to the polylines and markers at render time. Reduced-motion and AA focus rings are respected throughout.
57
 
58
  ## Honest limitations
59
 
60
- - **Bike uses the walk graph.** One mode-agnostic graph, with per-mode speeds. Routing bikes on the pedestrian network is a documented v1 approximation; a separate bike graph is on the deferred list.
61
- - **Confidence is OSM tag richness, nothing more.** A well-tagged tourist trap looks "confident"; a beloved hole-in-the-wall with two tags looks risky. The adventurousness slider leans into this honestly β€” it both fades the confidence penalty and *boosts* under-documented POIs β€” but the signal itself is just tag count.
62
- - **Paris only.** The graph and POI table are built offline for one city. The pipeline generalizes; the data doesn't, yet.
63
- - Cold boot pays ~8.6s to load the 90 MB graph (plus 0.2s for the CSR cache); a faster serialization is a known, deferred debt.
64
 
65
- ## What's next
66
 
67
- The P2 list, in rough order: live navigation (today it's plan-then-walk), external enrichment beyond OSM tags so "confidence" can mean more than tag richness, and place embeddings so taste matching can operate on places themselves rather than on 17 categories. Nearer-term housekeeping from the log: a real bike graph, faster graph serialization, and per-place removal in the profile UI.
68
 
69
- The submission itself is the last brick: push to a Space under the hackathon org, record the demo, write the post β€” and decide whether DiscoverRoute is Backyard AI (a real problem, really used) or a resident of the Thousand Token Wood.
 
1
  # Field Notes: Building WanderLust
2
 
3
+ *A walk from point A to B, routed through the places you'll actually love powered by a 1B model and a city's worth of OpenStreetMap, running entirely inside one Hugging Face Space.*
4
+
5
+ **Track:** Backyard AI Β· **Live Space:** https://huggingface.co/spaces/build-small-hackathon/WanderLust Β· **Demo video:** https://www.youtube.com/watch?v=55Ofnt6Hhv4 Β· **Source:** https://github.com/tristanleduc/wanderlust Β· **Published on the HF blog:** https://huggingface.co/blog/coreprinciple/wanderlust
6
+
7
+ This is an app built by myself and my teammate (https://huggingface.co/JohnDoe6) as a submission for the Huggingface Hackathon.
8
+
9
+ ---
10
 
11
  ## The inversion
12
 
13
+ Every navigation app we've used solves the same problem: get me there in the minimum time. WanderLust starts from the opposite premise. We're cyclists, and crossing an unfamiliar city we kept hitting the same frustration which is that the map only ever knows the *fastest* line between two points, but the whole joy of exploring is the bookshop, the quiet square, the viewpoint you'd never have found on the direct route. If you'd happily spend fifteen extra minutes, that surplus is a *budget* β€” and the interesting question is what to spend it on!
14
 
15
+ So WanderLust takes a start, a destination, a free-text *vibe*, and an *adventurousness* level, and returns a walkable or bikeable route that deliberately detours past places matching your taste within a hard travel-time budget plus a narrated itinerary explaining why each place is on your path. The route never exceeds `(1 + budget) Γ—` the direct time; budget 0 simply gives you the plain route. It runs on a 1B model and OpenStreetMap data, across nine cities, with no cloud API calls at request time.
16
 
17
+ ## Why the AI is load-bearing
18
+
19
+ Routing a path isnt actually the hard problem here, it's the gap between *how people describe what they want* and *what a router can optimize*. Someone types "a slow Sunday-morning kind of walk" or "bookshops and quiet streets," and something has to turn that unbounded human mood into concrete, scored weights across seventeen place categories plus quiet/green/lively modifiers. No lookup table or keyword list maps open-ended language onto a route. The model *is* the bridge and then it writes the itinerary that explains, in your own words, why each chosen stop matches what you asked for.
20
 
21
+ **Small is the point, not a constraint to grumble about.** Routing stays pure classical algorithm; the model is load-bearing in exactly two places β€”> interpretation and narration β€” and both fit a 1B. Your taste profile never has to leave the Space; there are no accounts and no external inference API.
22
 
23
+ ## Walking skeleton first, AI last
24
 
25
+ The strategy we committed to on the build log's first line: *walking skeleton first, scariest plumbing early, AI added only after a manual-weight router already works.* Each brick had a definition-of-done and a test, and wasn't left until green. The first bricks contained no AI at all.
26
 
27
+ The boring-but-scary plumbing came first: download the Paris walk network via OSMnx into a graph of **77,454 nodes and 221,688 edges** (a 90 MB GraphML), geocode inputs, run Dijkstra, render the polyline on a map. Then the POI layer: **30,357 Paris POIs across 17 curated categories**, each with greenness/quietness priors and a *confidence* score derived from OSM tag richness.
28
 
29
+ The heart of the system is classical, and it's where the engineering went. We model the detour as the **Orienteering Problem** β€” prize-collecting with fixed endpoints, NP-hard in general and solve it with a budgeted greedy heuristic over a **submodular reward**. The key modeling insight is *diversity by design*. A naive scorer that just sums point values will happily route you past five cafΓ©s, because cafΓ©s are everywhere and each scores fine on its own. The submodular reward applies diminishing returns *per category*: the first cafΓ© is worth full value, the second much less, the third almost nothing. So a park + a viewpoint + a bookshop beats five cafΓ©s not from a hand-tuned penalty, but because the objective itself says variety is worth more than repetition. The solver runs two greedy passes: by raw marginal gain *and* by reward-per-added-time and keeps the higher-reward feasible result; a single ratio pass alone gets trapped hoarding cheap duplicates. It's tested against a known-optimal synthetic instance, with an explicit diversity-beats-repetition test.
30
 
31
  Only once that manual-weight router produced real discovery routes on a real map did any model enter the picture.
32
 
33
  ## What the small models actually do
34
 
35
+ Two models, both deliberately small, each load-bearing in exactly one place β€” with graceful fallbacks so the app *never* breaks:
36
 
37
+ - **`openbmb/MiniCPM5-1B`** (1B parameters, standard `LlamaForCausalLM`, no custom kernels) does two jobs from one set of weights. **Call 1** turns your free-text vibe into a scored JSON of category weights. **Call 2** writes the first-person itinerary narration. It runs **inside the Space on ZeroGPU** via `@spaces.GPU`, weights pulled straight from the Hub β€” no external inference API, nothing leaves the Space.
38
+ - **`BAAI/bge-small-en-v1.5`** (~33M parameters, CPU-only) is the fallback interpreter: when no GPU is allocated, the vibe is embedded and matched by cosine similarity against a gloss for each of the 17 categories.
39
 
40
+ The interpreter is a **tiered dispatcher** β€” MiniCPM5-1B β†’ bge-small embeddings β†’ a model-free keyword net β†’ neutral β€” and every tier returns the same `{category: affinity}` shape, so the routing engine is oblivious to which one ran. The model is what makes the experience feel like it read your mind; the fallbacks are what keep it standing when ZeroGPU is busy. Narration has the same property: a deterministic, grounded-by-construction template is always available, and the LLM only *rewrites* it.
41
 
42
  ## The zero-hallucination gate
43
 
44
+ Letting an LLM narrate a route is exactly the place a hallucination hurts most: the model will cheerfully invent a charming bistro that doesn't exist, and a user might walk there. So narration sits behind a **fail-closed grounding verifier**. It extracts capitalized place-name spans from the generated text β€” handling multi-word names and French "de la" chains, and treating "and"/"et" as list-joiners that *separate* names rather than glue them β€” and the text passes only if *every* mention maps to an allowed name: the route's actual waypoints, the start/end, or a curated per-city gazetteer of districts, rivers, and landmarks. Any violation, and the system silently falls back to the deterministic template. The release-gate test plants a hallucinated "Eiffel Tower" and verifies the gate catches it.
45
 
46
+ The interesting part was getting the gate *right*, and it took a real adversarial pass to find the holes:
47
+
48
+ - **The substring trap.** The first version accepted an allowed name being a substring of a longer mention β€” so if "CafΓ© de la Paix" was a real waypoint, the invented "CafΓ© de la Paix sur Seine" sailed straight through. The fix inverts the containment: strip common words to a mention's *core*, then require the core to be a substring of an allowed name, not the reverse. Appending an invented qualifier to a real name is the precise hallucination vector, and it's now closed with a regression test.
49
+ - **The gate vs. good prose.** Fail-closed cuts both ways: a *too*-strict gate rejected genuinely grounded, evocative writing β€” a guide naming the Marais, the Seine, or the Latin Quarter β€” and silently dropped the app back to template narration every time. We watched this happen in the live inference traces. The fix was the per-city gazetteer plus a vocabulary of era/architecture adjectives and generic geographic nouns ("river," "quarter," "bridge"), so the model can write like an actual city guide and scene-set, while a distinctive token like "Eiffel" still has to match a real allowed name. The guarantee holds; the prose breathes.
50
+
51
+ ## Off the grid: nine cities, no cloud at request time
52
+
53
+ Paris ships full-city. The other eight β€” London, Barcelona, Berlin, New York, San Francisco, Tokyo, Mumbai, Shanghai β€” are baked offline as bounded walkable cores and hosted as an **open Hub dataset** (`build-small-hackathon/discoverroute-cities`), pulled and **pre-warmed into memory at boot** so the first user to pick a city waits zero seconds. With `DISCOVERROUTE_OFFLINE=1` (the deployed config) there are **no cloud API calls at request time**: geocoding resolves against the local POI index, routing runs on the cached graph, and the model runs in-Space. The whole interpretation-and-narration stack β€” and the template path entirely fits on a laptop.
54
 
55
  ## War stories
56
 
57
+ A few things broke, and what fixing them taught us:
58
+
59
+ - **Overpass timed out.** The combined POI query for all of Paris was too much for the Overpass API. Fix: fetch one tag key at a time with a long timeout β€” amenity, shop, tourism, leisure, historic, natural β€” then classify down to the 30,357 POIs.
60
+ - **Per-pair routing was the first latency wall.** Computing travel times between candidate POIs pair-by-pair put warm requests at **8–14 seconds**. Replacing it with **SciPy multi-source Dijkstra** β€” one C call over a cached CSR adjacency matrix β€” brought warm per-request latency to **~1 second**.
61
+ - **The 635 ms hiding in a loop.** During the adversarial review, a performance profile flagged alarming numbers that turned out to be a thrashing machine β€” a red herring. Re-measured on a quiet machine, the real bottleneck was `build_matrix` recomputed **three times** inside the alternatives loop. Hoisting it out (compute once, reuse) dropped three alternative routes from **~2.1 s to ~1.3 s** β€” the cost of a single route. Lesson: measure on a quiet machine before believing a profile, and look for repeated work before clever work.
62
+ - **ZeroGPU quota is per-call duration.** The live Space started falling back to the template, and the traces told us why: every inference threw `"180s requested vs. 170s left."` ZeroGPU *reserves* the requested `duration` seconds per call, so asking for a fat 120 s slice drains a day's allowance in a dozen requests. A 1B model loading from cache and generating ≀480 tokens on an A10G finishes well inside 45 s, so we ask for that β€” roughly 3Γ— more calls per day. Reading the actual production traces, not guessing, is what found it.
63
+
64
+ These last two only surfaced because we logged every inference call to a trace dataset (`build-small-hackathon/discoverroute-traces`) β€” which turned debugging the live Space from guesswork into reading rows.
65
+
66
+ ## The custom frontend
67
 
68
+ The default Gradio look got replaced wholesale. WanderLust runs on a hand-built HTML/CSS/JS app-shell served by Gradio's `gr.Server` FastAPI backend and called from the browser via `@gradio/client` β€” **no default Gradio components**: a custom map window with its own titlebar, custom controls, a live-map loader. The fiddliest part: the map is an iframe, and an iframe can't be animated from the parent page. So the route draw-on and the staggered marker pops are injected as JavaScript *inside* the iframe's own document, keyed off CSS classes attached to the polylines and markers at render time. Reduced-motion preferences and focus rings are respected throughout.
69
 
70
+ ## Save your taste, and take the route with you
71
 
72
+ Two product details we care about, because a discovery route is only worth planning if you can actually *keep* it.
73
 
74
+ **Your taste, remembered.** You can save a standing taste profile β€” free-text preferences plus the categories of places you've saved β€” and WanderLust blends that with each trip's mood instead of asking you to re-describe yourself every time. A handful of saved places nudges the route toward what you like, with a saturating boost so a few saves matter but a hundred don't swamp the trip's actual vibe. True to the "small, local-first" spirit, there are **no accounts**: the profile lives on your device, never on a server.
75
 
76
+ **Your route, taken with you.** A plan you can't follow is just a pretty picture, so every result carries a *Take it with you* row built entirely client-side from the plan payload β€” no extra round-trip:
77
 
78
+ - **Open in Google Maps** β€” a turn-by-turn deep link with your detour stops as waypoints (in walking or biking mode to match your choice; Google caps a single link at 9 stops).
79
+ - **Open in Apple Maps** β€” a walking-directions deep link from start to destination.
80
+ - **Download GPX** β€” the full track plus *every* waypoint, named, ready for a watch, a bike computer, or any mapping app.
81
 
82
+ The GPX is the lossless option, and the UI says so honestly: when a route has more stops than a Google Maps link can hold, it tells you the GPX keeps all of them. You plan the interesting way here, then walk or ride it with whatever navigation you already trust.
83
 
84
  ## Honest limitations
85
 
86
+ - **The eight secondary cities are walkable cores, not whole metros.** Paris is the only full-city build; the rest are bounded around their centres. The pipeline generalizes; the offline data is sized to keep the Space lean.
87
+ - **The model path depends on a GPU slice being free.** When ZeroGPU is saturated, you get the embedding interpreter and the template narration β€” correct and grounded, just less personal. That's a deliberate floor, not a crash.
 
 
88
 
89
+ ## What we shipped
90
 
91
+ A discovery router where the AI is genuinely load-bearing but small enough to run in one Space; a classical orienteering core that earns *diversity* from its objective rather than a hack; a fail-closed grounding gate hardened by real adversarial review; nine cities offline; a custom frontend; and two open datasets β€” city cores and inference traces β€” left on the Hub for anyone to reuse.
92
 
93
+ The spend-your-extra-time-on-discovery idea turned out to need surprisingly little model and a surprising amount of careful plumbing. That felt like the right ratio.
README.md CHANGED
@@ -57,9 +57,10 @@ ground right under your wheels.
57
  ## πŸ”— Links (judges start here)
58
 
59
  - **πŸ—ΊοΈ Live Space:** https://huggingface.co/spaces/build-small-hackathon/WanderLust
60
- - **🎬 Demo video:** _TBA_
61
- - **πŸ“£ Social post:** _TBA_
62
- - **πŸ“ Field notes (HF blog):** _TBA_
 
63
  - **πŸ§‘β€πŸ€β€πŸ§‘ Team:**
64
  - **Ishrat Jahan Ananya** β€” [Hugging Face](https://huggingface.co/coreprinciple) Β· [GitHub](https://github.com/coreprinciple6) Β· [Website](https://coreprinciple.vercel.app/)
65
  - **Tristan Leduc** β€” [Hugging Face](https://huggingface.co/JohnDoe6) Β· [GitHub](https://github.com/tristanleduc) Β· [LinkedIn](https://www.linkedin.com/in/tristan-leduc-56491b188/)
@@ -119,7 +120,7 @@ the experience feel like it read your mind.)
119
  | **Off the Grid** (`offgrid`) | With `DISCOVERROUTE_OFFLINE=1`, **zero cloud APIs at request time** β€” every city is pre-baked + pre-warmed at boot, geocoding is local, and the 1B model runs in-Space on ZeroGPU. |
120
  | **Off-Brand** (`offbrand`) | A hand-built `gr.Server` HTML/CSS/JS app-shell β€” **zero default Gradio components**: custom map, custom controls, custom live-map loader. |
121
  | **Sharing is Caring** (`sharing`) | Two reusable artifacts shared publicly on the Hub: the city-cores dataset [`build-small-hackathon/discoverroute-cities`](https://huggingface.co/datasets/build-small-hackathon/discoverroute-cities) and the inference-trace dataset `build-small-hackathon/discoverroute-traces`. |
122
- | **Field Notes** (`fieldnotes`) | The end-to-end build story β€” decisions, dead ends, fixes β€” drafted in [`FIELD_NOTES.md`](FIELD_NOTES.md) and published as an **HF blog post** (link above). |
123
 
124
  **Bonus badges** (prize categories) we're going for:
125
 
@@ -129,8 +130,6 @@ the experience feel like it read your mind.)
129
  | **Tiny Titan** ($1,500) | **MiniCPM5-1B β€” 1B parameters** (well under the 4B cap), in-Space on ZeroGPU. |
130
  | **Best Agent** ($1,000) | Four-stage planning pipeline: vibe β†’ routing weights β†’ POI scoring β†’ orienteering solve β†’ grounded narration. |
131
 
132
- In reach: **Best Demo** ($1,000) once the demo video + social post land, and **Bonus Quest
133
- Champion** ($2,000) by stacking the above.
134
 
135
  > **Sponsor prize:** built on OpenBMB's **MiniCPM5-1B**, so eligible for **Best MiniCPM Build** (OpenBMB).
136
 
 
57
  ## πŸ”— Links (judges start here)
58
 
59
  - **πŸ—ΊοΈ Live Space:** https://huggingface.co/spaces/build-small-hackathon/WanderLust
60
+ - **πŸ’» Source code (GitHub):** https://github.com/tristanleduc/wanderlust
61
+ - **🎬 Demo video:** https://www.youtube.com/watch?v=55Ofnt6Hhv4
62
+ - **πŸ“£ Social post:** [LinkedIn](https://www.linkedin.com/posts/ishrat-jahan-ananya_build-small-hackathon-build-small-hackathon-activity-7472015876708548609-Ss8T) Β· [X/Twitter](https://x.com/coreprinciple_/status/2066248778416267553)
63
+ - **πŸ“ Field notes (HF blog):** https://huggingface.co/blog/coreprinciple/wanderlust
64
  - **πŸ§‘β€πŸ€β€πŸ§‘ Team:**
65
  - **Ishrat Jahan Ananya** β€” [Hugging Face](https://huggingface.co/coreprinciple) Β· [GitHub](https://github.com/coreprinciple6) Β· [Website](https://coreprinciple.vercel.app/)
66
  - **Tristan Leduc** β€” [Hugging Face](https://huggingface.co/JohnDoe6) Β· [GitHub](https://github.com/tristanleduc) Β· [LinkedIn](https://www.linkedin.com/in/tristan-leduc-56491b188/)
 
120
  | **Off the Grid** (`offgrid`) | With `DISCOVERROUTE_OFFLINE=1`, **zero cloud APIs at request time** β€” every city is pre-baked + pre-warmed at boot, geocoding is local, and the 1B model runs in-Space on ZeroGPU. |
121
  | **Off-Brand** (`offbrand`) | A hand-built `gr.Server` HTML/CSS/JS app-shell β€” **zero default Gradio components**: custom map, custom controls, custom live-map loader. |
122
  | **Sharing is Caring** (`sharing`) | Two reusable artifacts shared publicly on the Hub: the city-cores dataset [`build-small-hackathon/discoverroute-cities`](https://huggingface.co/datasets/build-small-hackathon/discoverroute-cities) and the inference-trace dataset `build-small-hackathon/discoverroute-traces`. |
123
+ | **Field Notes** (`fieldnotes`) | The end-to-end build story β€” decisions, dead ends, fixes β€” in [`FIELD_NOTES.md`](FIELD_NOTES.md) and published as an **[HF blog post](https://huggingface.co/blog/coreprinciple/wanderlust)**. |
124
 
125
  **Bonus badges** (prize categories) we're going for:
126
 
 
130
  | **Tiny Titan** ($1,500) | **MiniCPM5-1B β€” 1B parameters** (well under the 4B cap), in-Space on ZeroGPU. |
131
  | **Best Agent** ($1,000) | Four-stage planning pipeline: vibe β†’ routing weights β†’ POI scoring β†’ orienteering solve β†’ grounded narration. |
132
 
 
 
133
 
134
  > **Sponsor prize:** built on OpenBMB's **MiniCPM5-1B**, so eligible for **Best MiniCPM Build** (OpenBMB).
135