Files
finance-app/europe-2026-expense-report.md
T
siddharthd d6b4ec84f6
ci / lint-test (push) Successful in 39s
feat(integrity): balance assertions, category constraint, refund netting
Four related fixes to spend correctness.

Balance assertions. Nothing checked that a statement's transactions add up to
its closing balance. getStatements now computes opening + movement - closing
and the statements page flags any statement that does not reconcile. Sign
depends on what the balance means: on a credit card or loan it is what you owe,
so spending increases it; on a transaction or offset account it is what you
hold. 11 statements currently fail, $4,177 unexplained - including two adjacent
ANZ statements off by exactly +/-$230.38, a transaction filed against the wrong
one.

Category normalisation (migration 0015). Categories arrive from Gemini (which
writes straight to Postgres from N8N), CSV import and manual edits, so the rule
belongs in the database - same reasoning as normalize_statement_type in 0013.
Adds normalize_category(), triggers on transactions and transaction_overrides,
and CHECK constraints. Backfilled 122 rows: 19 'payment' ($42,569) to transfers,
14 'refund' recovered to the merchant's usual category, and title-case duplicates
folded into their canonical spelling - 'Shopping' and 'shopping' had been
counted as separate categories by every GROUP BY.

Refund netting. Monthly analytics counted only debits, so a refund was counted
nowhere: excluded from spend by type, and not income by category. A $2,888.92
Expedia purchase refunded in full eight days later still read as $2,888.92 of
spend. SPEND_SIGNED and NET_SPEND_ROWS bring refunds in as negatives; 'income'
joins the excluded categories so incoming money cannot leak in as negative
spend. $36,773 across 93 rows now nets correctly.

Drill-down share. The insights drill-down used my_share_percent ?? 100, which
ignored transaction_splits entirely and repeated the bug fixed in ab00f8c one
layer up. getTransactions now returns my_share_pct and my_amount resolved
server-side, and the table shows gross alongside your share.
2026-07-26 10:35:59 +10:00

15 KiB
Raw Blame History

Europe 2026 — Expense Report

Trip: 22 days · Mar 21 Apr 12, 2026
Route: Rome → Venice → Ortisei (Dolomites) → Varenna (Lake Como) → St Moritz → Lucerne → Paris
Group: 5 travellers — Siddharth & Meghalee (Melbourne), Molina, Sibnath & Rumna (Delhi)
Data: 212 tagged transactions across all cards


Group Breakdown — AU vs India

The two groups fly different routes and airlines. Everything else (accommodation, trains, on-trip spending) is shared across all 5 travellers.

AU Group (Siddharth + Meghalee) India Group (Molina + Sibnath + Rumna)
Flights $2,164 $6,151
Share of shared costs $12,577 $18,866
Group total $14,741 $25,017
Per person $7,371 $8,339

Shared costs (accommodation + trains + all on-trip): $31,443
Apportioned 2:3 by headcount — AU group (2 pax) = 40% · India group (3 pax) = 60%

Scenario Amount
Total trip $39,757
Excl. all flights $31,443
Excl. AU flights only (what India group is responsible for) $37,593
Excl. India flights only (what AU group is responsible for) $33,607

Australia group flights: China Eastern MEL→PVG→FCO × 2 ($1,885) + Qatar cancellation fee ($279) = $2,164
India group flights: ITA Airways DEL→FCO × 3 (Azwebin, $2,696) + Air India CDG→DEL→GAU × 3 ($3,454) = $6,151
India group flights are ~3× the AU group total despite flying a similar number of legs — driven by 3 pax vs 2, plus the ITA outbound on top of Air India return.


Overall Summary

AUD
Gross charged $42,696.73
Refunds received $2,939.22
Net total spend $39,757.51
Phase Net Spend % of Total
Pre-trip bookings $23,730.02 60%
On-trip (22 days) $16,027.49 40%

On-trip daily average: $729/day for the group · $146/person/day


Refunds & Cancellations

Merchant Charged Refunded Net Detail
Qatar Airways $2,396.90 $2,118.10 $278.80 Cancelled. Replaced with China Eastern MEL→PVG→FCO.
Luxury Escapes $3,283.80 $820.95 $2,462.85 Partial refund on Paris Adagio Montmartre.
FreeNow $43.32 $0.17 $43.15 Hold reversal.

Pre-Trip Bookings — $23,730 net

Flights

Carrier Passengers Gross Refund Net
ITA Airways (via Azwebin) Delhi group (3) DEL → FCO $2,696.34 $2,696.34
Air India Delhi group (3) CDG → DEL → GAU (return) $3,454.32 $3,454.32
China Eastern Siddharth MEL → PVG → FCO $849.05 $849.05
China Eastern Meghalee MEL → PVG → FCO $849.05 $849.05
China Eastern (ancillary) Seat/baggage fees $187.14 $187.14
Qatar Airways Siddharth + Meghalee (cancelled) $2,396.90 $2,118.10 $278.80
Flights total $10,432.80 $2,118.10 $8,314.70

ITA Airways (billed as Azwebin Fiumicino) — 3 outbound tickets Delhi → Rome plus 3 sets of ancillary fees, all charged Jan 17.
Qatar was booked first (Jan 17) and then cancelled; refund of $2,118.10 processed Mar 24 (during Rome stay). Net cancellation penalty: $278.80. China Eastern replaced it at $1,885.24 total — slightly cheaper.

Accommodation

Property Destination Nights Booked via Net Cost
Fontana di Trevi area (Vrbo HA-XTP24R) Rome 4 Vrbo $2,221.66
Apartment Moro 4 — Rialto Venice 3 Direct $1,410.11
174A Apartments Murata Ortisei 4 Muse Holiday $1,815.06
In the heart of Varenna (Vrbo HA7B9C9J) Varenna 2 Vrbo $2,274.46
Cozy Central Duplex St Moritz 2 Agoda $2,560.45
Hotel Luzernerhof Lucerne 3 Agoda $1,930.07
Adagio Paris Montmartre Paris 4 Luxury Escapes $2,745.24
Accommodation total 22 nights $14,957.05

Paris: Luxury Escapes $3,283.80 $820.95 refund + $282.39 extra charge = $2,745.24 net.
Average cost per night (5 pax): $680 · per person per night: $136

Pre-booked Transport

Date Merchant Route Amount
Jan 26 Trenitalia (Frecciarossa Business) Rome → Venice (5 pax) $468.55
Mar 4 Rail Europe Various European routes $372.40
Total $840.95

On-Trip Spending — $16,027 net

By Destination

Destination Dates Net Spend Txns Daily Avg
Rome (inc. transit days Mar 1920) Mar 1924 $2,955 46 $493
Venice Mar 2527 $960 21 $320
Ortisei / Dolomites Mar 2831 $3,595 19 $899
Varenna / Lake Como Apr 12 $863 16 $432
St Moritz Apr 34 $355 10 $178
Lucerne Apr 57 $3,422 22 $1,141
Paris Apr 812 $3,878 46 $970
Total on-trip 22 days $16,027 180 $729

Rome & Transit (Mar 1924) — $2,955

Mar 1920 (transit via Shanghai → Milan → Rome):

  • Glovo Milan × 4 deliveries: $93 — feeding the group across airport transit days
  • Shanghai Sunny restaurant: $16 (CNY 78)
  • Todis Roma groceries: $55 (manual, likely night of arrival)

On ground in Rome:

Category Key Items Total
Dining Lione 21 EUR 90 ($148) · Suresh EUR 52 ($85) · Glovo × 4 ($179) · Faro Café ($40) · Bap Vaschette ($29) · various ~$780
Transport FreeNow × 2 ($43) · Uber × 6 ($151) · ATAC bus ($2) · Radiotaxi ($11) ~$210
Travel Trenitalia local ($23) · Roma Termini station tickets EUR 300 ($493) · Trenitalia EUR 275 ($452) · Trenitalia EUR 165 ($274) ~$1,242
Entertainment St Peter's Basilica tickets: $58 $58
Groceries Todis × 2 ($12) · F.Lli Casali ($17) · SumUp ($12) $41
Shopping Scudieri International Florence EUR 51: $84 $84
Cash Mar 21 cash transactions lump sum $337

Florence day trip (Mar 22): Trenitalia tickets $452 + $274 bought at Roma Termini ($493) and online, plus Scudieri shopping EUR 51. The two large Trenitalia charges on Mar 2223 suggest additional train legs beyond RomeVenice (possibly Florence or Naples day trip and ticket changes).


Venice (Mar 2527) — $960

Item Amount
Water taxi Ferrovia → accommodation $79
Caffe Rialto Venezia (dinner) $109
Fresh pasta restaurant (Sum Dal) $96
Baci Pasta $70
Dolcemente Salato (pastries/café) $67
Farini Pizza × 3 visits $92
Tvm Rialto Diretti 2 — shopping EUR 125 $209
Muse Holiday extra charge EUR 60 $100
Uber × 2 $48
Despar groceries $19
Gelatoteca Suso + Bar Americano $17
Pasticceria Ballarin + Bar Filovia × 2 $28

Grand Canal water taxi arrival ($79) was the planned "wow moment". Tvm Rialto Diretti 2 (EUR 125) — likely Murano glass or quality Venetian craft souvenirs, the largest single on-trip discretionary purchase outside of gear/activities.


Ortisei / Dolomites (Mar 2831) — $3,595

The most expensive on-trip leg. Five major items account for $2,857 of it:

Item Amount Detail
Erich Perathon (gear shop, Val Gardena) $1,477 EUR 880 — outdoor/ski clothing or equipment in Santa Cristina
Funivie Seceda cable car $441 EUR 236+27 — the centrepiece cable car to 2,519m above Ortisei
Trenitalia Venice → Bolzano (5 pax) $528 EUR 259.50 + EUR 55 split across two bookings
Taxi Demez Gregor (into valley) $238 EUR 142 — private taxi from Bolzano to Ortisei (no public option for 5 with bags)
Funivia Lagazuoi, Cortina $222 EUR 132.50 — Cortina d'Ampezzo day trip cable car
Despar Dolomiti (groceries) $201 EUR 97.59 + EUR 22.30 across two shops
Ristorante Meuble Pont, Cortina $180 EUR 107.50 — group dinner on Cortina day trip
Turonda Pizza Ortisei $93
Istanbul Kebab + Buffet Bolzano × 2 $111 Transit + local meals
Lago di Braies chocolate stop $21
Café Adler + Caffe Corso $34

Varenna / Lake Como (Apr 12) — $863

Item Amount
Taxi Demez Gregor (out of valley) $239
Trenitalia (onward journey) $101
Pizza e Sfizi × 2 dinners $113
Mr Panino Bellagio (lunch) $67
Bernasconi Claudia Varenna $55
Varenna 1 shop EUR 55 $92
Valli Mara Bellagio souvenirs $71
Bistro di Corti × 2 $50
Lake Bakery × 2 $30
Ristorante Liberty, Tirano $44
Groceries (Despar, Macelleria × 2) $49
Gelato + bakery $28

Bellagio day trip from Varenna was a highlight — lunch at Mr Panino, ice cream at Gilardoni Stefania, souvenirs.
Taxi Demez again for the exit from Val Gardena (charged Apr 1, same driver/company as arrival).


St Moritz (Apr 34) — $355

Almost entirely self-catered:

Item Amount
Coop supermarket (big shop) $114
Pizzaway × 2 orders $89
Bäckerei Bad × 3 visits $58
Coop (additional) $14
Bus und Service $22
Taxi St Moritz CHF 30 $55
SBB WC $3

Despite being the highest accommodation cost per night ($1,280/night), almost nothing was spent on-trip. The group self-catered using Coop and relied on the bakery.


Lucerne (Apr 57) — $3,422

Three large purchases dominate:

Item Amount Detail
Rail Europe (last-minute) $1,526 EUR 911.95 — Swiss Travel Pass or multi-city rail tickets; bought while already in Lucerne
Daytrip.com private tour $1,078 EUR 642 — private guided day tour (description: "Prague" but likely Rhine Falls or day excursion from Lucerne)
Rigi Bahnen (cog railway) $844 CHF 461.60 — historic Rigi mountain railway, 5 pax round trip (charged Apr 10 but relates to Lucerne stay)
Hotel Rigi Kaltbad dining $47 Lunch at mountain hotel summit
Rigi Dorfladen shop $37 CHF 20 at Rigi summit village
Uber Eats $94 CHF 51.23
WAL*TENZ GMBH Zurich $82 CHF 45.10 — dining/café in Zurich
QUZGYXOO-2 $150 CHF 82.50 — unidentified Lucerne charge
GO4T Luzern $114 CHF 62.40 — likely a guided activity or boat tour
Amorino Gelato + Alpineum Café $51
Juice Paradise bubble tea $29
SBB toilets × 4 $11 CHF 1.50 each — Swiss public toilets

The Rail Europe purchase last-minute cost $1,526 vs booking pre-trip where Swiss Travel Passes are generally cheaper. Worth pre-purchasing on future Swiss trips.


Paris (Apr 812) — $3,878

The return leg via Basel with 4 nights in Paris:

Transit Basel (Apr 8): Bahn Mi Pho CHF 69 ($126) + McDonald's CHF 51 ($93) + Weber1s bagels ($66) + Kaffeemacher coffee ($17) + Confiseur Bachmann ($22) + Acento Argentino ($26) = $350 in Basel for the group during travel day

Paris spending:

Item Amount Detail
Viator/TripAdvisor guided tour $296 USD 203.70 — skip-the-line or guided Versailles/Paris tour
Palace of Versailles tickets $292 Full group
Louvre Museum tickets $277 Full group
Adagio Paris hotel extra charge $282 EUR 169 — extra night or room upgrade
FC Soleil restaurant $207 EUR 125 — group dinner
Uber airport transfer (Apr 12) $126 EUR 75.98 — departure to CDG
Deliveroo × 2 $123 EUR 52.49 + EUR 21.90
Yak and Yeti restaurant $83 EUR 49.90
Uber Eats $65
Wild Deer restaurant $64 EUR 38.20
Uber trips × 5 $192 Multiple rides across Paris
Fric Frac Montmartre $55 EUR 33
Mitao $56
Navigo transit pass $61 Group metro/bus
Little Versailles coffeeshop $36
Pain Pain bakery × 2 $29
Sarl by Orsel (groceries) × 2 $35
Various small $49

Museums + guided tour = $865 on Paris attractions (Versailles, Louvre, Viator).
Uber was heavily used in Paris — 5+ trips totalling $254 including the CDG airport transfer.


Spend by Category (Net)

Category Net Spend Txns Notes
Travel $31,290 51 Flights, accommodation, trains, cable cars
Dining $4,241 95 95 restaurant/café/delivery transactions
Transport $2,636 35 Uber, taxis, buses, metro passes
Shopping $2,053 6 Erich Perathon $1,477 + Venice/Varenna/Florence
Entertainment $698 4 Versailles, Louvre, Basilica, Bellagio souvenirs
Groceries $589 15 Despar, Coop, Todis, Macelleria
Other (net) $1,751 4 Qatar refund exceeds "other" debits
Uncategorised $42 2 Residual
Total $39,757 212

The negative "Other" figure reflects the Qatar Airways refund ($2,118) offsetting the ITA/Azwebin ancillary fees charged in the same category.


Top 15 On-Trip Transactions

Date Merchant AUD Category
Mar 28 Erich Perathon, Val Gardena $1,477 Shopping — EUR 880 gear
Apr 6 Rail Europe $1,526 Travel — EUR 912 rail passes
Apr 7 Daytrip.com $1,078 Transport — EUR 642 private tour
Apr 10 Rigi Bahnen $844 Travel — CHF 462 mountain railway
Mar 22 Roma Termini (train tickets) $493 Travel — EUR 300
Mar 22 Trenitalia $452 Travel — EUR 275
Mar 28 Trenitalia $435 Travel — EUR 260
Mar 30 Funivie Seceda $396 Travel — EUR 236 cable car
Apr 9 Viator/TripAdvisor $296 Travel — USD 204 Paris tour
Apr 8 Palace of Versailles $292 Entertainment
Apr 10 Adagio Paris extra $282 Travel
Apr 8 Louvre Museum $277 Entertainment
Mar 23 Trenitalia $274 Travel — EUR 165
Mar 21 Cash transactions (Rome) $337 Various
Apr 1 Taxi Demez Gregor $239 Transport — EUR 142

Key Observations

Flight changes cost $278.80. Qatar was booked first (Jan 17, $2,396.90) and later cancelled; the refund of $2,118.10 came through during the Rome stay on Mar 24. China Eastern (MEL→PVG→FCO) replaced it at $1,885.24 — marginally cheaper but via Shanghai with a longer travel time.

60% of spend was locked in before leaving Australia. Pre-trip bookings (flights for all 5, accommodation across 7 cities, and pre-purchased trains) totalled $23,730 vs $16,027 spent on the ground.

Dolomites was the most expensive on-trip leg despite only 4 days — Erich Perathon EUR 880 gear purchase in Val Gardena accounts for $1,477 alone. Without it, Dolomites would be the cheapest destination per day.

Rail spend ballooned in Lucerne. Rail Europe EUR 912 ($1,526) bought last-minute in Lucerne was the single largest on-trip travel cost. Swiss Travel Passes are considerably cheaper when purchased outside Switzerland before travel.

Dining: 95 transactions, $4,241 net. The group averaged $193/day on food across 22 days ($39/person/day). Glovo delivery was the primary dining mode during transit days and in Rome (10+ orders). Paris had the most sit-down restaurant spending — FC Soleil EUR 125, Yak and Yeti EUR 50, plus multiple delivery orders.

St Moritz: highest accommodation cost, lowest on-trip spend. Agoda charged $2,560 for 2 nights but the group spent only $355 on-trip — almost entirely groceries and the local bakery.

Paris Uber spend: $254 across 6+ trips including a $126 CDG airport transfer (EUR 76). Combined with 2 Deliveroo orders and Uber Eats, rideshare/delivery accounted for ~$380 of Paris on-trip costs.