BulkRefundChecks
bulk-refund-checks:BulkRefundChecks
Confirms the refund type, ingests and deduplicates tickets, sequences the four specialists, then collates the single Approve / Flag result set. Owns everything the sub-skills don't.
One pipeline runs a batch of refund tickets end to end — ingest, triage, verify, check, collate — into a single Approve / Flag result set, while holding the line against fraudulent refunds.
Four one-time steps. Do these the first time you use it — the checks and fallbacks rely on each one — and every run after just works. The full companion guide lives on Notion.
Powers the service-status check: when our own records can't answer, it falls back to the live train-times sites (Recent Train Times / On Time Trains).
Without it, any row that needs the fallback comes back as service status unknown and is flagged — never wrongly approved.
Signing in is required — it's how the pipeline reads the tickets in the first place. Enabling writes is what additionally lets it send the automatic replies for the few matched flag reasons.
Signed in but writes left off → matched replies are recorded as (writes disabled) to send by hand. Notes, status and tags are always on.
On the first run, when the process reaches the UTN (barcode) check, it prompts you to log in with the TTK device username and password.
The password is never shown on screen, written into the output files, or stored in the tool itself.
That's it — then just tell Claude ‘Do the bulk refunds’ and it takes it from there.
You point it at the tickets. It does the checks.
One tool works through a whole batch of refund tickets in one go — it gathers them, decides which it can handle automatically, confirms who the customer is and whether they're owed a refund, runs its safety checks, and hands back two lists.
It clears the easy, obvious refunds on its own, stops the dodgy or ineligible ones getting through, and passes anything unclear to a person instead of guessing. You don't run the checks yourself — you tell it the refund type and point it at the tickets.
Refund due if the train was cancelled, delayed or rescheduled and the customer chose not to travel. Claim within 28 days.
Refund due if First Class was declassified or not provided. Claim within 28 days.
That's a Train Operating Company matter — Delay Repay, not a Seatfrog refund.
The short version: used the ticket → no refund from us. Didn't travel because the service was cancelled or disrupted → refund. Travelled but delayed → send them to the train company.
Every refund follows the same route. The orchestrator, BulkRefundChecks, owns the ingest and the final collation and hands each stage to a specialist skill. It calls at five stops before the verdict.
Pull the New refund tickets from a Zendesk view, search, or id-list — read across every page and reconciled against the view count, so nothing is missed. Deduplicate by booking key: duplicates are worked first (an internal note links each to the primary ticket) and the checks continue on the surviving ticket. Anything raised today is left out (travel must have passed).
Read the customer's own words (and any clear screenshot) to decide what the request really is: a genuine cancellation or declassification, or something for a person — a mistake, a change of mind, or a travelled-but-delayed claim. For upgrades it also splits cancellation from declassification.
One lookup confirms identity and the 28-day window, then returns the booking — route, travel date, ticket numbers, receipt total. Upgrades match on User ID + Sale ID; Flex matches on the booking reference, and the ticket email must match the booking's owner email. A ticket whose travel date hasn't passed is deferred — an internal note on the ticket says so, and a later run picks it up.
The fraud guard: a UTN check asks whether the barcode was clipped on board the train, or a Delay Repay claim started — scans at the station barriers don't count as travel. Cancellations then get a service check — did the booked train actually run — from our own records first, live sources only as fallback. A clipped ticket still gets a records-only check, so the decline can carry Delay Repay advice.
Each ticket lands on one outcome — Approve, Flag, or Deny — written to two lists in your Downloads folder. Flagged cases are then worked one-by-one in the terminal; the Approve list gets its replies only after a human confirms the refunds are processed. Customer-facing writes stay tightly gated throughout.
The pipeline is five composable skills. The sub-skills run in pipeline mode inside the orchestrator; the two check skills also run standalone for a one-off scan or service lookup.
bulk-refund-checks:BulkRefundChecks
Confirms the refund type, ingests and deduplicates tickets, sequences the four specialists, then collates the single Approve / Flag result set. Owns everything the sub-skills don't.
…:RefundTriageAndClassify
Reads ticket text and any clean screenshot to set the disposition, and for upgrades the cancellation / declassification category. Makes no admin lookups.
…:RefundVerifyAndHydrate
The single query. Enforces the identity and 28-day gates and returns the hydrated sale record the rest of the pipeline reuses.
…:BulkUTNChecks
Reports each sale's UTN scan status as APPROVE / FLAG / DENY. Standalone or in pipeline. Scan status only — no service lookup.
…:BulkServiceStatusChecks
Looks up whether the booked train was cancelled, delayed, or disrupted — refunds-hsp archive first, RTT / OTT via Chrome as fallback. Makes no refund decision.
The scan gates, the actuals confirm. A delay never auto-approves — that is a Train Operating Company matter, not a Seatfrog refund.
Didn't travel/the upgrade was unused, and the service status checks confirm the booked segment was cancelled or disrupted. Safe to refund.
Needs a human: ambiguous scan, unknown service, identity mismatch, out-of-scope request, or a duplicate ticket.
Proof of use — a clip scan on board the train — or a Delay Repay claim, or a claim raised more than 28 days after travel. Barrier entry/exit scans alone never deny: a customer can pass through the gates, meet a cancelled train, and leave without travelling.
| UTN check says | Result | Why |
|---|---|---|
| Clipped on board the train or Delay Repay claim already made | Deny | The upgrade is marked as used, claiming with the TOC is the correct course of action. Barrier entry/exit scans don't count as use. |
| Unclear | Flag | A person should look. |
| Not used | Approve | We can't disprove it and the upgrade wasn't used → refund stands. |
| UTN check | Did the train run? | Result | Why |
|---|---|---|---|
| Clipped on board | Checked from our records, for the reply only | Deny | They travelled — and if the records show their train was disrupted, Claude sends Delay Repay advice with the decline. |
| Not used | Cancelled / disrupted on their leg | Approve | Didn't travel → refund. |
| Not used | Ran (on time / only delayed) | Flag | Claim not supported — a delay is for the train company; a person decides. |
| Not used | Can't tell | Flag | Needs a person to review. |
| Unclear | — | Flag | The scan isn't clear enough; needs a person. |
Both land in your Downloads folder, named by product and date. The first column of each is refund_type — each product is refunded through a different system, so it must show on every row.
refund_type,zendesk_ticket_id,sale_id,
user_id,category,utn,service_status
flag_type,refund_type,zendesk_ticket_id,…,
reason,service_status,related_ticket_id,macro_reply
| flag_type | What it means | What you do |
|---|---|---|
| DENY | The checks would refuse it — proof they travelled, Delay Repay, or a claim over 28 days after travel. | Work the refusal — reply / decline. |
| FLAG | Needs judgement — unclear scan, unknown train status, email didn't match. | Review and decide. |
| SURFACE | Triage caught it as not belonging here — a mistake, a delay claim, an early termination. | Handle by hand. |
| DUPLICATE | A second ticket for a booking already in the batch. | Close it against the primary (in related_ticket_id). |
Both written to ~/Downloads · the customer's email is never written to any file — you find them by zendesk_ticket_id
The tool writes back to a customer in exactly two situations — a matched flag reason, and the post-refund confirmation you sign off — and it keeps the two lists honest so no ticket is worked twice.
A small set of flag reasons have an approved canned reply. Claude reads the macro first, sends it whole so the right tags and fields go on too, and then sets the ticket to Pending (unless the macro sets its own status). Everything else is left for the one-by-one session below. If Zendesk replies are switched off, the tool skips the send and marks the row (writes disabled).
| Type | Reason | Canned reply |
|---|---|---|
| Upgrade | Wrong / unconfirmable reference | Incorrect reference |
| Upgrade | Delay Repay already claimed | Upgrades: Delay Repay Already Started |
| Flex | Delay Repay already claimed | Claimed for Delay Replay |
More on the way: a set of new Claude: macros (Delay Repay advice after travel, refund window passed, service ran, duplicate request) is drafted and awaiting creation in Zendesk — each joins this table once it exists and a human has approved it.
One ticket at a time, never a batch. When you're ready, Claude walks the Flag list with you in the terminal: for each row it re-checks the ticket is still New, finishes any outstanding investigation by default, and briefs you on the reason and evidence.
You decide, it does. It proposes an action with a recommendation — a permitted macro, an internal note, or leave-for-human — and only acts once you confirm. The worked ticket goes to Pending and comes straight off the list.
Approvals are refunded by a person, in each product's refund system — the tool never replies to an Approve row on its own.
Once you confirm the refunds are done, Claude bulk-replies to those tickets with the Refund processed reply — checking each is still New first, and removing each row from the list as its reply lands.
Before starting on a list, the tool re-checks every ticket's status. Any ticket that's no longer New has already been picked up — by a colleague, an earlier step, a merge, or an automatic rule — so it comes off the list rather than being worked.
And again immediately before every single action. A check done at the start goes stale on a shared queue — so no reply, note, or status change happens without confirming the ticket is still New right then.
As each ticket is worked, it comes off the list — instantly. The file on disk is rewritten there and then, never saved up for the end. The lists shrink to empty as the batch clears — which is what stops two people refunding or replying to the same ticket.
Only a pipeline run adds rows. Nothing is hand-added or re-added — if a ticket needs rework, it comes back through a fresh run.
Ticket ingestion, the gated public-reply macros and post-refund confirmations, plus always-on internal notes and status changes.
RDS sale & flex_state_prod queries, the refunds-hsp archive, and TTK scan lookups.
Only for the Recent Train Times / On Time Trains service-status fallback.
Check the refund type first. It decides the list, the details, and how tickets are sorted.
Today's tickets are always skipped — travel has to have happened first.
We never refund a used ticket, and a delay is always a train-company matter, never an automatic yes.
A mismatched email means flag, not decline — it's almost always a typo, not fraud.
Refund figures always include the Platform Fee — the receipt TOTAL, £3 per passenger on upgrades.
Never work a ticket that isn't New — someone's already on it. When in doubt, it gets flagged, not guessed.
Flagged cases are surfaced in the terminal to review and action one-by-one — removed from the Flag list as they're worked, with any outstanding investigation completed by default.
Duplicates are worked first — a linking internal note goes on the extra ticket, and the checks continue on the surviving one. Deferred tickets (travel date not yet passed) get an internal note too.
Approve-list tickets get a bulk Refund processed reply — once a human confirms the refunds have been paid. And every worked ticket is set to Pending by default.
New Claude: macros — drafted and awaiting creation in Zendesk. Once live, even less manual handling of flagged cases.
When a ticket was clipped on board, Claude still checks (from our own records) whether the train was disrupted — and the decline goes out with Delay Repay advice, once its Claude: macro is created. Barrier entry/exit scans no longer count as proof of travel.
Fully automatic merging of duplicate requests (the merge itself is still done by a person today).