Clam Card

Level: Intermediate 30–60 min

Concepts: State

Solutions: C# | TypeScript | Python


Implement a system for a contactless travel card for subways.

  • The card does not need to be topped up.
  • The card charges the owner’s bank account directly when used.
  • The card is used by touching in and out at train stations.
  • The train system accepting this card has two categorical zones of stations, Zone A and Zone B.
Zone A Zone B
AsteriskBison
AmershamBugel
AldgateBalham
AngelBullhead
AnerleyBarbican
  • Traveling within zone B is more expensive than traveling in Zone A.
  • The price of zone B is inclusive of traveling within zone A.
  • If one of the stations is within Zone 2 at any point in a journey, the price for zone 2 will be charged.

The fares are as described below:

Zone Single Day Week Month
Zone A $2.50 $7.00 $40.00 $145.00
Zone B $3.00 $8.00 $47.00 $165.00
  • A Single is a journey from one station to another.
  • A Day fare includes all single journeys made within a single day.
  • A Week fare includes all single journeys made within a single week.
  • A Month fare includes all single journeys made within a single month.
  • No matter how many journeys are made within one of the time boundaries within a particular zone, the price will cap at that time period’s fare.

Scenarios

Follow these scenarios driving out our code as you go.

One-Way Zone 1 Journey Given Michael has a Clam Card And Michael travels from Asterisk to Aldgate Then Michael will be charged $2.50 for his first journey

One-Way Zone 1 to Zone 2 Journey Given Michael has a Clam Card And Michael travels from Asterisk to Barbican Then Michael will be charged $3.0 for his first journey

Multiple journeys Given Michael has a Clam Card And Michael travels from Asterisk to Aldgate And Michael travels from Asterisk to Balham Then Michael will be charged $2.50 for his first journey And a further $3.00 for his second journey. Totaling $5.50

Multiple Journeys including Zone B reaching daily cap Given Michael has a Clam Card And Michael travels from Asterisk to Barbican And Michael travels from Barbican to Balham And Michael travels from Balham to Bison And Michael travels from Bison to Asterisk Then Michael will be charged $3.00 for his first journey And a further $3.00 for his second journey And a further $2 for his third journey And a further $0.00 for his fourth journey

Multiple Journeys Zone A reaching daily cap Given Michael has a Clam Card And Michael travels from Asterisk to Aldgate And Michael travels from Aldgate to Angel And Michael travels from Angel to Antelope And Michael travels from Antelope to Asterisk Then Michael will be charged $2.50 for his first journey And a further $2.50 for his second journey And a further $2 for his third journey And a further $0.00 for his fourth journey

Bonus

Take into account for return journeys, the new fares are as described below:

Zone Single Return
Zone A $2.00
Zone B $2.50
  1. A Single is a journey from one station to another.
  2. A Return is a single journey where the next immediate journey is the inverse of the previous journey.
  3. Traveling from station A to station B, then from Station B to station A, classifies as a return.
  4. A Day fare still includes all single and return journeys made within a single day.
  5. A Week fare still includes all single and return journeys made within a single week.
  6. A month fare still includes all single and return journeys made within a single month.

Multiple Return Journeys And Michael travels from Asterisk to Barbican And Michael travels from Barbican to Asterisk And Michael travels from Asterisk to Balham And Michael travels from Balham to Asterisk Then Michael will be charged $3.00 for his first journey And a further $2.50 for his second journey And a further $1.50 for his third journey And a further $0.00 for his fourth journey

Reference Walkthrough

Reference implementations in C#, TypeScript, and Python live at tddbuddy-reference-katas/clam-card. This is an F2 (light builder) kata: one primary aggregate (Card), a value-type Ride that records (from, to, zone, fare), a two-case Zone enum, a single UnknownStationError for off-network stations, and two small test-folder builders (CardBuilder + RideBuilder) that stage the zone→station network and synthesise Ride literals for equality scenarios. The API reads as the spec’s sentence form: card.travelFrom("Asterisk").to("Aldgate").

Scope note — pure domain only, daily cap only. The reference covers single-journey fare calculation across the four zone combinations, cumulative totals, and the daily per-zone cap ($7.00 Zone A, $8.00 Zone B). The weekly and monthly caps, the return-journey discount (the kata’s Bonus section), a bank-account collaborator the card notifies per charge, a validated station catalogue, and any peak/off-peak or concession pricing are all out of scope and listed as stretch goals in the repo README. Weekly/monthly caps need a clock collaborator rich enough to reason about week/month boundaries; return discounts need the card to remember its previous journey and detect the inverse — both are F3-shaped extensions.

This kata ships in Agent Full-Bake mode at middle gear, the F2 (light builder) tier. See the repo’s Gears section for why middle gear is the deliberate choice.


  • Test Deletion Is a Privileged Operation
    The cheapest way for an agent to make a failing test pass is to delete it. That is logical for the agent and catastrophic for the codebase. Tests are append-only by default. Deletion needs a human author, a separate commit, and a separate review.
  • The Instruction File Is Not the Discipline
    Teams that want their agent to do TDD reach for the instruction file. The TDAD paper measured this directly: adding a 'do TDD' instruction raised regressions by nearly two-thirds. Contextual test discovery cut them by seventy percent. The instruction is theatre. The codebase's test surface is the discipline.
  • Tamper-Resistant Test Design Is What the Suite Now Owes the Codebase
    Tests were a contract the team made with itself. Agents made the suite an attack surface. Tamper-resistant test design is what the suite now owes the codebase: expensive to spoof, redundantly verified, partially hidden, and impossible to delete without a separate review.