Reconciliation v2
read-only · click a figure to scope everything below it · times in {{tzLabel}}
{{g.title}}
{{model.lastError}}
Pick an event to begin.
Loading…
{{model.data.eventTitle || ('Event #' + model.data.eventId)}}
with {{model.group.length}} other stage(s) · figures below count each person once
Showing
{{cohortLabel(model.data.appliedCohort)}} · {{model.data.appliedType}} only
Events {{model.data.groupEventIds}}
Produced {{model.data.generatedAtUtc | date:'dd.MM.yyyy HH:mm:ss'}} {{tzLabel}}
Parts add up
yes - {{partitionSum() | number}} = {{partitionTotal() | number}}
NO - {{partitionSum() | number}} ≠ {{partitionTotal() | number}}. Do not use these figures.
Known limits of this report
- Turned-away entries come from the scanners, not from the server. A scanner that already knows a ticket is used refuses it on screen and sends no scan, so nothing about the refusal ever reaches the server by itself. It is counted from the handsets' own record instead: a refusal made offline is included once that scanner catches up, and a scanner on a build older than 04.08.2026 keeps no such record and contributes nothing.
- The operator is missing on older scans. The person behind each handset has only been stored since 03.08.2026; earlier scans show under "(not recorded)".
- Gate history begins 02.08.2026. Which handset stood at which gate is recorded from that date; nothing reconstructs it for earlier events.
- An unknown code cannot name a gate or a scanner. The lookup that detects it carries only the code and the event, so a forged ticket is counted but not placed.
- Times are the handset's own clock at the moment the ticket was presented, not the moment the server received it.
Who they were — click to scope everything below · the four exclusive groups sum to {{partitionTotal() | number}}
{{cohortCount(c.key) | number}}
{{c.label}}
Cancelled, Every stage, Stopped partway and Never came are exclusive and add up to Everyone.
Came in is the two scanned groups together, so it deliberately overlaps them.
Cancelled, Came in and Never came are exclusive and add up to Everyone.
Add another stage above and the per-stage groups appear.
What happened at the door — attempts, not people; these do NOT belong to the groups above
{{model.data.attempts.admittedAgain | number}}
Got in twice
reported {{model.data.attempts.admittedAgainReports | number}} times by re-sending scanners
{{model.data.attempts.preventedDuplicate | number}}
Turned away
{{model.data.attempts.unknownCode | number}}
Not our ticket
{{model.data.attempts.cancelledAdmitted | number}}
Cancelled, let in
{{model.data.attempts.noEventRefused | number}}
No event named
{{model.data.attempts.relayEcho | number}}
Relay echoes
Click any of the first four to compare it against the selected group in the chart below, per gate, sector or ticket type.
Turned away reads zero, which now means no scanner refused a ticket it already knew was used. It comes from the handsets' own record rather than from what reached the server, so a refusal made while a scanner was offline is counted once it catches up. A scanner on a build older than 2026-08-04 keeps no such record and contributes nothing here.
How they split — {{model.chart.labels.length || 0}} row(s) · compare by adding door events
{{d.label}}
{{t.label}}
Loading…
{{s.label}} · {{s.total | number}} {{s.kind === 'attempts' ? 'attempt(s)' : 'people'}}
the bar is the crowd; a door event is a count and a rate among the people who came in there - click a rate heading to put the worst gate on top
| {{model.dimension === 'TicketSector' ? 'Sector' : model.dimension === 'ScannedGate' ? 'Scanned at' : model.dimension === 'TicketType' ? 'Type' : 'Gate on ticket'}} | {{s.label}} ↕ | People |
|---|---|---|
| {{l}} | {{seriesVal(s, l) | number}} · {{seriesRate(s, l) | number:2}}% · no base | |
| Total | {{s.total | number}} | each column totals its own series |
{{u.label}}: {{u.total | number}}
cannot be split by gate, sector or type - these attempts belong to no ticket of ours, so there is nothing to attribute them to. Shown as a total on purpose rather than as zeros across every gate.
{{f.label}}
could not be computed, so it is left off the chart rather than drawn as zero. The rest of the comparison is unaffected. Try again, and if it persists the server log names the query.
Nothing in this selection.
Scanner activity — {{model.flow.bucketMinutes}}-minute buckets on one shared clock · {{model.flow.from | date:'dd.MM HH:mm'}} → {{model.flow.to | date:'HH:mm'}} · busiest {{model.flow.allPeak | number}} in one bucket at {{model.flow.allPeakAt | date:'HH:mm'}}
Everyone{{model.flow.allPeak | number}} peak
{{g.entrance}}{{g.total | number}} scans · peak {{g.peak | number}} · up to {{g.maxDevices}} scanner(s)
{{t.t | date:'HH:mm'}}
Every bar is scaled to the busiest bucket of the busiest gate, so a tall bar means the same number everywhere.
A gap is a bucket in which that gate recorded nothing - which is a fact about the gate, not missing data.
The solid line marks the event's busiest bucket; the small marker on each row marks that gate's own.
A gate whose rate fell while its scanner count fell was understaffed, not congested, and the two are
indistinguishable in a scan count alone - hover any bar for both numbers.
Presented again — {{model.data.repeatPresentations.length}} ticket(s) used once and offered a second time
| Ticket | First presented | Presented again | Reached us after | |||||
|---|---|---|---|---|---|---|---|---|
| gate | scanner | time | gate | scanner | time | |||
| {{r.ticketUnique}} | {{r.firstGate}} | {{r.firstDevice}} | {{r.firstScan | date:'dd.MM HH:mm:ss'}} | {{r.againGate}} | {{r.againDevice}} | {{r.againAt | date:'dd.MM HH:mm:ss'}} · {{r.reportedBy}}× | not known | {{r.delayMinutes}} min {{(r.delayMinutes / 60) | number:1}} h |
Times are when the ticket was presented, taken from the handset that scanned it, not when the server
learned of it. Those differ by hours for a scanner that was offline and drained later, and the last column is
exactly that gap. Only entries that GOT IN can appear here: a scanner that already knew the ticket was used
refuses it on screen and sends nothing at all.
Per operator — the person behind the handset, not the handset
| Operator | Scans | Scanners used | Gates worked | First | Last |
|---|---|---|---|---|---|
| {{o.operator}} | {{o.scannedCount | number}} | {{o.devices | number}} | {{o.entrances | number}} | {{o.firstScan | date:'dd.MM HH:mm'}} | {{o.lastScan | date:'dd.MM HH:mm'}} |
Scans made before the operator was recorded show under (not recorded). That is missing data, not one person who scanned everything.
{{model.forensics.open ? '▾' : '▸'}} What the scanners themselves recorded
How many people were let in and how many were turned away and why, who stood at which gate and for how long, and everything that happened on this event in order. Taken from the handsets' own record, not from the tickets.
Reading it…
This could not be read.
That is this page failing, not a statement about the event. Every other figure above is unaffected.
What the doors did
Nothing recorded for this event. Handsets on a build older than 2026-08-04 do not keep this record.
| What | Presentations | Tickets | Scanners | First | Last |
|---|---|---|---|---|---|
| {{events.activityLabel(a.kind)}} | {{a.activities | number}} | {{events.activityNamesTicket(a.kind) ? (a.tickets | number) : 'no ticket'}} | {{a.devices | number}} | {{a.firstAt | date:'dd.MM HH:mm'}} | {{a.lastAt | date:'dd.MM HH:mm'}} |
Times are {{tzLabel}}. {{events.activityTotal() | number}} presentations in total. A row counts PRESENTATIONS, so one visitor offering a used ticket three times is three of them, and Tickets beside it is how many different tickets that was. Scanners is the part worth reading: forty unknown codes from one handset is a door with a problem, forty from thirty handsets is an audience with one. Refusals appear only here, because a refused person never became a scanned ticket.
Shifts
No shift was recorded for this event. Handsets on a build older than 2026-08-04 do not keep one.
| Scanner | Operator | Gate | Sector limit | From | To | Admitted | Refused |
|---|---|---|---|---|---|---|---|
| {{s.deviceName || s.deviceGuid}} | {{s.operatorName || '(not recorded)'}} | {{s.entrance || 'any gate'}} | {{s.sectors || 'no limit'}} | {{s.openedAt | date:'dd.MM HH:mm'}} | {{s.closedAt ? (s.closedAt | date:'dd.MM HH:mm') : 'still open'}} | {{s.admitted | number}} | {{s.refused | number}} |
Times are {{tzLabel}}. A shift that admitted nobody is highlighted: it stood at a gate and did nothing, which is a finding rather than an empty row. Still open means the handset never closed that shift, usually because it was switched off rather than logged out.
What happened, in order
Nothing recorded for this event.
| Time | Scanner | What | Detail | Operator | Gate |
|---|---|---|---|---|---|
| {{j.occurredAt | date:'dd.MM HH:mm:ss'}} | {{j.deviceName || j.deviceGuid}} | {{j.kind}} | {{j.detail}} | {{j.operatorName || ''}} | {{j.entrance || ''}} |
Newest first, most recent 300. Each line carries the time the DEVICE thought it was. A line with no operator against it is a boot or an event loaded before anyone signed in, which genuinely happened with nobody there.