Split a trip into its two economies, so travel stops being a 60% slab
ci / lint-test (push) Successful in 48s
ci / lint-test (push) Successful in 48s
travel dominated every trip page and said nothing. The tempting fix is a finer travel taxonomy, which needs a hand-maintained merchant list — the trap #19 already describes — and it is also the wrong diagnosis. travel is the only category that spans both phases of a trip. Every other one is 100% on-the-ground: on Europe 2026, dining, transport, entertainment, groceries and shopping are all exactly $0.00 before departure. The chart was not bad, it was two economies stacked into one, and travel was the only thing visible in the union. So split on start_date and use the axis that carries information in each phase. Booked ahead ($22,050.51, 57%) is all flights and stays, so merchant is the axis — Agoda $4,490, Air India $3,454, Luxury Escapes $3,284. On the ground ($16,946.94) travel falls to $8,241 among dining $4,452 and transport $2,938, and category is finally worth charting. The hero is the ratio, not a lone total, with the on-ground daily rate beside it — the only figure comparable between trips, since totals are not: Europe $677.88/day against Auckland $83.39. A trip with near-zero committed spend says so, because Sonu + Sunny's $184.84 is a filing artefact (both legs' bookings sit on the first trip), not a cheap trip. Two dataviz rules this page was breaking. Category bars now use one copper hue with the name as a direct label: the per-bar rainbow double-encoded identity the label already carries, and the trip subset fails CVD validation on this surface (other vs shopping at delta-E 5.0 protan, below the floor of 6). And the hero figure drops the serif and tabular-nums, which read as decoration at that size. The phase bar is two ordinal steps of one hue, validated with --ordinal against the card surface, with a 2px gap so the boundary is an edge. 278 passing, build clean. Data verified against the database directly; I could not render the page in a browser to eyeball the layout.
This commit is contained in:
@@ -252,6 +252,60 @@ trip never incurred a cost it cancelled.
|
||||
Europe — Sonu + Sunny (id 3, 12–28 Apr, created 2026-07-28 from tag 5),
|
||||
Singapore + Bangkok 2026 (id 4).
|
||||
|
||||
### Why `travel` looked useless on a trip page, and the fix (2026-08-02)
|
||||
|
||||
`travel` was ~60% of every trip and told you nothing. The tempting fix — a finer
|
||||
travel taxonomy (flights / stays / getting around) — needs a hand-maintained
|
||||
merchant list, which is the trap ticket #19 already describes. It is also the
|
||||
wrong diagnosis.
|
||||
|
||||
**Measured on Europe 2026, `travel` is the only category that spans both phases
|
||||
of a trip. Every other category is 100% on-the-ground — dining, transport,
|
||||
entertainment, groceries and shopping are all exactly $0.00 before departure.**
|
||||
So the category chart was not a bad chart; it was two different economies stacked
|
||||
into one, and travel was the only thing visible in the union.
|
||||
|
||||
The fix is to split on `trips.start_date` and use the axis that carries
|
||||
information in each phase:
|
||||
|
||||
- **Booked ahead** (before `start_date`, $22,050.51 / 57% on Europe) — everything
|
||||
is a flight, a stay or a rail ticket, so category is a constant and **merchant**
|
||||
is the axis: Agoda $4,490.52, Air India $3,454.32, Luxury Escapes $3,283.80.
|
||||
- **On the ground** (on/after `start_date`, $16,946.94) — travel falls to
|
||||
$8,240.74 among dining $4,452.18, transport $2,937.70, entertainment $698.14,
|
||||
groceries $589.22. **Category is finally worth charting.**
|
||||
|
||||
`getTripAnalytics` returns `phases`, `committed_merchants`, `on_ground_categories`
|
||||
and `on_ground_daily`. A trip with a NULL `start_date` has no knowable departure,
|
||||
so the SQL folds everything into on-ground rather than reporting it as committed.
|
||||
|
||||
**The daily rate is the only figure comparable between trips**, because totals are
|
||||
not — trips differ in length. Europe $677.88/day, Sonu + Sunny $573.57, Singapore
|
||||
+ Bangkok $309.76, Auckland $83.39. The page ranks the current trip against the
|
||||
others from the already-loaded `useTrips()` list.
|
||||
|
||||
**A trip with near-zero committed spend is a filing artefact, not a cheap trip.**
|
||||
Europe — Sonu + Sunny shows $184.84 committed against Europe 2026's $22,050.51
|
||||
because both legs' flights and stays were filed on the first trip. The page says
|
||||
so rather than letting the ratio read as missing data.
|
||||
|
||||
Two chart rules this page now follows, both from the `dataviz` skill and both
|
||||
previously broken here:
|
||||
|
||||
- **One series → one colour.** The category bars use a single copper hue with the
|
||||
category as a direct label. `CATEGORY_COLORS` was a per-bar rainbow, which
|
||||
double-encodes identity the label already carries — and the trip subset **fails**
|
||||
CVD validation on this surface (`other` ↔ `shopping` ΔE 5.0 protan, below the
|
||||
floor of 6). Do not reintroduce per-category colour on a labelled bar chart.
|
||||
- **No serif and no `tabular-nums` on the hero figure.** Fraunces is for section
|
||||
headings; a display face on a large number reads as decoration, and equal-width
|
||||
digits make it look loose.
|
||||
|
||||
The phase split bar is two ordinal steps of one hue (`#7c4820` → `#d28a47`),
|
||||
validated with `--ordinal` against the `#171410` card surface, with a 2px gap so
|
||||
the boundary is an edge rather than a colour change. Both segments are
|
||||
direct-labelled, so it needs no legend.
|
||||
|
||||
### Trip participation is derived, and a trip is shared
|
||||
|
||||
Rebuilt 2026-08-02. Trips were scoped to `trips.owner_id`, so Sonu saw **no
|
||||
|
||||
Reference in New Issue
Block a user