RestaurantBrain feasibility study

#05 — The CRM Product and Scope Tiers

TL;DR

  • The POS exists to be the CRM's capture device, and the CRM exists to make the POS worth buying. Neither half sells alone: a CRM on someone else's POS caps identity at 24% of Checks and has zero switching cost; a POS without the CRM is a EUR 79 commodity in a market where Storyous **starts at 390 CZK/mo (EUR 16)** and its mid tiers run 690–890 CZK (~EUR 28–36) [verified 2026-08, secondary CZ sources] — the till alone is already cheap, which is exactly why the CRM has to carry the price. The bundle is why the first 100 Customers buy. Payments and installed base are why the next 700 stay — see 01 — Feasibility verdict.
  • The headline feature is Holdout-based incremental revenue, and it costs ~1.5 eng-months. Freeze an Audience, hash-bucket 10% into a Holdout, join outcomes to POS Check margin on guest_id, report (mean margin Treated − mean margin Holdout) × n Treated with a bootstrapped 95% CI net of channel and Discount cost. No CEE competitor can produce that number because none of them own both the send and the till.
  • Canon's ~52% identity capture is a v2-core number, not a v1 number. With only the MVP/v1 capture surfaces live (POS phone lookup, SMS e-receipt join line, Receipt QR landing) the realistic blend on the CZ ICP is 30–38% of Checks if A4 holds, 22–28% if it does not. QR ordering and Wallet passes — the two mechanisms that move it to ~52% (v2 core) — are both v2 core, and they are the two things the P3 capacity cut deliberately keeps (§8).
  • Offline redemption is the only technical differentiator in CRM that a funded competitor cannot copy in a quarter, because it is a property of owning the Check fold, not a feature. Signed Ed25519/COSE Vouchers + leased float + a local spent-set. Modelled cross-Site double-spend loss at 500k Vouchers/yr: ~EUR 14/year [estimate]. Put that arithmetic in the deck.
  • Cut list is as important as the build list. Deleted permanently: probabilistic matching (until 500 Orgs), BTYD/CLV, visual journey builder, gift cards, kiosk capture, Wi-Fi captive portal, cross-brand consumer network, per-restaurant native Guest apps, reservations/table management. Roughly 14 eng-months of enterprise-shaped work that a 4-Site Prague café chain will never use.
  • CRM domain budget is fixed by canon: 2.5 eng-months at MVP, 19.0 at v1 sellable (S1 cut), 29.0 at v1 Full, +11.0 for full v2. The full-v2 tier is now split into v2 core (the P3 build) and v3 (P4 and later): of the canonical +11.0 CRM delta, +9.0 is v2 core and the +2.0 cashback/subscriptions line moves to v3 (§11). Every scope decision below is made against those numbers, not against r06's original 55.

#1. Product thesis, in one paragraph

We sell a Prague or Brno operator of 2–8 counter-service Sites one thing: the ability to name the Guests who stopped coming, and to prove in euros that getting them back was worth the fee. Everything else in the product is the machinery required to make that sentence true. To name a Guest you must attach an Identity to a Check, and the only place that happens reliably at a counter is inside the order flow itself — which is why we own the POS and why "CRM on top of your existing till" is a structurally worse business (~24% of Checks identified, no switching cost, 40–60%/yr churn on marketing tools [known]). To prove the euros you must join the send back to the margin on the Check, which requires the till, the Menu Snapshot costing, and a randomised Holdout assigned at Audience-build time — three things that live on our side of the wire and nobody else's. The Customer thinks they are buying loyalty and campaigns; what they are actually buying is a Guest database that fills itself and a quarterly number that survives their own scepticism. The POS is the price of admission to that number, the payment rail is how we make money on it (see 04 — Payments, fiscalisation, privacy and security), and the CRM is the reason the conversation starts at all.


#2. The Guest data model and identity resolution

#2.1 Entities (canonical names; storage, sync and crypto-shredding are in 02 — System architecture)

EntityWhat it isKey fields (product level)
IdentityOne identifierkind ∈ {phone_e164, email, loyalty_id, par_hash(v2 core)}, value_normalised, value_hash, first_seen, last_seen, source, provenance
ClusterThe derived set of Identities believed to be one Guestcluster_id, member Identities, cluster_version, computed_at
GuestThe person. Holds nothing but guest_id + the Cluster linkguest_id (the only thing the financial event log ever stores)
ProfileDisplay projection over a Clustername, birthday (d+m), locale, home Site, VIP flag, lifetime margin, last 5 Checks
MemberA Guest enrolled in a Loyalty Programprogram_id, joined_at, tier, loyalty_id, T&C version accepted
Consent RecordAppend-only, per (Identity, channel, purpose)state, policy_text_sha256, locale, ui_surface, captured_at, confirmed_at, evidence, prev_record_id
VoucherSigned, single-use, offline-redeemable entitlementsee §4.3
Campaign / Audience / Holdout / TreatedThe send, its frozen snapshot, the randomised control bucket, the sent bucketsee §5.3

Three invariants from canon that are product rules, not implementation details:

  • Guest PII lives in a separate crypto-shreddable store keyed by guest_id. The Check/Line/Payment event log holds guest_id and nothing else. A Fiscal Document survives erasure; the person does not.
  • consent_record.ui_surface is an enum with no pos_staff value and the API rejects it. The cashier-consent button does not exist and cannot be added as a custom field. It will be requested in week two of every chain deal; the answer is a schema, not a policy.
  • The migration importer sets marketing suppression by provenance, and the segment builder cannot clear it. Guests imported from a Dotypos export or an aggregator feed are analytics-only forever unless they independently join. See 06 — Integrations for the importer.

#2.2 Normalisation rules (get these wrong and the review queue explodes)

Identity kindRule
phone_e164libphonenumber, default region from the Site country (not the Device locale). Store E.164 + originating region. Reject non-mobile prefixes for SMS-channel Consent Records but keep them as Identities
emaillowercase; strip +tag; strip dots for gmail.com only; reject role addresses (info@, objednavky@) from marketing channels, keep as Identity
loyalty_idour issuance, never reused, checksummed so a mis-scan fails loudly
par_hash (v2 core)HMAC of the EMVCo PAR under a per-Org key. Enrolment-only. See §3.4 and 04 — Payments, fiscalisation, privacy and security

value_hash is HMAC-keyed per Org, so a leaked index is not a phone book, and so that erasure is key-revocation rather than a delete sweep.

#2.3 Matching rules — deterministic only in v1

Canon: probabilistic identity matching is out of scope until 500 Orgs and a labelled evaluation set exists. For 2–8 Site CZ chains the entire matching problem is "the same phone number typed twice".

RuleAuto-link into ClusterTier
Same loyalty_id scannedYesMVP
Same phone, delivery-confirmed (SMS e-receipt delivered or OTP)YesMVP
Same email, double-opt-in click confirmedYesv1
Same phone typed at POS twice, never delivery-confirmedYes, flagged in the Profile as unverifiedMVP
Same par_hash, both sides enrolledYesv2 core
Same normalised name / same birthday / same address / device fingerprintNo. Never in v1–v3

Guards that stay even in a deterministic-only world:

  • Two open Checks at different Sites within 10 minutes on the same Identity → do not link, raise a data-quality flag. This is almost always a cashier typing the Site's own phone number, which is how a single Cluster ends up owning 4,000 Checks.
  • Bias hard toward false splits. A false merge shows Guest A Guest B's Check history — a reportable personal-data breach (see 04 — Payments, fiscalisation, privacy and security). A false split just splits points and is fixed by a manual merge.
  • Merges are edges, never destructive rewrites. Unmerge must work, with an audit trail, or the erasure story is unbuildable.
  • Consent never inherits across a merge. Consent attaches to (Identity, channel, purpose). If the phone consented to SMS and the email did not consent to email, the merged Guest is SMS-only.

Expected review-queue volume once the base is mature: 1–4% of Clusters [estimate]. Above 8% the normalisation is broken, and it is always phone region or gmail aliasing. The review-queue UI itself is v1-Full-only — in the S1 cut it is a CSV export and a support-console action.


#3. The capture playbook

The binding constraint is not software. A cashier at a lunch rush will not spend more than 3–5 seconds per Check on anything that is not taking money, and will silently stop within two weeks if it is not either frictionless or enforced by the order flow.

#3.1 Every mechanism, with the canon tier it actually lands in

Capture rate = share of Checks that end with a usable Identity. All figures [estimate] — design inputs, not facts. Effort is CRM-domain (G) eng-months unless noted.

MechanismCanon tierCashier secondsRealistic captureData qualityEng-monthsVerdict
Phone typed at POS (prefix lookup on repeat)MVP6–12s first, 2–4s repeat15–25% of Checks; 60–80% of repeat Guests once enrolledHigh0.5The workhorse. Kills throughput at peak; the lookup must return in <150 ms offline
SMS e-receipt with a neutral join linev14–8s20–30% give the number; 8–20% of those joinHigh for phone, zero marketing consent by itself0.8Best identity-per-second in the product. Gated on assumption A4 (Czech counsel, due M2)
Receipt-footer QR → join landingv10s1–4% scan, ~50% completeHigh0.5Near-worthless alone; the mandatory fallback if A4 fails
Consent-gated dual Receipt renderer (fiscal-only vs promotional)v1 (domain F)0senabler, not a capture surface0.5–0.75 (F)Required the moment a second market exists. PL is fiscal-only, zero promotional content
QR PWA ordering (Guest orders and pays on own phone)v2 core0s60–90% of Orders through that channelVery high(domain C)The single biggest lever that exists, and it is v2 core — kept in the P3 cut precisely because it carries identity capture. This is why v1 capture is ~35%, not ~52% (v2 core)
Own web orderingv2 core0s~100% of that channel; channel is 3–15% of volumeVery high(domain C)Does not solve the counter
Wallet pass (Apple PassKit / Google Wallet)v2 core1–2s scan55–80% of Members present it once installedHigh2.5Best retention mechanic per euro; free push channel. Deferred by canon, and that hurts
Card-linked recognition (PAR, enrolment-only)v2 core0s~100% of an enrolled Guest's card Payments — but only 3–12% of Members ever enrol [estimate]Very high2.0Never put this in an ICP or a pricing argument. See §3.4
Self-order kiosk promptNever0s25–45% if prompted pre-PaymentHighCanon: kiosk is 4.5 eng-months of app + 5–8 of unattended EMV + 1.0–1.5 of accessibility. Not in this plan
Wi-Fi captive portalNever0s4–17% of coversMedium (junk emails)Needs its own consent, its own hardware story, and MAC randomisation killed silent re-ID
NFC tap-to-join tag on the counterNever (not in canon)0s3–8%High0.3Cheap (EUR 0.30/tag) and tempting. Not scoped; would need a written amendment
Plastic loyalty cardNever1–2s30–60% of enrolled Members present itHighLegacy. Support scanning a migrated barcode as a loyalty_id; do not issue cards

#3.2 Capture math for the canonical ICP

A 4-Site Prague counter-service chain, 350 Checks/Site/day → 1,400/day → ~42,000/month.

Product stateMechanisms liveBlended capture of ChecksNew Guests/month (dedup, ~35% new)Members after 12 months
MVP (P1 pilot)POS phone lookup15–22%~2,3009,000–13,000
v1 sellable, A4 true+ SMS e-receipt join line + Receipt QR30–38%~4,40017,000–22,000
v1 sellable, A4 false+ fiscal-only e-receipt + Receipt QR only22–28%~3,20012,000–16,000
v2 core (guest surfaces live)+ QR PWA on 30–40% of Orders + Wallet passes45–55%~6,10024,000–30,000

The clarification the rest of the deliverable needs: canon's "~52% when we own the order flow" is the v2-core figure and must always be written with that qualifier. The v1 sellable numbers are 30–38% (A4 true) / 22–28% (A4 false). At the P2 exit gate we sell a ~35% product and must not quote 52% in a demo. The gap is not a defect — it is the reason v2 core exists, and it is why guest ordering, the QR PWA and Wallet passes survive the P3 capacity cut while table service and the handheld do not — but a sales asset that quotes 52% (v2 core) at v1 is a churn generator.

flowchart LR A["Check closed at POS<br/>cashier asks for phone<br/>2-4 s"] --> B["Identity captured<br/>basis: contract"] B --> C["Transactional e-receipt<br/>SMS or email"] C --> D{"A4: is a neutral<br/>join line lawful<br/>in this market?"} D -- "CZ, pending counsel" --> E["Join line in e-receipt<br/>8-20% join"] D -- "PL, and DE later" --> F["Fiscal-only receipt<br/>join moves to Receipt QR<br/>1-4% scan"] E --> G["Join landing on the<br/>Guest's own device"] F --> G G --> H["Layer 1: join Loyalty Program<br/>= contract, gets points"] H --> I["Layer 2: marketing consent<br/>per channel, unbundled,<br/>double opt-in"] I --> J["Consent Record written<br/>with policy_text_sha256"] J --> K["Campaign eligibility<br/>re-checked at send time,<br/>never at Audience build"]

Identity capture and marketing consent are two different acts with two different lawful bases and two different UI surfaces. We capture Identity at the counter (fast, contract-basis) and we capture Consent on the Guest's own device (slow, evidenced, revocable). The Loyalty Program discount is the consideration for the Layer-1 contract and must not depend on Layers 2 or 3. Legal reasoning and per-market divergence live in 04 — Payments, fiscalisation, privacy and security; the product consequence is the diagram above, and it is non-negotiable.

#3.4 Card-linked identity: why it is v2 core and stays there

Per-Check it is the best mechanism in the product — 0 seconds, ~100% recognition, unifies the physical card with an Apple Pay DPAN. Per-base it is small: 3–12% of Members enrol [estimate], because it requires a deliberate "give us your card" act. At 20,000 Members that is 600–2,400 Guests. Assumption A6 (does Stripe Terminal emit PAR on card-present in CZ) is informational only: if yes, this moves to v1.1; if no, it stays v2 core and we do not switch PSPs for it. Adyen returns PAR on Terminal API [verified] and is the hedge, not the plan.


#4. Loyalty and promotions

#4.1 Mechanic inventory against canon tiers

MechanicTierServer-authoritativeOffline behaviourNote
Points (earn X per EUR)MVP (ledger) / v1 (full)YesEarn: trivial. Burn: needs leased floatBalances are materialised views over the append-only ledger, never the source of truth
Stamps ("buy 9 get 1")MVPYesSameThe mechanic CZ cafés actually ask for
Tiers (expiry, thresholds)v1YesRead-only cached tier is fineTier changes are ledger events
Vouchers, signed and offline-redeemablev1Issuance server-side; redemption must work offlineEd25519/COSE — §4.3The differentiator
Promo rules enginev1Both — identical evaluation on Device and serverMandatory offline§4.4
Cashback (money-value)v3YesBurn is real money; strict float capMoved out of v2 core by the P3 capacity cut (§8)
Subscriptions ("coffee club", EUR 19/mo)v3YesEntitlement check must work offlineHigh commercial value in CEE cafés, and the most painful thing in the v3 list. Moved out of v2 core by the P3 capacity cut (§8)
Brand Voucher (single-brand, face value, no top-up, no cash-out)v2 coreYesHard offline capReplaces gift cards entirely and stays outside the e-money perimeter
Referrals, surprise-and-delightNever in v1–v3Not in canon. Would need a written amendment
Gift cards / multi-merchant stored valueNeverBrand Vouchers replace them. Three legal opinions before a line of code, and we are not spending them

#4.2 The loyalty ledger

loyalty_event(id, guest_id, type ∈ {earn, burn, adjust, expire, void}, amount, unit, source_device, check_id, idempotency_key, occurred_at, recorded_at, prev_hash) — append-only, hash-chained per Guest. Hash-chaining is not paranoia: it is the answer to "is my Brno manager issuing himself points?", which is half of the canonical job-to-be-done (§10). Voids and refunds of a Check that carried a Discount emit a matching reversal event tied to the correction document.

#4.3 Offline redemption — the design

Vouchers are signed entitlements. They carry their own authority, so a Device with no WAN can verify and burn one.

Token (CBOR/COSE_Sign1, Ed25519, ~140–200 bytes → QR or PDF417):

FieldMeaning
vidUUIDv7 — time-sortable, so expiry pruning is a range scan
gidopaque Guest id
cidCampaign id
val{type: pct | abs | item | bogof, amount, currency, item_scope}
scpSite / Brand scope
nbf / expnot-before / not-after
cndrule-bundle id + rule id, evaluated locally
nonce, kid128-bit nonce; signing key id for rotation

Verification order on the Device:

  1. Signature against a pinned key set, shipped with the Menu Snapshot. A Device refuses a token signed by a kid it has never seen. No JWKS fetch at redemption time.
  2. nbf/exp against a trusted clock — last successful sync + monotonic uptime, not wall clock. If now − last_sync > 72 h, refuse any Voucher whose expiry falls inside the uncertainty window and tell the cashier why. Staff change Device clocks; this is not hypothetical. Related: canon invariant 4 already forbids closing or tendering on a Device more than 5 minutes from Coordinator time.
  3. Local spent-set — the vid table in the Device store. Canon cancels the LAN Bloom-filter gossip; T0 is single-station, so intra-Site double-spend is not reachable in v1.
  4. Condition evaluation through the shared rules evaluator (§4.4).
  5. Mark spent with an idempotency key, apply the Discount before fiscalisation, enqueue for upload.

Float leases for points and cashback. Balances change, so they cannot be signed into a token. The server grants a Device an offline_allowance = min(balance, tier_cap) (default EUR 20 / 2,000 points, per-Brand configurable) with an expiry and a monotonic lease_seq, refreshed on every online lookup of that Guest. On reconnect, burns replay by idempotency key against the ledger.

Overdraw policy — a per-Brand setting, never a hardcoded behaviour:

OverdrawDefault action
≤ EUR 5Absorb, log, count as marketing expense
EUR 5–25Balance goes negative; next earn repays first; notify the Guest neutrally
> EUR 25Negative balance + block further offline redemption for that Guest + ops alert
≥3 events / 90 daysFraud queue; block offline redemption for that Guest globally

The residual risk, bounded rather than eliminated — say this out loud in the deck, because competitors handwave it and lose deals to "what if the internet goes down":

annual loss ≈ Vouchers_issued × P(redeemed) × P(both Sites offline simultaneously) × P(fraud attempt) × face value

500,000 Vouchers/yr × 12% × 0.3% × 2% × EUR 4 ≈ EUR 14/year [estimate].

#4.4 The promo rules engine — cut from a DSL to a config schema

Canon: ~8 rule types, one shared JSON spec, 500-case golden corpus in CI. Not a DSL, not portable bytecode, not a Rust/WASM core. The 500-case corpus is the part that earns its keep; the DSL is the part that eats 3 eng-months and ships a divergence bug.

The eight rule types: cart total threshold · Item/category quantity · time-of-day and day-of-week · Site scope · channel · Member tier · first-visit / nth-visit · Voucher present. Plus three orthogonal controls: max uses (per Guest / per Business Day / global), exclusivity group, priority.

RequirementRule
Single implementationThe evaluator lives in the shared Kotlin :domain module and runs identically on the Device and the backend. Two implementations diverge; you find out from a Customer. See 02 — System architecture
Bounded evaluation≤15 ms per Check recompute on the slowest certified SKU. No unbounded loops during a lunch rush
StackingDo not promise a global optimum. Exclusivity groups + explicit priority + a bounded search over at most K=6 candidate combinations, and say so in the UI: "applied: 2 of 3 eligible offers"
Fiscal orderingThe Discount is present on the Fiscal Document at issue. Loyalty applies before fiscalisation, so identity must be captured at order or payment time, never after. Post-hoc goodwill is a new Voucher for the next visit, never a retroactive edit to a closed Fiscal Document

CZ has no fiscalisation [known], so the fiscal ordering constraint is free in the beachhead — and it is load-bearing in UA and PL at P3, which is precisely why it must be designed in now rather than retrofitted. VAT treatment of a points redemption versus a zero-price Line differs and is a per-market counsel question (canon P0 exit gate); see 04 — Payments, fiscalisation, privacy and security.


#5. The marketing engine and closed-loop revenue attribution

#5.1 The six templated flows (v1) — and why they are templates, not a builder

Canon deletes the visual journey builder. Six configurable templates cover the value; a builder wins RFPs we are not in.

#FlowTriggerChannelTypical measured lift [estimate]
1Welcome / first rewardJoinEmail (+ Wallet pass at v2 core)25–40% redeem; drives the 2nd visit
2Post-visit thank-you + 1-question ratingCheck closed + 2hSMS or email3–10% response
3Lapsed winbackGuest crosses lapse thresholdSMS > email2–6% incremental visit rate
4BirthdayBirthday − 5d, Voucher expires +14dSMS15–30% redemption; highest-margin flow in the set
5Lapsed VIPTop-decile spender crosses lapseA manual task to the Site manager, not a sendSmall n, large value. A phone call beats a Voucher
6Second-visit nudge1 visit only, +10dSMSConverting 1→2 visits is the biggest lifetime-margin lever in the system

Lapse threshold is Guest-relative, not a fixed 90 days. A café regular is lapsed at 10 days; a lunch-only office worker is not lapsed at 21. Use the Guest's own inter-visit p80 where n≥4, falling back to the Site's p80. This is a two-day statistics job that makes flows 3 and 6 materially better, and it is inside the flow budget.

Guardrails in the engine, not in a runbook: global frequency cap (default 4 marketing messages/Guest/month), quiet hours (21:00–09:00 local forbidden by default), channel-priority fallback (cheapest capable channel first), per-Campaign spend cap in EUR, and consent re-checked at send time, never at Audience build — a 4-hour-old Audience contains people who withdrew 3 hours ago.

#5.2 Channels and their real unit costs

ChannelTierUnit costNote
SMS via a CZ local aggregatorv1~EUR 0.035–0.050/segment [estimate]Local aggregators run 40–70% under global CPaaS at volume [estimate]. Twilio is failover only
Email (SES or Brevo)v1~EUR 0.0001–0.001/message [known]Effectively free. Deliverability is the cost — per-tenant sending subdomains with their own DKIM/SPF
Manager task (flow 5)v1EUR 0The highest-value channel in the product and nobody prices it
Wallet pass push updatev2 coreEUR 0The best free retention channel that exists, and canon defers it
WhatsApp (Meta Cloud API)v2 coremarketing ~USD 0.01–0.14/msg by market [verified 2026-08]Per-tenant senders only. Template pre-approval per template
Viber Business Messagesv2 coreEUR 100/mo minimum per account, EUR 150 for UA and several CEE markets [verified 2026-08]Per-tenant senders only. Aggregating one sender across PL tenants exposes us to PKE art. 398 fines scaled to our revenue [verified]
Push (FCM/APNs)NeverfreeRequires a Guest app. Canon prohibits per-restaurant native apps (Apple Guideline 4.2.6)

#5.3 Closed-loop attribution: the mechanism

Everyone in this category says "closed-loop attribution" and means "we counted Voucher redemptions". Redemption counts overstate value massively, because most redeemers would have come anyway. Here is what we actually do, and it is only possible because we own the till.

flowchart TD A["Campaign defined<br/>lapse rule + offer + window"] --> B["Audience materialised<br/>and FROZEN as rows"] B --> C["bucket = hash(guest_id || campaign_id) mod 100<br/>stratified by RFM decile and home Site"] C --> D["Holdout: buckets 0-9<br/>receives nothing"] C --> E["Treated: buckets 10-99<br/>receives the send"] E --> F["Send-time consent re-check<br/>per Identity, per channel"] F --> G["Channel cost accrued<br/>per message, per Campaign"] D --> H["Attribution window<br/>default 14 days"] G --> H H --> I["Outcome = POS Check margin<br/>joined on guest_id<br/>NOT clicks, NOT redemptions"] I --> J["Wait window_end + 48 h<br/>for late-arriving offline Checks"] J --> K["Incremental revenue =<br/>(mean margin Treated - mean margin Holdout) x n Treated<br/>bootstrapped 95% CI"] K --> L["Net = Incremental revenue<br/>- channel cost - Discount cost"]

Mechanism, step by step:

  1. Randomised Holdout at Audience-build time. bucket = hash(guest_id || campaign_id) mod 100; buckets 0–9 are the Holdout (configurable 5–20%). Stratify by RFM decile and home Site so the two arms are balanced on the two variables that dominate spend.
  2. Freeze the Audience as rows: (campaign_id, guest_id, variant, bucket, rfm_decile, site_id, assigned_at). Reproducible for audit and for a data-subject request ("why did I get this?").
  3. Attribution window, default 14 days. 7 for coffee, 30 for a lunch concept.
  4. Outcome is POS margin, joined on guest_id — visits, gross revenue, Discount cost, gross margin from the Menu Snapshot's category-level costing. Not clicks. Not redemptions.
  5. Incremental revenue = (mean margin Treated − mean margin Holdout) × n Treated, reported with a bootstrapped 95% CI and net of channel cost and Discount cost. The canonical term is Incremental revenue; "attributed revenue" is banned from the UI and the deck.
  6. Power check, shown before send. Below ~1,500 Treated Guests most Campaigns are statistically undetectable. Show that. "This Campaign is too small to measure; we will pool it with the next three sends into a rolling estimate" is a trust-building message and no competitor ships it.
  7. Rolling programme-level Holdout. A permanent 3–5% of the base receives no marketing at all, so we can answer "is the loyalty programme worth its fee?" with a number rather than an anecdote. This is the strongest renewal argument in the category.
  8. Late Checks. An offline Device may land a Check four hours late. Recompute on a rolling 48-hour lag; never report a final number before window_end + 48 h.

#5.4 The arithmetic a founder can quote in a demo

4-Site Prague chain, ~17,000 Members at 12 months, lapsed Audience of 5,000. Holdout 10% → 500 Holdout / 4,500 Treated. All figures [estimate]:

LineValue
SMS send, 4,500 × EUR 0.042−EUR 189
Offer: free pastry, COGS EUR 0.55, redeemed by 158 Guests−EUR 87
Measured incremental visits = 3.5% of Treated = 158
Incremental gross margin: 158 × EUR 6.80 × 68%+EUR 730
Net Incremental revenue+EUR 454 on EUR 276 of spend

And the honesty that closes the deal: with 4,500 Treated against a 500-Guest Holdout, the minimum detectable effect on mean margin per Guest is roughly 12–18% relative [estimate]; raising the Holdout to 20% (900 Guests) tightens it to ~9–13%. The product says that out loud, before the send, and offers to pool. This computed automatically, per Campaign, is the product.

#5.5 What is deliberately not in the marketing engine

Segmentation query-builder UI, campaign power-analysis UI and multi-channel adapters beyond one SMS aggregator + email are v1-Full only — the S1 cut ships the six templates, a fixed lapsed/VIP/birthday/first-visit segment set, and a support-console CSV path for anything else. For S1, which never builds the Full tier, three of those deferrals are repaid inside v2 core at P3 because v2 core depends on them: the segmentation query-builder UI (+2.5) and CRM reporting (+1.5), without which journeys and RFM/lapse modelling have no surface, and the multi-channel adapter framework (+1.5), without which WhatsApp and Viber cannot be added. The campaign power-analysis UI (+1.0) and the Guest Profile at point of service (+1.0) are not repaid — they stay deferred past P3 in the S1 plan. BTYD/CLV modelling and the visual journey builder are deleted, not deferred: r06 concedes BTYD is junk below 6 months of history or 500 repeat Guests, which describes every Customer we will have before M31.


#6. Reservations, table management, feedback

CapabilityVerdictReasoning
Reservations / table management (floor plan, pacing, covers caps, table combination, no-show deposits, channel ingestion)Never build. Integrate.~6 eng-months for a worse product than TheFork/OpenTable/SevenRooms, which also send inbound demand we cannot. It closes no counter-service deal. Canon: "Integrate, never build"
Minimal waitlist + walk-in SMSDecision recorded: out of scope in MVP / v1 / v2 core / v3. ~1.5 eng-monthsNamed trigger: a signed or renewing chain of ≥4 Sites makes it a written condition of purchase, or the ICP moves off counter-service. Until one of those fires it needs no sprint plan and no amendment — it is irrelevant to counter-service, which is the whole ICP
Guest Profile at the point of service (notes, VIP flag, lifetime margin, last 5 Checks, last 5 Lines)v1 Full (1.0 eng-months in domain G)Cheap, disproportionately loved, and it is what a manager uses to recognise a regular. Cut from the S1 cut
Allergy / dietary notes on the ProfileNot in any tier. Do not buildThese are health data with a distinct lawful basis, a separate access role, exclusion from segmentation, and a shorter retention. See 04 — Payments, fiscalisation, privacy and security. Allergens on the Menu (EU FIC 1169/2011) are a different thing and are in v1
Feedback / NPS beyond flow 2's single ratingNot in any tierThe 1-question rating inside automation flow 2 is inside the flow budget. Decision recorded: detractor routing to a manager task with an SLA timer plus a service-recovery Voucher (~1.0 eng-months once the Voucher and task primitives exist) is out of scope in MVP / v1 / v2 core / v3. Named trigger: ≥3 paying Customers ask for it in writing, or one renewal is made contingent on it. It stops floating as an unfunded candidate
Review managementv1 via Google Business Profile only (domain I)Gated on assumption A14 (allowlist, applied M1, due M6). If refused, this degrades to manual paste and has no engineering workaround. TripAdvisor/Yelp/Facebook are partner-gated and out of reach. See 06 — Integrations

One compliance-shaped product rule that is genuinely a competitive attack, not a constraint: do not gate review solicitation. Asking only happy Guests for a public review breaches Google's policies and the EU Unfair Commercial Practices Directive as amended [known]. Solicit from everyone; handle detractors privately first. Several incumbents in this category ship review-gating as a feature.


#7. AI: what is differentiating, what to skip

All model routing goes through the internal ai-gateway; nothing else imports the SDK; every feature carries a global spend cap that a human must raise, on top of per-tenant caps.

#7.1 Build

FeatureModelTierCostWhy it earns its place
AI tier-0 support agent with Device telemetryclaude-sonnet-5v1 (domain H)~EUR 0.10–0.40/Site/mo [estimate]The highest-value AI in the whole product and it is not a CRM feature. An agent that reads printer/reader/sync telemetry and files the ticket before the restaurant calls turns the coverage floor from "a human on a phone at 22:00" into "a human reviewing a queue at 09:00" — the difference between 8–11 support FTE and 3 plus escalation at 500 Sites. See 07 — Roadmap, Team and Budget
Menu ingestion from PDF/photo with citations + confidence + human reviewclaude-opus-5MVP (domain B)~USD 0.53/menu at 30k in / 15k out [known rates, estimated tokens]Onboarding labour 22–55h manual → 16–34h. ~40% reduction, not 80%. Never auto-publishes
POS-migration ingestion (Dotypos export → configuration)claude-opus-5v1 (domain I)same pipelineConverts "I'll lose 3 years of history" into a 30-minute onboarding step. This is the switching-cost killer
Google review reply drafting, brand voice, human approvesclaude-sonnet-5v1~USD 0.003/review [estimate]Hard rule: never auto-send below 4★. A bad auto-reply to a 1★ review is a screenshot
Campaign copy drafting for the six flowsclaude-sonnet-5v1~USD 0.01–0.03/Campaign [estimate]Cheap bolt-on. The plumbing is the product; the copy is a convenience
NL analytics narrative over the incrementality reportclaude-opus-5 tool-calling / claude-sonnet-5 prosev1 Full~USD 0.01–0.05/question [estimate]Tool-calling over a fixed, parameterised, tenant-scoped metric layer. Text-to-SQL over the open schema is prohibited
Anything nightly (digests, review drafts, narratives)Batch APIv150% off [known]

Steady-state AI spend: EUR 0.30–1.50/Site/month plus EUR 0.50–3.00 one-off per onboarding, against the canonical EUR 110–130/Site/month subscription-only ARPU (POS+CRM+KDS; it excludes payments residual and messaging). AI cost is not a risk. AI correctness is.

#7.2 Skip

FeatureWhy not
AI-generated Audiences from prose ("customers who like coffee and haven't come lately")Text-to-SQL with better marketing. Prohibited by canon. The segmentation UI is a parameterised builder or it is a CSV
Per-Guest AI-chosen offer valueAutomated decision-making with plausibly significant effects, and it is per-Guest dynamic pricing by another name. Do not build without counsel
BTYD / CLV / churn-probability scoringDeleted by canon. Junk below 6 months of history and 500 repeat Guests
Agentic back-office ("draft Monday's order")v3, funded only. Not in any canon tier. The risk is an agent placing a wrong EUR 4,000 order
Voice ordering, phone or drive-thru6–10 eng-months for phone, 12+ for drive-thru with a public failure record. Not in this plan
AI-drafted replies auto-sent below 4★See above. This is a policy that must be enforced in code

#8. The full feature map

Tiers match canon §4 exactly, including canon's split of the old v2 tier into v2 core (the P3 build: T2 LAN multi-station, UA and PL fiscal, guest web ordering + QR PWA + Wallet passes, card-linked identity, Brand Vouchers, WhatsApp/Viber, segmentation and journeys, and the payments-attach machinery) and v3 (P4 and later). Anything not listed in a tier is out of scope for that tier. There is no implicit inclusion.

Why the split, in CRM terms. The full v2 tier is +86.5 → +96.5 eng-months and remains the canonical S2 figure; S2 ships all of it in the same phase on ~132 eng-months/yr. S1's P3 (M32–M46) has 58–65 eng-months, so S1 ships v2 core only. What moved to v3 is table service (courses, seats, splits by item/seat, merges, transfers, floor plan), the handheld waiter app, the customer-facing display, Apps-on-Devices, the HubRise-resold delivery module and any native Glovo connector, DE+AT fiscal, the second cell — and, from this document, cashback and subscriptions. Table service and the handheld serve a different ICP than canon §1's "counter-service only, single-station first" and cannot be funded before the counter-service base is at ~150 Sites. The two things load-bearing for P3's own exit gate are kept: Site count (T2 LAN plus the two second markets) and identity capture / CRM renewal (guest ordering + QR PWA + Wallet passes). Cutting either of those would break a thesis this deliverable states as proven elsewhere — §3.2's route from ~35% to ~52% (v2 core) capture, and §10's renewal conversation. Cutting full-service does not.

CapabilityMVP (P1 pilot)v1 sellable (S1 cut)v1 Full (S2 only)v2 core (P3)v3 (P4+)Never / trigger
Identity
Deterministic Identity: phone E.164, email, loyalty_id
Cluster derivation, merge/unmerge with audit
Merge review-queue UIS1 cut ships CSV + support console
Card-linked identity (PAR, enrolment-only)
Probabilistic matching (name, address, birthday, device)Never until 500 Orgs + labelled eval set
Household groupingNever — not in canon
Capture
POS phone lookup + capture
SMS e-receipt with join lineGated on A4
Receipt QR join landingMandatory fallback if A4 fails
Consent-gated dual Receipt rendererdomain F
QR PWA ordering / own web orderingdomain C
Wallet passes (Apple + Google)
Self-order kiosk promptNever in this plan
Wi-Fi captive portalNever
Plastic card issuanceNever (scan migrated barcodes only)
Loyalty
Points + stamps ledger, append-only
Hash-chained ledger, tiers, expiry, materialised balances
Signed offline-redeemable Vouchers (Ed25519/COSE)
Float leases + overdraw policy + fraud caps
Promo rules engine, 8 rule types, 500-case corpus
Cashback, subscriptionsMoved to v3 by the P3 capacity cut
Brand Vouchers
Gift cards / multi-merchant stored valueNever
LAN Bloom-filter spent-set gossipNever — cancelled, EUR 14/yr exposure
Consent and rights
Append-only Consent Record + versioned policy text
Consent & preference centre, double opt-in, per channel/purpose
DSAR / erasure with offline tombstone propagation + per-Device ackCannot be deferred; retrofit is a rewrite
Cashier-consent buttonNever — no enum value, API rejects it
Marketing
Six templated automation flows + guardrails
One SMS aggregator + email transport
Holdout-based incrementality reportingThe headline feature
Programme-level rolling Holdout (3–5%)The renewal argument
Segmentation query-builder UI
Campaign power-analysis UIS1 cut shows a static warning threshold; not repaid at P3 in the S1 plan (§5.5)
Multi-channel adapters beyond 1 SMS + email
WhatsApp / Viber, per-tenant sendersNever aggregate one sender across PL tenants
RFM / lapse modelling, journeys, CRM reportingGuest-relative lapse threshold is in v1 inside flow budget
Visual journey builderDeleted
BTYD / CLVDeleted
Audiences pushed to Meta / Google AdsNever in this plan
Service and feedback
Guest Profile at point of serviceCut from the S1 cut and not repaid at P3 (§5.5)
1-question rating inside flow 2
Detractor routing + service-recovery VoucherOut of scope, decided (§6) — trigger: ≥3 paying Customers ask in writing, or a renewal is contingent on it
Google Business Profile review inbox + AI repliesdomain I, gated on A14
TripAdvisor / Yelp / Facebook reviewsNever
Reservations / table managementNever build. Integrate
Minimal waitlist + walk-in SMSOut of scope, decided (§6) — trigger: a ≥4-Site chain makes it a written condition of purchase, or the ICP moves off counter-service
Allergy / dietary notes on the ProfileNot in any tier
Structural
In-app staff self-onboarding / trainingDecided: v1 Full at 1.5 eng-months, deferred in the S1 cut — domains C/H, not G. The A20 mitigation in 08 — Pros, cons, difficulties and risks
Per-tenant controllership (model A), schema partitioned for B
Cross-brand consumer wallet / network (model B behaviour)Never in v1–v3
Per-restaurant white-label native Guest appsNever (Apple Guideline 4.2.6)
Partner / reseller portal200 Sites

Three items that were floating outside every tier now have a written decision, because they were being costed in one document and ignored in another:

  • In-app staff self-onboarding / training — IN, at v1 Full, 1.5 eng-months, deferred in the S1 cut. It is the only real mitigation for 60–100%/yr front-of-house churn and A20 (blended ticket rate ≤1.4/Site/month) depends on it, so the S1 deferral is a named, accepted risk rather than an oversight: in the S1 cut, retraining is the installer runbook plus the AI tier-0 agent (§7.1). Effort sits in domains C/H, not G, so it does not move the canon §5.1 domain-G totals in §11.
  • Detractor routing + service-recovery Voucher — OUT of MVP / v1 / v2 core / v3 (~1.0 eng-months). Trigger: ≥3 paying Customers ask for it in writing, or one renewal is made contingent on it.
  • Minimal waitlist + walk-in SMS — OUT of MVP / v1 / v2 core / v3 (~1.5 eng-months). Trigger: a chain of ≥4 Sites makes it a written condition of purchase, or the ICP moves off counter-service.

#9. Competitors: features and price

VendorPriceOwns the POS?Offline redemptionHoldout attributionCZ / CEE reachThe gap we attack
Us (POS + CRM + KDS)EUR 79 POS + EUR 39 CRM + EUR 25/KDS station = EUR 118 for a Tier A Site; subscription-only ARPU target EUR 110–130 (POS+CRM+KDS, excluding payments residual and messaging)YesYesYesCZ first
Storyous (Teya)390 / 690 / 890 / 2,990 CZK per month ex VAT by tier ≈ EUR 16 / 28 / 36 / 120 [verified 2026-08, secondary CZ sources]YesUnknown, assume partialNoCZ/SK/PL incumbentAcquirer-owned, frozen roadmap. Loyalty attach unclear. Teya owns them, so Teya cannot be our payments partner (A3)
Dotykačka / Dotypos (Teya majority)Monthly licence banded by card turnover, + Dotypay 0.79–0.99% per transaction, + HW 5,990–22,990 CZK ex VAT [verified 2026-08]YesUnknownNoCZ/SK/PL incumbentTheir effective software price for a card-heavy Site trends to zero. Our EUR 118 bundle is priced against a competitor whose list price falls as our payments thesis succeeds. This is the sharpest commercial risk in this doc — see 08 — Pros, cons, difficulties and risks
Syrve / iiko (iikoCard)~EUR 30–120/Site/mo by module [estimate]YesYes — do not assume weakNoThe real CEE/UA incumbentDated UX, complex config, no incrementality measurement, weak modern automation
Poster POSUSD 29 Mini / 49 Business / 69 Pro [known]YesBasicNoUA/PL/CZLoyalty is points-and-discounts. Proof the UA ARPU ceiling is EUR 30–60
Square LoyaltyUSD 45–49/Site/mo, bundled into Square Plus since the 2025 repricing; no volume discount across Sites [verified 2026-08, secondary]YesNoNoNot CZPoints/stamps only, POS-locked
Toast Loyalty / MarketingLoyalty not sold standalone; Marketing Essentials USD 185/Site/mo incl. loyalty + gift + email + SMS [verified 2026-08, secondary]YesNoNoUS/CA/UK/IE — absent from CEEBasic marketing tooling, no incrementality. Their absence is the market opening
PiggyUSD 79 / 299 / 849 per month + enterprise [verified 2026-08]NoNoNoNL/EU, hardware-agnosticShallow POS integration, generic retail, no Check/covers semantics, no offline guarantee
Flipdishfrom EUR 49/mo annual (69 monthly) ordering site; EUR 79/99 with app [verified 2026-08]Ordering-firstNoNoIE/EULoyalty is a feature of ordering, not a CRM. But note: ordering-first is the honest fallback wedge if A1 fails
SevenRooms~USD 300–500/Site/mo small groups → 500–1,000+ enterprise [estimate — indicative, custom-quoted, no published list price]NoNoNoGlobal, full-serviceReservation-led, thin loyalty ledger, no POS ownership
PAR Punchh / PaytronixCustom enterprise; category commonly EUR/USD 1,500–5,000+/mo per brand [estimate — indicative, no published list price]NoNoPartial (enterprise services)US-centricEnterprise-only motion, integration-dependent, no EU privacy story
ComoCustom, none published [known — no published list price]NoNoNoIL/global incl. some CEEDated app UX; per-brand app economics fail below ~10 Sites
UDS~EUR 30–100/Site/mo [estimate]NoNoNoUA/CISA consumer network — their app, not the Customer's brand. Structurally different product; the only CEE player whose model beats ours on Guest acquisition

The intersection nobody occupies: POS-owned, offline-correct loyalty with Holdout-based incremental revenue reporting, at EUR 39/Site/month for the CRM on top of a EUR 79 POS, in Czech. The nearest credible threat is Syrve/iiko extending its marketing module, and the structural threat is Dotykačka's turnover-banded pricing, not a feature.


#10. The one painful job-to-be-done we win on first

"I own four cafés. I cannot tell you which of my regulars stopped coming, and I cannot tell you which of my four managers is discounting."

Both halves are the same data problem and both are unavailable from Dotypos or Storyous today. Both are unsolvable with a spreadsheet. Both require owning the Check.

The demo that closes it is 90 seconds and uses only v1 sellable:

  1. Screen one — the lapse list. 214 Guests whose own inter-visit p80 has been exceeded, ranked by trailing 12-month margin, with the last 5 Lines each. Not "customers", not a count: names, phones, what they used to order.
  2. Screen two — the Discount and Comp report by Site and by employee. Comps carry an actor and a reason; Discounts reduce the taxable base and are on the Fiscal Document. The manager at Site 3 is at 4.2% of net sales against a 1.1% chain average.
  3. Screen three — last quarter's Incremental revenue. "Your loyalty programme generated EUR 4,120 of incremental gross margin last quarter, measured against a randomised Holdout, net of send and Discount cost, 95% CI EUR 2,380–5,860." No competitor in CEE can produce that sentence.

Screen three is also the renewal conversation, which is why the programme-level Holdout is v1 and not v2 core: at month 13 the Customer asks whether EUR 39/month for the CRM was worth it, and we either have a number or we have an anecdote.

What we do not claim: we do not claim card payments work offline (they do not — the claim is "orders, kitchen, cash and receipts keep working with no internet; card depends on your acquirer"), we do not claim 52% identity capture at v1 — 52% is the v2-core number and 30–38% / 22–28% is v1 (see §3.2), and we do not sell the attribution feature to a single-Site café, because below ~1,500 Treated Guests our own product will tell them to cancel.


#11. Effort, and the assumptions this document rests on

CRM domain (G) against canon §5.1. These sum to the canonical totals; do not re-derive them.

Work itemMVP→ v1 S1 cut→ v1 Full→ full v2 (canon)
Deterministic Identity, normalisation, Cluster, merge/unmerge audit0.8+0.5+1.0 (review queue UI)
Capture surfaces (POS lookup, SMS e-receipt, QR join landing)0.5+1.5
Loyalty ledger (append-only → hash-chained, tiers, expiry, balances)0.8+1.8+1.0 (Brand Vouchers — v2 core)
Signed offline Vouchers, float leases, reconciliation, fraud caps+3.2+2.0 (cashback, subscriptions — v3)
Promo rules engine + 500-case golden corpus+2.0
Consent Record schema → full consent & preference centre0.4+1.2
DSAR / erasure with offline tombstones + per-Device ack+2.8
Six templated flows + guardrails+1.3+1.5 (branching)+1.5 (journeys, RFM)
Channel abstraction + 1 SMS aggregator + email+0.7+1.5 (extra adapters)+2.0 (WhatsApp, Viber)
Holdout attribution + incrementality reporting+1.5+1.0 (power-analysis UI)
Segmentation query-builder UI+2.5
Guest Profile at point of service+1.0
CRM reporting (cohorts, retention, Campaign P&L, Member vs non-Member basket)+1.5
Wallet passes (Apple PassKit + Google Wallet, push, per-tenant certs)+2.5
Card-linked identity (PAR enrolment, one PSP)+2.0
Domain G total (canon)2.519.029.0+11.0

The +11.0 is the full-v2 CRM delta, which only S2 carries. Split across the two tiers of §8: v2 core = +9.0 (Brand Vouchers +1.0 · journeys and RFM +1.5 · WhatsApp/Viber +2.0 · Wallet passes +2.5 · card-linked identity +2.0) and v3 = +2.0 (cashback and subscriptions). For S1 specifically, P3 must also repay the three v1-Full CRM deferrals that v2 core depends on (§5.5) — segmentation query-builder UI +2.5, CRM reporting +1.5, multi-channel adapters +1.5 — so S1's CRM cost at P3 is 9.0 + 5.5 = 14.5 eng-months, not 9.0. That 14.5 is derived from the rows above, not from canon; canon states only the +11.0 full-v2 delta. See 07 — Roadmap, Team and Budget for whether P3's 58–65 eng-month S1 envelope absorbs it.

Guest web ordering, the QR PWA and the KDS/POS surfaces that carry capture are domain C, not G. Menu ingestion, the metric layer and NL analytics are domain B. The AI tier-0 support agent is domain H. See 07 — Roadmap, Team and Budget for the roll-up.

Assumptions this document depends on, with the owner who must resolve them:

#AssumptionDueIf false, in CRM terms
A1CZ counter-service 2–8 Site chains pay ≥EUR 89/Site/moP0 exit (M4)The CRM is the reason they pay above EUR 79. If A1 fails, the honest alternative is first-party ordering + loyalty for delivery-heavy concepts — 100% identity on 20–40% of revenue, no POS
A4A neutral join line in a transactional e-receipt is lawful in CZM2Blended capture drops from 30–38% to 22–28% at v1. The CRM value case weakens and the Receipt QR becomes the primary join surface at 1–4% scan
A5PL forbids that line, and forbids one aggregated Viber/SMS sender across PL tenantsbefore P3 PLAlready assumed true. PL e-receipts are fiscal-only; PL tenants hold their own sender or the channel is not offered
A6Stripe Terminal need not emit PAR for v1M1Informational. If yes, card-linked identity moves to v1.1; if no, it stays v2 core and we do not switch PSPs for it
A14Google Business Profile allowlist grantedM6Review management degrades to manual paste. Drop it from v1 sellable; there is no engineering workaround
A20Blended support ticket rate ≤1.4/Site/month through 24 monthsP2 exitThe AI tier-0 agent (§7.1) is the single largest lever on this and is the reason it is a v1 item