On-Site Check-In & Print Routing: Event-Day Print Floor Architecture
The print floor is where every upstream promise gets tested in real time. By the time an attendee scans in at a kiosk, their record has already been ingested, paid, reconciled, and rendered to a badge PDF — this section owns the last, unforgiving leg: turning that scan into a physical badge in the attendee’s hand before they finish reading the welcome sign. The controlling constraint is a hard service-level objective measured in wall-clock seconds. Scan-to-print must stay sub-second at the median even while a registration desk fires hundreds of scans a minute, a single badge must print exactly once no matter how many times its barcode is waved at the reader, and the floor must degrade gracefully — never catastrophically — when a thermal printer jams, a spooler wedges, or the venue Wi-Fi drops a switch. This document is written for the event-ops engineers and on-site technical leads who stand behind the check-in counter with a laptop, and it defines the architecture, the dispatch contract, and the runbooks that keep the line moving when something inevitably breaks at 8:55 a.m. on day one.
The Scan-to-Print SLA & Floor Boundaries Link to this section
Everything in this section is organized around one number: the time between a successful check-in scan and the first line of thermal output. Hold that under a second at the median and the desk feels instantaneous; let it drift to three seconds and a queue forms that never recovers during the morning rush. The floor keeps that budget by refusing to do any heavy work inline — the badge PDF is already rendered upstream, so the dispatch path only has to resolve a printer, enqueue a job, and acknowledge. The two feeds into this floor are explicit. Rendered artifacts arrive from the PDF routing workflows stage, which decides which template and output channel each badge takes; the paid, reconciled records that authorize a print in the first place arrive from async batch processing, the durable queue that guarantees only settled attendees ever reach a printer. This section owns nothing before the badge PDF exists and nothing about payment truth — its remit starts at the scan and ends at a piece of printed stock, with a bounded, auditable failure path for everything in between. Keeping that boundary sharp is what lets the print floor be rebooted, re-cabled, or re-homed to a backup printer mid-event without touching a single financial record.
Stage 1 — Check-In Kiosk Sync Link to this section
The scan is the trigger for everything downstream, and the hardest problem it introduces is not speed but truth under intermittent connectivity. Venue networks partition; a kiosk keeps accepting scans while its uplink is dead, then rejoins with a backlog of check-ins that may overlap with scans other kiosks already processed. The Check-In Kiosk Sync stage owns exactly this: capturing a scan locally with a monotonic sequence, holding it durably when offline, and reconciling the queued check-ins against the authoritative attendee state once the network returns — without printing a badge for someone a neighboring kiosk already served. It defines the attendee-presence contract the rest of the floor trusts, so that a dispatch decision is always made against a single, agreed check-in event rather than a racing pair. Get this stage wrong and the symptoms are unmistakable: two badges for one attendee after a Wi-Fi blip, or a “not registered” rejection for someone who scanned thirty seconds before the switch dropped. Its job is to make the scan idempotent at the source, before dispatch ever sees it.
Stage 2 — Print Queue Orchestration Link to this section
Once a scan is a trusted check-in event, it becomes a print job that has to reach a specific printer without overwhelming it. The Print Queue Orchestration stage is the beating heart of the floor: it accepts each badge print job, assigns it a stable identity, orders jobs by priority, and holds one bounded queue per physical printer so no single device is buried while its neighbor sits idle. This is where backpressure lives — a printer that falls behind must shed or redirect load rather than let its queue grow without limit and blow the scan-to-print budget for everyone behind it. It is also where the idempotency guarantee is enforced structurally: a job carries a deterministic key, and the dispatcher will enqueue that key exactly once, so a double-tapped barcode or a retried dispatch collapses to a single sheet of stock. The orchestration layer treats spooler health as first-class signal, watching per-printer depth and completion rate to decide, job by job, which target keeps the median latency inside the SLA. Everything else on the floor is a variation on getting a job into, or safely out of, one of these queues.
Stage 3 — On-Site Print Failover Link to this section
Printers fail in the middle of events — that is a certainty, not a risk, and the floor is designed to treat a dead printer as a routine event rather than an incident. The On-Site Print Failover stage owns the graceful-degradation half of the SLA: detecting that a printer has stopped completing jobs, draining its stranded queue to a healthy peer or a designated backup device, and doing so without reprinting anything that already came out or losing anything that had not. The subtle failure here is the “silent zombie” printer that accepts jobs and reports ready while its cutter has jammed, so failover cannot rely on a clean error — it watches completion telemetry and trips on a stall, not just on a hard fault. When the pool shrinks, the coordinator re-homes work to the least-loaded survivor and, if the whole pool is down, parks jobs in the reprint queue rather than dropping them on the floor. This stage is what turns “a printer died and the line stopped” into “a printer died and nobody at the desk noticed,” which is the entire point of engineering the floor rather than manually shuffling USB cables.
Stage 4 — Badge Reprint Handling Link to this section
Reprints are where the exactly-once guarantee meets human reality: a badge smudges, a name is misspelled, an attendee loses their lanyard at lunch, or a jam eats a half-printed sheet. The Badge Reprint Handling stage owns the deliberate, audited path for producing another copy without duplicating what a badge authorizes — access credentials, session entitlements, or a scannable payment token that must not suddenly exist twice in the wild. It distinguishes a true reprint (same attendee, invalidate-and-reissue) from an accidental duplicate (the same scan reaching dispatch twice, which the orchestration layer already suppresses), and it carries the reason code and operator identity so a reprint surge is traceable to a jammed printer rather than to fraud. This stage is the controlled escape hatch from idempotency: every other part of the floor works to guarantee one print per scan, and reprint handling is the single, logged place where a second print is allowed on purpose. It closes the loop with failover — jobs that land in the reprint queue after a device dies are re-driven here — so recovery is a first-class workflow, not a manual re-scan.
Production-Ready Python Implementation Link to this section
The coordinator below is the floor’s dispatch core in miniature: a Pydantic v2 job model that mints a deterministic, idempotent job_id from the scan, a printer pool that tracks per-device health and queue depth, a least-loaded routing decision, and a failover hook that re-homes a dead printer’s stranded work. It is runnable as-is, uses only explicit control flow suitable for a live floor, and its __main__ block proves the two properties that matter most — a replayed scan collapses to one job, and a printer death moves work without loss.
from __future__ import annotations
import hashlib
import logging
from collections import deque
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field, model_validator
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
logger = logging.getLogger("print_dispatch")
class PrintDispatchJob(BaseModel):
"""One scan-to-print unit of work handed to the print floor."""
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
attendee_id: str = Field(min_length=4, max_length=48)
badge_pdf_ref: str = Field(min_length=1, max_length=512)
checkin_scan_id: str = Field(min_length=4, max_length=64)
priority: int = Field(default=100, ge=0, le=1000)
job_id: str = ""
@model_validator(mode="after")
def _mint_job_id(self) -> "PrintDispatchJob":
# Deterministic, idempotent id: one scan of one badge can only ever
# mint one job_id, so a double-tap or a retried dispatch collapses.
if not self.job_id:
seed = f"{self.attendee_id}:{self.checkin_scan_id}:{self.badge_pdf_ref}"
self.job_id = "job_" + hashlib.sha256(seed.encode("utf-8")).hexdigest()[:24]
return self
class PrinterPool:
"""Per-printer FIFO queues plus a health flag for each device."""
def __init__(self, printer_ids: list[str]) -> None:
self.queues: dict[str, deque[PrintDispatchJob]] = {p: deque() for p in printer_ids}
self.healthy: dict[str, bool] = {p: True for p in printer_ids}
def healthy_printers(self) -> list[str]:
return [p for p, ok in self.healthy.items() if ok]
def mark_down(self, printer_id: str) -> None:
self.healthy[printer_id] = False
def depth(self, printer_id: str) -> int:
return len(self.queues[printer_id])
class DispatchCoordinator:
"""Routes idempotent jobs to the least-loaded healthy printer, with failover."""
MAX_ATTEMPTS = 3
def __init__(self, pool: PrinterPool) -> None:
self.pool = pool
self._seen: set[str] = set() # structural idempotency guard on job_id
def _least_loaded(self) -> Optional[str]:
healthy = self.pool.healthy_printers()
if not healthy:
return None
return min(healthy, key=self.pool.depth)
def dispatch(self, job: PrintDispatchJob) -> Optional[str]:
if job.job_id in self._seen:
logger.info("duplicate suppressed | job_id=%s", job.job_id)
return None
target = self._least_loaded()
if target is None:
logger.error("no healthy printer | job_id=%s -> reprint/DLQ", job.job_id)
return None
self._seen.add(job.job_id)
self.pool.queues[target].append(job)
logger.info("dispatched | job_id=%s -> %s | depth=%d", job.job_id, target, self.pool.depth(target))
return target
def print_next(self, printer_id: str, simulate_fault: bool = False) -> Optional[PrintDispatchJob]:
"""Retry hook: bounded attempts before the job is handed back for failover."""
queue = self.pool.queues[printer_id]
if not queue:
return None
job = queue.popleft()
for attempt in range(1, self.MAX_ATTEMPTS + 1):
if simulate_fault and attempt < self.MAX_ATTEMPTS:
logger.warning(
"print attempt failed | job_id=%s | printer=%s | attempt=%d",
job.job_id, printer_id, attempt,
)
continue
logger.info("printed | job_id=%s | printer=%s | attempt=%d", job.job_id, printer_id, attempt)
return job
return job
def failover(self, dead_printer: str) -> int:
"""Re-home a dead printer's stranded jobs to healthy peers; return count moved."""
self.pool.mark_down(dead_printer)
stranded = list(self.pool.queues[dead_printer])
self.pool.queues[dead_printer].clear()
moved = 0
for job in stranded:
target = self._least_loaded()
if target is None:
logger.error("failover blocked | job_id=%s -> reprint/DLQ", job.job_id)
continue
self.pool.queues[target].append(job)
moved += 1
logger.info("failover | job_id=%s | %s -> %s", job.job_id, dead_printer, target)
return moved
if __name__ == "__main__":
pool = PrinterPool(["printer-01", "printer-02", "printer-03"])
coordinator = DispatchCoordinator(pool)
raw_scans = [
{
"attendee_id": "att_10293", "badge_pdf_ref": "s3://badges/att_10293.pdf",
"checkin_scan_id": "scan_88121", "priority": 10,
},
{
"attendee_id": "att_55420", "badge_pdf_ref": "s3://badges/att_55420.pdf",
"checkin_scan_id": "scan_88122",
},
]
jobs = [PrintDispatchJob.model_validate(s) for s in raw_scans]
# Idempotency: re-scanning the same badge mints an identical job_id.
replay = PrintDispatchJob.model_validate(raw_scans[0])
assert replay.job_id == jobs[0].job_id
for job in jobs:
coordinator.dispatch(job)
# A duplicate dispatch of the same scan is suppressed — never double-printed.
assert coordinator.dispatch(replay) is None
# printer-01 dies mid-event; its queued work fails over to a healthy peer.
moved = coordinator.failover("printer-01")
assert "printer-01" not in pool.healthy_printers()
print(f"OK: failover moved {moved} job(s); pool now {pool.healthy_printers()}")
The __main__ block is the contract in executable form: the replayed scan asserts equal to the original job’s identity (one scan, one badge), the second dispatch of that identity returns None (the double-print guard), and the failover call proves stranded work is re-homed rather than dropped when a device leaves the pool.
Failure Modes & Operational Runbook Link to this section
| Failure Mode | Trigger / Symptom | Immediate Containment | Resolution Path |
|---|---|---|---|
| Kiosk network partition | Scans accepted offline, then a burst of overlapping check-ins on rejoin | Buffer locally with a monotonic sequence; dedupe on reconciliation before dispatch | Reconcile the offline backlog against authoritative state in Check-In Kiosk Sync; replay only unserved scans |
| Silent zombie printer | Printer reports ready but stops completing jobs (jam / empty roll) | Trip on a completion-rate stall, mark the device down, stop routing to it | Fail over stranded jobs to a healthy peer via On-Site Print Failover; clear the jam and re-admit |
| Queue backpressure at peak | One printer’s queue depth climbs past its high-water mark; latency drifts over SLA | Shed to the least-loaded peer; reject new jobs to that target until depth drains | Rebalance load across the pool per Print Queue Orchestration; add a printer if sustained |
| Duplicate scan / double-tap | Same barcode read twice; risk of two identical badges | Structural idempotency guard on job_id suppresses the second dispatch |
No action needed at print time; confirm the guard fired in logs, audit if the rate spikes |
| Total pool outage | All printers down (power / network); nothing completes | Park all jobs in the reprint queue; alert the on-site lead; hold the line | Restore a device, drain the reprint queue through Badge Reprint Handling in priority order |
Operational Guardrails Link to this section
These are the non-negotiable invariants for a print floor under live load; enforce them in code and alerting, not in tribal knowledge at the desk:
- One scan, one badge. Every job carries a deterministic
job_idderived from the check-in scan; dispatch enqueues that identity exactly once, so a double-tap or a retry can never produce a second sheet. - Never print inline on the hot path. The badge PDF is rendered upstream; the floor only resolves a printer, enqueues, and acknowledges — heavy work off the scan path is what keeps median scan-to-print sub-second.
- Bound every queue. Each printer has a hard high-water mark; crossing it sheds or redirects load rather than growing an unbounded backlog that blows the SLA for everyone behind it.
- Trip on stalls, not just faults. Health is measured by completion rate, so a zombie printer that reports ready while jammed is detected and drained, not trusted.
- Degrade, never drop. A job that cannot be routed lands in the reprint/dead-letter queue with its reason code — never a silent loss and never an infinite retry.
- Reprints are logged and intentional. The only sanctioned second print goes through the audited reprint path with an operator identity and reason, so a reprint surge is explainable after the fact.
- Instrument per printer. Emit correlation IDs and per-device depth, completion rate, and failover counts; tighten alert thresholds for the day-of check-in window.
Frequently Asked Questions Link to this section
How do you actually hold scan-to-print under a second at peak? By doing no expensive work on the scan path. The badge PDF is already rendered upstream in PDF routing workflows, so a scan only has to mint an idempotent job id, pick the least-loaded healthy printer, enqueue, and acknowledge — all in-memory or single-round-trip operations. Rendering, payment checks, and template resolution happen before the attendee ever reaches the desk, which keeps the day-of critical path down to routing and spooling.
What stops one attendee from getting two badges after a Wi-Fi blip?
Two independent guards. The kiosk stage makes the scan idempotent at the source with a monotonic sequence, so overlapping offline check-ins reconcile to a single presence event; the dispatch coordinator then enforces a structural idempotency guard on the derived job_id, so even if two dispatches slip through, the second is suppressed before it reaches a queue. A genuine reprint is a separate, logged action, never an accidental duplicate.
When a printer dies mid-event, do queued jobs get reprinted or lost? Neither. Failover marks the device down and re-homes its stranded queue to the least-loaded healthy peer; jobs that already completed are not re-driven, and jobs that had not printed are moved rather than dropped. If the entire pool is down, work parks in the reprint queue and is drained in priority order once a device recovers, so a hardware failure degrades throughput without losing a single attendee’s badge.
Related Link to this section
This section is the event-day fulfillment end of the platform. Continue with the stages it contains and the workflows that feed it:
- Print Queue Orchestration — accepts and orders badge print jobs, holds per-printer queue depth, and enforces backpressure and the idempotency guarantee.
- Check-In Kiosk Sync — captures the scan, survives network partitions, and reconciles offline check-ins into a single trusted presence event.
- On-Site Print Failover — detects stalled printers and re-homes stranded work to healthy devices without reprinting or losing jobs.
- Badge Reprint Handling — the audited, intentional path for reissuing a badge without duplicating the credentials it authorizes.
- PDF Routing Workflows — the upstream stage that renders and routes the badge PDF this floor consumes at scan time.
- Async Batch Processing — the durable queue that guarantees only paid, reconciled records ever become printable on the floor.