Restaurant DX InsightsPublished: September 14, 2026
By Rezabo Editorial Team

Peak-Time Table Allocation and Turnover: Timetable Design That Lifts Bangkok Seat Utilization to 89%

Peak-Time Table Allocation and Turnover: Timetable Design That Lifts Bangkok Seat Utilization to 89%

BLUF: Full at 19:00 is usually a dead 75-minute gap, not a turnover problem

A Bangkok room that is “full” on Friday at 19:00 and still misses the night’s sales target is rarely failing on check average or on server speed. The leak is the 60- to 75-minute hole that a rigid 120-minute block leaves between bookings, plus hosts who glue two two-tops into a four-top and then starve the next deuce. The loss is not the party you turned away on the phone. It is the 18:00 walk-in you refused because a 19:30 name sat on the same chair. On a 42-seat floor, Friday and Saturday nights alone put that gap-refusal, late-hold, and over-merge stack in the 180,000 to 240,000 THB a month band. Seat utilization moves from about 71% to 89% when you stop rushing guests and instead sell dwell as SKUs, inventory a 75-minute early window, auto-release no-contact lateness at 15 minutes, and allow only merges that preserve residual two-top stock. A two-sitting grid that ignores kitchen fire rate will print a prettier turn number and then blow the second sitting with late appetizers.

1. Count the loss as a dead 75 minutes, not as a nightly turn rate

Thong Lo Soi 13 and Phrom Phong Soi 39 rooms of 40 to 48 seats (yakiniku, washoku, izakaya) pack demand from 17:30 to 22:30 on Friday and Saturday. Japanese corporate entertainment wants a private room and a ranked seat, and it stays 135 to 150 minutes. The same night, a two-top happy-hour walk-in, a Hungry Hub 90-minute package, and an Instagram tourist four-top hit the host stand together. If the only tool is a paper calendar that writes “120 minutes per table,” or a LINE reply that records a start time and nothing else, those four demands are treated as the same inventory object. They are not.

Turn rate (covers divided by tables divided by hours) is the wrong decision unit. Four layers move P&L.

  1. Gap refusal. A four-top booked at 19:30 causes the host to refuse a four-top walk-in at 18:00. The chair sits empty for 90 minutes. At 2,050 THB average check, four covers is 8,200 THB of demand. A 75-minute early SKU would have recovered about 6,000 THB of it.
  2. Two-top starvation. Two deuces are pushed together for a four-top banquet. The next two-top is sent to the terrace or turned. You booked one four-top and discarded two two-tops whose checks are equal or higher.
  3. Late hold. An unanswered 19:00 booking is held for 30 minutes. The walk-in at the door is told the house is full. If the booking never arrives, the chair is empty. If it arrives, the 21:00 sitting starts late and the rest of the night slips.
  4. Kitchen simultaneous fire. Sitting one at 17:30 and sitting two at 19:30 with zero reset looks like two turns on a spreadsheet. On the floor the first party is still on dessert at 19:28, the second sits at 19:35, and course appetizers stack at 19:50. Dwell overruns because of ticket lag, not because of a seating rule.

Place the arithmetic on a 42-seat mix (eight two-tops, four four-tops, two combinable sixes), 2,050 THB dinner check, eight Friday-Saturday peak nights, twelve weekday dinners.

  • Gap refusal (Fri-Sat): 3 tables × 8 nights × 3.2 covers × 1,650 THB (early check) = 126,720 THB
  • Late-hold walk-in miss: 1.5 tables × 8 nights × 3 covers × 2,050 THB × 0.5 resale probability = 36,900 THB
  • Over-merge of two-tops: 6 parties/month × 2 covers × 2,050 THB = 24,600 THB
  • Weekday dead time: 12 nights × 1 table × 3 covers × 1,850 THB × 0.5 = 33,300 THB

Subtotal about 221,520 THB. Hosts who read the book as “nothing is free” and refuse four more parties (4 × 3 × 2,050) push the month near 246,000 THB. That is the body of a house whose cover count has stalled even though seat count has not changed.

Align the utilization denominator. The dinner window 17:30 to 22:30 is five hours. Forty-two seats give 210 seat-hours. Occupancy at 71% is 149.1 seat-hours. Occupancy at 89% is 186.9. The 37.8 seat-hour gap, at 105 minutes average dwell (1.75 seat-hours) and 70% fill of that gap, is about 15 covers a night. Eight Friday-Saturday nights is 120 covers. At 1,900 THB that is 228,000 THB. Live shops land in the +180,000 to +240,000 THB band because the early SKU prices below prime and not every gap fills. 89% is not “every chair occupied at every minute.” It is the ceiling after you keep a 15-minute reset and a long corporate private-room SKU, and after you stop gap refusal, late holds, and bad merges.

2. Floor notes, Reddit, and X describe the same seating failure

Shop notes and public threads point at one structure.

On the floor. At 18:05 the terrace host takes a four-top walk-in. The register notebook says “19:30 Tanaka 4 T12.” The host refuses: “this table is reserved.” T12 is empty from 18:00 to 19:25. The same night two two-tops are merged for a six-top, and the 21:00 deuce waits in the rain. An unanswered 19:00 booking is held until 19:28. A three-top at the door leaves.

On the diner side. r/restaurateur repeats the same Friday-Saturday wait tactics: cap parties over four at peak; mixed two-, four-, and six-tops plus a step and a wheelchair path mean many “free” chairs cannot actually combine. The report that follows is always the same: merge two deuces, take the four-top, then watch two-top wait times explode because the graph has no two-top stock left. r/Serverlife questions the house rule “everyone gets two hours.” Small checks and large checks consume the same seat-hour, so RevPASH (revenue per available seat-hour) falls. r/FoodNYC and r/TalesFromYourServer regularly carry guests who were told, after they sat, that they had 60 to 90 minutes. The anger is not the limit. It is that the end time was missing from the booking screen. r/KitchenConfidential reports the night two sittings are stacked with zero buffer: appetizer tickets fire together, the floor says “the table is free,” and the line is not. r/Bangkok and r/ThailandTourism scatter “LINE stayed read, we arrived, the previous party was still there” and “they quoted 45 minutes for a walk-in, then seated a reservation that was 20 minutes late.”

On the operator side (X). Bangkok Japanese rooms post in the late afternoon: “reservations full after 18:30, walk-in until about 18:00.” As demand copy that is honest. The shop problem is that many of those rooms do not list the 18:00 window as inventory in the booking engine. Accounts that do announce walk-in start times get the follow-up “it was full, we waited a little, we sat.” Those floors run bussing and a 15-minute release. Floors that cannot sit the wait are running a grid that stores start time only: no end time, no reset slot. Thai-language guests already accept รอบแรก / รอบสอง (first sitting / second sitting). What they reject is a first sitting whose end time is missing from the confirm screen and is then spoken at the table. Expat posts on delay from the motorway or a squall are a weekly pattern. If “we hold if you message, we resell at 15 minutes with no contact” is not in the completion bubble, the host reverts to a 30-minute courtesy hold, and the rule changes by shift.

The guest implication is simple. People want the chair. They refuse (a) being turned when a short window exists, (b) a merge that makes the next two-top impossible, (c) a late reservation that punishes the walk-in, and (d) an end time they cannot see before they confirm.

3. Feature, fee, and limit comparison: five ways to “raise turnover”

“Install a system that raises turnover” is a slogan. Implementation decides Friday 19:00 gap recovery and monthly seat-hour yield. Conversion used below is 1 USD = 34 THB, 1 JPY = 0.23 THB. Fees are August 2026 published lists and the Japan seat-band examples that still circulate. Thai quotes on foreign engines are custom.

Dimension Paper + flat 120 min Spreadsheet / color calendar Foreign engine + OTA sync (TableCheck class) SEA public SaaS (Eat App class) Rezabo dynamic-slot ledger
Book of record Handwritten start time. End time lives in someone’s head Merged cells. Simultaneous edits overwrite Chart UI. OTA package dwell is a separate contract Table × time grid. WhatsApp conversations capped monthly Cloud table × 15-min slot. LINE intake is the same event
75-minute gap Invisible. Walk-in refused Visible if colored. No sales channel Pacing rules exist. Hungry Hub 90 min plus à la carte 120 min recreates the gap Vacancy is visible. Thai LINE and PromptPay are a separate build Vacancies ≥60 min listed as “can seat until 19:15”
Table merge Host instinct. Two-tops vanish first Cannot store combinability as a column Drag-and-drop is strong. Whether the merge maximizes residual two-tops is settings, not default On-screen merge. Spatial-graph optimization is product-dependent Adjacent-only proposals that maximize residual two-top stock
Late release Spoken rule. 30-minute holds survive No timer Policy is configurable. Guest-screen end time vs door announcement mismatch produces tickets Reminders are email / WhatsApp first 15-minute rule on the LINE confirm. Auto-resale on expiry
Dwell granularity Lunch and tasting both 120 min Manual, unenforced Daypart rules exist. Japan public price is by seat band Feature split by plan. Annual billing changes the unit price Lunch 60, early 75, prime 105, corporate 135, by weekday
Published price shape Software 0. Gap loss is the cost Software 0. Eyes-on labor is the cost Japan public (ex-tax): setup ¥30,000; takeout-only ¥16,000/mo; ≤30 seats ¥18,000; 31-100 ¥24,000; 101+ ¥30,000. Min 6 months. POS setup ¥30,000; CTI setup ¥88,000 + ¥5,000/mo. Thailand is a quote Annual guide: Starter $48/mo (300 covers), Essential $111/mo (unlimited, 250 WhatsApp), Pro $209/mo (500). Monthly $69 / $139 / $272. Booking Network extra $2/cover Ledger 1,900 THB/mo, unlimited reservations. LINE OA plan and rail fees sit on other rows
Usage trap None. Dead time exceeds 200,000 THB/mo instead None. Peak cells go stale “Zero cover fee” is own-site and SNS only. Hungry Hub is a package discount. Crossing 31 seats jumps the monthly band No native cover fee. Starter’s 300-cover cap, WhatsApp overage, lost annual discount, and $2/cover network are the body No per-reservation usage. Seat count does not move the ledger fee
Offline Paper still writes. End time is not shared Needs network Device rules are contractual Cloud-first Device cache continues seating. Server CAS is the confirm of record

Read the table as operating risk, not as a sticker ranking. Paper looks free and prices a dead 75 minutes as the real cost. A color calendar “visualizes” and still has no channel that can sell the gap, and no exclusive lock. Foreign-engine charts are strong at host gestures. What breaks a Thai floor is a Hungry Hub 90-minute package and a house 120-minute à la carte sitting on the same table ID, so two products own the end time. Eat App-class tools carry no native cover fee and have a real SEA install base. The constraints are USD billing, WhatsApp conversation caps, LINE Official Account and PromptPay sitting off the primary path, and Free / Starter cover caps (100 / 300) that stop intake mid-month when Friday-Saturday grows. Rezabo’s 1,900 THB is ledger rent. It does not buy Google Reserve volume or OTA demand.

OpenTable-class network distribution, on 2026 public lists, adds $149 to $499 a month plus up to $1.50 per network cover, and a 2% service fee on deposits and no-show captures. A 42-seat room that takes 400 network covers a month on Friday-Saturday only pays about 245,000 THB a year in cover fees ($1.50 × 34 THB × 400 × 12). A contract whose invoice grows as you turn more tables is the opposite of this article’s objective function.

4. Rate limits and contract constraints that belong in due diligence

Sales decks say “real-time floor,” “unlimited bookings,” and “sync.” Published and field limits disagree. Put these rows in the pack before legal signs.

Constraint Paper / spreadsheet TableCheck-class engine Eat App-class SaaS OpenTable-class network Rezabo
Time grain of record Whatever the host writes Chart often displays 15 min; OTA dwell may be 90 Grid 15 min. Guest-facing slots often 30 Network slots 15-30 min. Pacing per 15-min window 15-min slots of record. Display may round; inventory does not
Cover / conversation cap None Quote assumes a seat band. 31 seats jumps Japan public monthly Free ~100 covers/mo. Starter 300. WhatsApp 250 (Essential) / 500 (Pro) Network cover fee $1.00-$1.50. Basic also $0.25/cover on own-site unless $49 flat Unlimited reservation count on the ledger fee
API surface None Web Booking, Directory, POS, CRM, Booking, Messaging. Enterprise-scoped REST / widget. WhatsApp is a metered conversation Partner APIs are network-scoped, not a shop ledger Webhook + REST for reservation events. LINE Messaging / LIFF is the guest path
Published rate limit Human speed Public notes cite 10,000 requests / 5 minutes per API component (limits do not pool) Plan and region specific. Polling the floor every 5 seconds is how you 429 Network polling is not a shop integration path Burst control on webhook ingest. Retries must be idempotent on reservation_id
POS close → table free Manual. 8-20 min ghost occupy Connector is a paid option (Japan public POS setup ¥30,000) Connector by plan. Confirm the closed-ticket event, not “sale posted” Not the seating bottleneck; network fee is check.closed webhook auto-releases. Duplicate check_id consumes once
Dwell SKU per channel One number in the host’s head Hungry Hub 90 min vs house 120 min is a config, not a default One default length unless you split resources Package vs à la carte is the shop’s problem Channel SKU is a first-class field. External ID is the idempotency key
Offline / clock Paper works. End time is not shared Device rules are contractual Cloud-first. Peak Wi-Fi drop stops input Host app needs network for network bookings Device cache continues seating. Release uses server time only
Minimum term None Japan public examples: 6 months Annual vs monthly unit price. Dropping annual is a 20%+ jump Monthly, plus network economics Month-to-month. Full CSV export is standard

Three traps sit inside that matrix.

Starter’s 300 covers is the cheap month. A 42-seat room doing 40 covers on each of eight Friday-Saturday nights is already 320 covers before weekday dinners. Intake stops before month-end. The free band (about 100 covers) dies on one Friday.

10,000 requests / 5 minutes per component is generous for a single shop and tight for a naive multi-site poller that refreshes every table every 5 seconds. Limits do not pool. A CRM extract that shares a window with a booking poller will 429 one of them. Honor Retry-After. Do not retry create.

“Zero cover fee” is a channel statement. It does not erase a Hungry Hub package sold 10% to 20% below à la carte. At 400 package covers, 1,800 THB list, and a 15% giveaway, 108,000 THB a month leaves through the price list. That line dominates any engine retainer.

5. Cut the timetable as dwell SKUs, not as “two sittings”

Two sittings (17:30 / 19:45) are a tactic. The design object is a seat-hour SKU per menu, party size, and channel.

SKU placement (42-seat yakiniku / izakaya):

  • Lunch (11:30 to 14:30): 60 minutes. Set-menu heavy. 10-minute bus.
  • Early dinner (17:30 to 18:45): 75 minutes. One-drink or early discount. End time locked on the booking screen.
  • Prime (19:00 to 21:00): 105 minutes. À la carte and standard courses.
  • Corporate / private room: 135 to 150 minutes. Merge locked. Deposit in scope.
  • Late (from 21:30): no end, or until 45 minutes before kitchen close. Second-round and bar demand.
  • Channel exception: Hungry Hub-class packages are a 90-minute SKU on separate inventory. Do not overlay a 120-minute SKU on the same chair. Eatigo-class daypart discounts need their own band if stay length differs from house default.

Leave a 15-minute reset between sitting one and sitting two. 17:30 then 19:30 looks like two turns on paper. On the floor dessert is still on the table at 19:28, the next party sits at 19:35, and appetizers concentrate at 19:50. A two-sitting grid that deletes the reset raises reported utilization and breaks ticket time.

15-minute late protocol. No contact past reservation time: LINE nudge at +5, host prepares the door wait at +10, inventory returns to walk-in at +15. BTS delay and squalls are written as a house exception, not as shift-by-shift courtesy. Courtesy that is only spoken makes hold length a random variable, and hosts stop deciding. A booking that did message delay is held, and the door wait sees on screen “this table is locked because they contacted us.” Without that display you get the r/Bangkok ticket: “we waited 45 minutes and a late reservation walked in.”

Kitchen sync. A seating engine that only optimizes chairs will land four early four-tops at 18:00 and six prime four-tops at 19:00, then stack appetizers around 19:00. The constraint set is not only the table graph. Cap concurrent fires (example: eight course appetizers per 15 minutes). A two-sitting plan that ignores that cap dumps dwell overrun onto the floor. Bind ticket fire time to remaining SKU time. A 75-minute SKU carries a floor script: last order at +40, check presented at +55.

How to use RevPASH. Raising check and raising turns cancel each other if you apply one rule to every chair. Early 75 minutes can price at about 1,650 THB and still beat a 90-minute empty chair on seat-hour yield. Corporate 135 minutes lowers turns and is still the right SKU because check and repeat visits are the product. Putting every table on 75 minutes is the inverse of the r/Serverlife failure “everyone gets two hours.” The metrics are seat-hour yield and the sell-through of green frames (vacancies of 60 minutes or more), not cover count.

A Friday working timetable on a 42-seat floor looks like this.

  1. 17:15: host reads green frames (≥60 minutes free) on the iPad. Terrace rain flag is on the same screen.
  2. 17:30 to 18:45: sell early 75 minutes on LINE and at the door. Repeat the end time out loud.
  3. 18:50 to 19:05: reset. Do not stand the next party until bussing is done.
  4. 19:00 to 21:00: prime 105 minutes. Private rooms stay at 135. Two-top merges locked except engine proposals.
  5. Reservation +5 / +10 / +15: nudge, door prep, resale.
  6. From 21:30: late. Last order 45 minutes before kitchen close. Remaining vacancies sit with no end time.

6. Table merge is a spatial graph, not an extension of instinct

A floor is not a bag of empty chairs. Each table is a node with min/max covers, adjacency, combinability, a private-room wall, a wheelchair path, and a terrace flag that dies in rain. Assigning a six-top to “any free four plus any free two” is how you delete the next two-top.

State it as a constraint satisfaction problem.

  1. Party size sits inside the table’s min/max.
  2. A merge is physically adjacent only. Across-aisle merges are rejected.
  3. Residual two-top count after the merge is not lower than the no-merge assignment, or the drop is the minimum among legal merges.
  4. Private rooms and terrace are isolated by SKU and weather flag.
  5. A merge must not eat a later confirmed start time.

A host cannot solve those five at Friday 19:00 from tenure alone. The engine proposes one merge. The host may override only with a logged reason (VIP, allergy seat, stroller). Unlogged overrides reproduce the same over-merge next week.

May-to-October terrace is a separate SKU. When a squall forecast moves terrace bookings inside, eating indoor two-top stock first overflows the indoor book. Relocation targets are vacancies that fit without a merge. If none exist, contact the terrace party and, if a deposit is in play, offer move or refund per policy. An implementation that auto-packs indoor chairs double-sells the indoor Friday book.

Wheelchair paths and steps are node attributes. As r/restaurateur keeps repeating, two two-tops that “connect” on a drawing but leave no aisle width must not appear as candidates. If they do, Friday hosts will decide “it should connect” and stand the party in the walkway.

type TableId = string;

interface TableNode {
  id: TableId;
  minCovers: number;
  maxCovers: number;
  zone: "indoor" | "terrace" | "private";
  wheelchair: boolean;
  rainClosed: boolean;
}

interface SlotRequest {
  covers: number;
  start: Date;          // 15-minute aligned
  skuMinutes: 60 | 75 | 90 | 105 | 135 | 150;
  channel: "line" | "walkin" | "hungryhub" | "google";
  wheelchair: boolean;
}

const ADJACENT: ReadonlySet<string> = new Set([
  "T11|T12", "T12|T13", "T21|T22",
]);

function pairKey(a: TableId, b: TableId): string {
  return a < b ? `${a}|${b}` : `${b}|${a}`;
}

export function legalMerges(
  nodes: TableNode[],
  occ: ReadonlySet<TableId>,
  req: SlotRequest,
): TableId[][] {
  const free = nodes.filter((n) => {
    if (occ.has(n.id)) return false;
    if (n.zone === "terrace" && n.rainClosed) return false;
    if (req.wheelchair && !n.wheelchair) return false;
    return true;
  });

  const singles = free
    .filter((n) => req.covers >= n.minCovers && req.covers <= n.maxCovers)
    .map((n) => [n.id]);

  const pairs: TableId[][] = [];
  for (let i = 0; i < free.length; i++) {
    for (let j = i + 1; j < free.length; j++) {
      const a = free[i], b = free[j];
      if (!ADJACENT.has(pairKey(a.id, b.id))) continue;
      const min = a.minCovers + b.minCovers;
      const max = a.maxCovers + b.maxCovers;
      if (req.covers < min || req.covers > max) continue;
      pairs.push([a.id, b.id]);
    }
  }

  const twoTopStock = (used: TableId[]) =>
    free.filter((n) => n.maxCovers <= 2 && !used.includes(n.id)).length;

  return [...singles, ...pairs]
    .sort((x, y) => twoTopStock(y) - twoTopStock(x) || x.length - y.length)
    .slice(0, 1);
}

The server, not the iPad, owns the adjacency matrix. An API that accepts “any two tables whose capacities sum” will merge across an aisle and through a private-room door. Return HTTP 400 for a pair that is not in the shop-edited matrix. Client-only validation dies on a stale iPad cache.

Operational traps (contract and floor)

  • Two sittings with no end time on the booking screen. Telling a seated party “you have until 19:30” is the Reddit time-limit pile-on. Print start and end in Thai, English, and Japanese on the confirm card.
  • Leaving two-top merges to host discretion. Every four-top you force this way deletes two-top stock. Make non-proposed merges unselectable.
  • A 15-minute release the guest never saw. Sudden resale is a complaint. Put “no contact at +15 and we resell; message us and we hold” on the LINE completion bubble and on the reminder.
  • Hungry Hub 90 minutes and house 120 minutes on one table grid. Sync does not equal SKU. Split tables or run a dedicated band whose dwell matches the package.
  • Deleting the 15-minute reset to inflate turns. Seating onto an unbussed table produces ticket lag, and the next 105-minute SKU becomes 120. Weekly turn reports rise. Friday RevPASH falls.
  • No kitchen fire cap in the objective function. An empty chair that would push appetizers past eight plates / 15 minutes is a dwell-overrun seed. Refuse or re-SKU it, and show the host “kitchen concentration,” not a blank decline.
  • A plan whose invoice or intake dies when Friday grows. OpenTable-class $1.00 to $1.50 per cover, Eat App WhatsApp caps (Essential 250, Pro 500) and Starter’s 300-cover stop, seat-band jumps at 31 tables: force the quote to show “rows that grow in a busy month” as a table.
  • POS close does not free the table. A grid that waits for a manual close leaves a paid table “occupied” for 20 minutes and hides gap inventory. No closed-ticket webhook means the bottleneck is the register, not the seating rule.
  • Counting seasonal terrace as contracted seats. TableCheck’s Japan public band jumps at 31 seats. Adding a wet-season terrace to the all-year seat count bills the high band in months you keep those chairs stacked. Seat-band pricing fights seasonal furniture.

7. Pricing-plan traps and annual TCO

Paper and spreadsheets win a sticker comparison. Rank on total cost of ownership after gap loss, usage, and term lock, and the order reverses.

Assumptions: Thong Lo 42 seats, eight Friday-Saturday peak nights, twelve weekday dinners, 2,050 THB check, gap-related miss 221,520 THB a month.

Cost line (annual) Paper + flat 120 min Foreign engine + OTA-led SEA public SaaS (Eat App Essential, annual) Rezabo flat
Software / ledger 0 Japan public band parked on Thailand: 31-100 seats ≈ 66,000 THB/year (¥24,000 × 0.23 × 12) + setup ≈ 6,900 THB. Live quotes add IVR / POS $111 × 12 = $1,332 (≈ 45,300 THB). Monthly $139 ≈ 56,700 THB 19,000 THB (annual plan, 2 months free)
Distribution / discount 0 (demand is LINE and walk-in) Hungry Hub-class package giveaway. 400 covers × 1,800 × 15% × 12 ≈ 1.296 million THB can sit on this row No native cover fee. Booking Network at $2/cover × 100/mo ≈ 82,000 THB/year Own LINE / web = 0. OTA is optional overflow
Usage / caps None “Zero cover fee” is own-path only. Public 6-month minimum. 31 seats jumps the monthly WhatsApp 250. Overage is a bundle (from $100) or Pro (annual $209/mo). Drop to Starter and 300 covers stop intake Unlimited reservations. No seat-band
Gap loss (year) 221,520 × 12 ≈ 2.658 million THB Chart install with mixed SKUs still leaves 1 million-plus Vacancy visibility is strong. Without LINE as primary and a 15-minute release, recovery stays partial Shops that split SKUs compress this to a few hundred thousand
Host labor (seating calls) Visual scan and refusal scripts. Fri-Sat ≈ 20 h × 90 THB × 12 = 21,600 Falls after config. OTA rule translation remains English UI training can remain Override-only
Year-1 TCO shape Software 0 + 2.66 million dead time Engine tens of thousands + OTA discount as the protagonist Ledger 45k-80k + conversation overage + residual gaps Ledger 42k + rail fees + residual gaps

Five traps.

First, paper’s “cost zero” is only an invoice fact. 221,520 THB is seat-hour abandonment. COGS cuts and ads do not recover it.

Second, a foreign engine’s “zero cover fee” is copy about own site and social. It does not restore the net price of an OTA package.

Third, dollar Essential at $111 a month looks cheap. Thai booking completion finishes inside a LINE thread. You either lift to Pro to buy more WhatsApp conversations, or you keep Official Account in parallel. The moment you run both, the book of record splits again.

Fourth, seat-band pricing (the jump at 31 seats) spikes the month you add terrace chairs as “seats.” Houses that stack terrace in the wet season are a bad fit for that meter.

Fifth, Eat App Starter’s 300-cover cap is the cheap month. Forty covers × eight Friday-Saturday nights is 320 before weekdays. The free 100-cover band dies in one Friday service.

Do not oversell the Rezabo line either. 1,900 THB a month is an unlimited-count ledger and a seating grid. LINE Official Account plan fees, PromptPay MDR, and Wongnai POS or Ocha close events are other rows. The value is that Friday-Saturday cover growth does not move software cost, and that a 75-minute gap is inventory you can sell. It is not a demand product.

At three years the gap widens. Paper’s 2.66 million THB a year of dead time is about 7.97 million THB. An OTA giveaway of 1.29 million THB a year is about 3.89 million THB. Ledger flat is 126,000 THB. Year-one migration labor does not change the object you are recovering. You are recovering seat-hours, not software.

8. API and infrastructure constraints to kill before go-live

Time grain. A 30-minute slot leaves a table that paid at 19:10 “occupied” until 19:30 and kills 20 minutes. The book of record is 15 minutes. Rounding only the display is how hosts misread gaps.

POS close webhook. Three failures show up in week one: no event, a late event, the same check_id twice. The ledger releases a table once per check. Wongnai POS and Ocha do not share a close-event schema. Map fields in the install week. A grid still closed 20 minutes after payment is an integration defect, not a turnover defect.

{
  "id": "evt_9c2e1a",
  "type": "pos.check.closed",
  "occurred_at": "2026-08-21T12:08:11+07:00",
  "data": {
    "check_id": "wng_chk_883410",
    "table_ids": ["T12"],
    "covers": 4,
    "closed_at": "2026-08-21T12:08:09+07:00",
    "amount_thb": 8200,
    "channel": "walkin"
  }
}
export async function onCheckClosed(evt: PosCheckClosed, bus: Ledger) {
  const applied = await bus.releaseOnce({
    idempotencyKey: evt.data.check_id,
    tableIds: evt.data.table_ids,
    freedAt: evt.data.closed_at,
  });
  if (!applied) return; // duplicate webhook, already open
  await bus.recomputeGreenFrames(evt.data.table_ids);
}

Kitchen cap in the booking path. Seat-optimization responses must carry a per-window concurrent-seat cap. Overflow requests return a late SKU or another date. A decline on an empty chair will not be used unless the host sees “kitchen concentration” as the reason.

Channel SKU collisions. Hungry Hub and Google bookings key on the external reservation ID. A retry of the same ID must not consume a second slot. Do not overwrite a 90-minute channel dwell with the house 120-minute default. That overwrite eats the next sitting. Assume webhooks arrive late and twice; make the handler idempotent.

-- Dinner window 17:30-22:30 = 300 minutes. 42 seats => 12,600 seat-minutes.
-- Occupied minutes exclude the 15-minute reset SKU.
SELECT
  d.service_date,
  ROUND(100.0 * SUM(occupied_minutes)::numeric
        / (42 * 300), 1) AS seat_util_pct,
  ROUND(SUM(check_thb)::numeric
        / NULLIF(SUM(occupied_minutes) / 60.0, 0), 0) AS revpash_thb,
  COUNT(*) FILTER (WHERE gap_minutes >= 60 AND sold) * 100.0
    / NULLIF(COUNT(*) FILTER (WHERE gap_minutes >= 60), 0) AS green_sell_pct
FROM seat_hour_facts d
WHERE d.daypart = 'dinner_peak'
GROUP BY 1
ORDER BY 1;
query EarlyGaps($shopId: ID!, $date: Date!) {
  availableSlots(
    shopId: $shopId
    date: $date
    skuMinutes: 75
    minGapMinutes: 60
  ) {
    tableIds
    start
    mustVacateBy
    residualTwoTops
    kitchenHeadroom
  }
}

Offline and clocks. Friday 19:00 shop Wi-Fi loss is a premise, not an incident. The iPad caches the latest grid and keeps seating. Confirm of record is server CAS after reconnect. If two devices both “confirm” the same chair offline, reconnect rejects one as a conflict. Release timing uses server time only. A device clock five minutes off turns a 15-minute rule into 10 or 20. An iPad left on Buddhist calendar year 2569, or on Tokyo time, writes the 19:00 slot onto another date. Guest Wi-Fi plus a captive portal drops the WebSocket. Put floor devices on a shop SSID or a 4G router.

LINE and LIFF. Early 75-minute conversion falls if the thread never shows the end time. An email-confirm engine sends Thai hosts back to screenshot checks. Finish slot select to confirm inside LIFF, and put end time plus the 15-minute rule in the same bubble. A rich-menu URI tap does not carry a replyToken. Confirm cards and +5 nudges are Push, and they count against the monthly message quota. A month-end stop of confirm cards is not a seating-logic outage; it is a quota outage. Japan-region Light is published at 5,000 messages a month with no top-up. Thailand-region shops still put “check remaining quota before Friday” in house rules.

Rate limits. LINE Messaging API broadcast and insight endpoints are tight per window. Hitting delivery stats every minute returns 429. Host-grid refresh is WebSocket or short-cycle diffs. Do not put official insight endpoints inside the seating loop.

9. Land three SKUs in three weeks. Do not dynamize every daypart on day one

  • Week 1. Friday-Saturday dinner only. SKUs: early 75, prime 105, private 135. End time on the booking screen. Two-top merges locked except proposals. 15-minute rule in the LINE completion bubble. Kitchen plates-per-15-minutes agreed with the GM. Migrate every future booking and reconcile residual paper rows.
  • Week 2. Vacancies of 60 minutes or more render green on the host iPad as “can seat until HH:MM.” Walk-ins sit only on green frames. No-contact +15 auto-resale goes live. Test that POS close frees the table on ten Friday checks. Kill the register-side notebook as a write mouth.
  • Week 3. Pin the 15-minute reset between sittings. Kitchen-caused overruns extend the bar on screen and recompute downstream. Use extend logs to retune SKUs next week. Terrace rain relocates only to indoor chairs that fit without a merge. After Friday close, audit merge overrides and 15-minute releases.

Do not put every weekday and every channel on dynamic slots in week one. Weekday lunch at a fixed 60 minutes is a small leak. Add SKUs weekly. Add weekday dinner and late after 90 days. Do not score the project on weekly covers alone. Score RevPASH and the share of refused walk-ins that a green frame could have sat. Leaving paper as a second book of record means you bought software and kept the leak.

10. Questions the GM should pin before signing

Q1. Will a 75-minute SKU cut check average?

Yes. Early checks often sit 10% to 20% below prime. The comparison is not prime check. It is a 90-minute empty chair at zero. On a 42-seat Friday-Saturday book, seat-hour yield still rises.

Q2. Will guests reject a short seating?

Parties who saw the end time at booking rarely complain. Spoken limits after they sit are the complaint source. Houses that attach a welcome drink to the early SKU write the copy as “a window you can sit now.” Thai รอบแรก / รอบสอง copy works when the confirm screen also prints the clock time.

Q3. What if the kitchen blows the window?

Kitchen-caused overrun does not get a guest asked to leave. Extend on screen and move the next party, or message them. Log the reason as kitchen. If you do not, hosts stop applying the 15-minute rule the following week.

Q4. The late guest arrives after resale. Then what?

The screen offers counter, terrace, or the next free slot. Do not reopen the original table and break the party now sitting. If a deposit is in play, apply it per policy.

Q5. Does every shop need two sittings?

No. A private-room corporate house can run a 135-minute SKU plus gap sales. Two sittings pay off on à la carte floors with 17:00 demand on Friday-Saturday. Do not install two sittings that exceed the kitchen cap.

Q6. Can the floor change the map during service?

Capacity edits go live the next day. Same-day drag changes break already-booked min/max constraints. Layout permission is a GM role.

Q7. Does a Wi-Fi drop stop seating?

The latest grid keeps seating from device cache. Confirm of record is server CAS. Two offline “confirms” on one chair resolve as a conflict on reconnect. Do not put floor devices on guest Wi-Fi.

Q8. Can we keep Tabelog or TableCheck and only add dynamic slots?

A week with two books of record collides before it sells gaps. If a demand channel stays, bind its dwell SKU to that channel and let one grid write confirm. Do not leave paper and personal LINE as a third write mouth.

11. What to measure before and after

Metric Flat 120 min / paper seating After SKU split + dynamic gap sales Why it matters
Fri-Sat peak seat utilization (17:30-22:30) 68% to 74% 86% to 89.5% Combined test that gaps and late holds actually died
Turns per table per night 1.15 to 1.30 1.75 to 1.90 Reset slot presence tells you the number is not a two-sitting cheat
RevPASH (THB per seat-hour) 750 to 820 980 to 1,050 Detects an early SKU priced too cheap
Green-frame (≥60 min) sell-through Unmeasurable 55% or higher Visible inventory is actually sold
Two-top merges (Fri-Sat) Host-dependent, high Zero outside proposals Two-top stock protection
Mean hold on no-contact lateness 25 to 35 min Resale at 15 Direct cause of walk-in miss
Minutes from POS close to table free 8 to 20 Under 1 (webhook) Ghost occupy on the grid
Extends caused by ticket lag Unlogged Falling week on week Kitchen cap is working
Share of walk-ins sat on a green frame Unmeasurable Rising week on week Hosts have stopped refusing on “there is a reservation”
89% seat utilization is not a number you get by rushing service. It is the result of selling a 75-minute gap as inventory, protecting two-tops, reselling at 15 minutes, and never seating past the kitchen’s fire cap.

Conclusion

A Bangkok peak-time room that is “full” and still misses sales is not short of demand. A rigid 120-minute block refuses the 18:00 walk-in, a two-top merge ejects the next deuce, and an unanswered booking sends the door queue home as if the house were sold out. The leak sits in the 180,000 to 240,000 THB a month band and never appears on an invoice. The stop conditions are: cut dwell as SKUs, print the end time before confirm, sell gaps as green frames, constrain merges on a spatial graph, and free the table on POS close.

Paper and color calendars stop at visualization. Foreign engines and SEA SaaS sell a chart and “zero cover fee.” What works at Friday 19:00 for a Thai independent is a flat ledger that finishes confirm, including end time, inside LINE, and that can carry PromptPay and a POS close event on other rows. Ledger cost is about 42,000 THB a year. Recoverable seat-hours exceed 2 million THB a year. The difference is not a decimal on turn rate. It is whether the 18:05 four-top is seated as “until 19:15.”

When you take quotes, do not freeze one monthly line. Freeze a table of (1) rows that grow in a busy month, (2) seat bands that jump at 31 chairs or when terrace is counted, (3) WhatsApp or SMS conversation caps, (4) OTA package giveaway rate, and (5) whether POS close frees the table. A contract that ships gross profit off the floor on the Fridays you finally turn is incompatible with an 89% utilization design.

If you want the 75-minute sell volume and the kitchen cap sized to your table map and course mix, the Bangkok team can walk the Friday grid on a demo.

Free Download (PDF)Ver 2.6 Live

Bangkok Restaurant No-Show Prevention Checklist (15 Rules)

An operational field guide for dining venues in Thailand: sync kitchen prep strictly with dynamic PromptPay QR deposits and automated LINE reminders to eliminate food waste.

  • Strict synchronization of Friday 16:00 kitchen prep with paid bookings only
  • Dynamic QR verification replacing static slip matching and slip forgery
  • High-retention refund deadline rules and seamless tourist card routing
Download Free Checklist (Get via LINE / PDF)

Delivered instantly via LINE Official Account (Instant 3-sec access)

1-Month Free Trial Available

Restaurant Booking & CRM Made Simple & Smart.

Born from Bangkok dining floors, Rezabo enables new floor staff to allocate tables and share VIP notes in 5 seconds with zero double-booking.

Explore Rezabo Features & Pricing (Free Trial)
← Back to Knowledge Articles