Login
I'm a traveler
I'm a property partner
Sign up for free
Unlock business travel rates
Products
Book Company Travel
Flights, hotels, and car rentals
Book Large Groups + Extended Stays
White-glove support for groups of 9+ or stays of 28+ days
Book Meeting Spaces
Venues for offsites, team meetings, and events
Pay with Engine X
Business cards for travel, spend, and rewards
A jet plane flying in the sky2 Luggages leaning against each otherAn empty road
Solutions
By Role
Travel Managers
Operations
Finance
Travelers
By Group Type
Construction Crews
Sports Teams
Conferences + Events
Offsites + Retreats
Weddings
Disaster Relief
Public Sector
Food served on a tableA person sitting and holding a luggageA car driving over a bridge
Company
About
Culture
Talent
Career Opportunities
Resources
Templates
Partnerships
Press
A laptop, a coffee mug and a phoneA view of snowy mountainsA group of people at a work meeting
For Hotels
Partner Hub Overview
Increase Revenue
Build Customer Loyalty
Manage Reservations
Team Communication
Traveler Support
Insights & Insights+
Log into Partner Hub Become a Partner Contact Support
A wing of a plane and a sunset in the backgroundA group of people talking at an officeA staircase at a cozy hotel lobby
Pricing
Watch Demo
Login
I'm a traveler
I'm a property partner
Sign up for free
Unlock business travel rates
By clicking “Sign up for free,” you agree to Engines Terms of Service and Privacy Policy.
Library
/
Blog

Hotel Attrition Calculator for Group Room Blocks

Barry Goodnight
By 
Barry Goodnight
August 27, 2025
Hotel Attrition Calculator for Group Room Blocks

Group Hotel Attrition: Calculate Minimums, Shortfall, and Damages

Estimate attrition risk and damages before you sign. Upload your nightly grid or enter totals to model cumulative or per-night minimums, allowed attrition, resell credits, and final penalties.

Planning a group stay is easier when the math is clear. This Attrition Calculator turns contract terms into simple numbers you can use and understand; showing minimum performance, allowable drop, shortfall, and estimated damages in seconds.

If you want to simplify the entire room-block workflow, use Engine's group booking platform and skip the manual back-and-fourth with the hotels. Engine is built for project-based teams, handles 9 to 9,000 room bookings, tracks spend by project code, gives you Direct Bill with one monthly invoice, and adds flexible change protection so shifting schedules do not blow the budget. Best of all? It's totally free. No really, we make our money off hotel commissions, our entire platform is completely free to use and will never increease your hotel rates. Give it a spin and see for yourself.

Engine.com is 100% FREE.

‍

.attrition-calc { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 16px; border: 1px solid #e6e6e6; border-radius: 12px; } .attrition-calc * { box-sizing: border-box; } .attrition-calc h2 { margin: 0 0 12px; font-size: 1.4rem; } .attrition-calc p.helper { margin: 6px 0 14px; color: #444; font-size: 0.95rem; } .attrition-calc .tabs { display: flex; gap: 8px; margin: 8px 0 12px; } .attrition-calc .tab-btn { border: 1px solid #d8d8d8; background: #f7f7f7; padding: 10px 12px; border-radius: 10px; cursor: pointer; font-weight: 600; } .attrition-calc .tab-btn.active { background: #111; color: #fff; border-color: #111; } .attrition-calc .card { border: 1px solid #ececec; border-radius: 12px; padding: 12px; background: #fafafa; } .attrition-calc label { display: block; font-size: 0.9rem; margin: 8px 0 6px; color: #222; } .attrition-calc input[type="number"], .attrition-calc input[type="text"], .attrition-calc select { width: 100%; padding: 10px; border: 1px solid #c7c7c7; border-radius: 8px; background: #fff; font-size: 0.95rem; } .attrition-calc .row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .attrition-calc .row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; } .attrition-calc .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .attrition-calc .btn { appearance: none; border: none; border-radius: 10px; padding: 12px 14px; font-weight: 600; cursor: pointer; background: #111; color: #fff; } .attrition-calc .btn.secondary { background: #f1f1f1; color: #111; border: 1px solid #d8d8d8; } .attrition-calc table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 12px; overflow: hidden; } .attrition-calc th, .attrition-calc td { border-bottom: 1px solid #eee; padding: 10px; font-size: 0.95rem; } .attrition-calc th { text-align: left; background: #f7f7f7; } .attrition-calc .actions { display: flex; gap: 8px; align-items: center; } .attrition-calc .muted { color: #666; font-size: 0.85rem; } .attrition-calc .results { display: grid; gap: 10px; } .attrition-calc .stat { padding: 12px; border: 1px solid #e6e6e6; border-radius: 12px; background: #fff; } .attrition-calc .right { text-align: right; } .attrition-calc .toggle { display: inline-flex; align-items: center; gap: 8px; font-size: 0.9rem; } @media (max-width: 860px) { .attrition-calc .row, .attrition-calc .row-3, .attrition-calc .grid { grid-template-columns: 1fr; } }

Attrition Calculator

Estimate minimum performance, allowable drop, shortfall, and damages for group room blocks. Choose Simple for a single ADR and total room nights, or Advanced to load a nightly grid and pick the calculation method.

Simple Advanced

Contracted room nights (total)

Allowed attrition %

Pickup (actual or forecast)

ADR used for damages

Damages % of rate

Resell credit (rooms)

Calculate Reset Simple method uses cumulative minimums.

Minimum performance—

Allowable drop—

Shortfall (rooms)—

Damages—

Inputs recap—

NotesFigures are estimates. Always follow the contract language for method and basis, and apply any wash/cutoff provisions first.

Allowed attrition %

Damages % of rate

Resell credit (rooms)

Method Cumulative

Per-night

CSV upload (optional) Columns: label/date, contracted, pickup, rate

Label / Date Contracted Pickup ADR Actions Add row Clear all

Calculate Reset

Minimum performance—

Allowable drop—

Shortfall (rooms)—

Damages—

Summary—

NotesCumulative compares totals across nights. Per-night applies nightly minimums and sums shortfalls. Resell credit subtracts rooms from shortfall, capped at the shortfall.

(function(){ const $ = (id) => document.getElementById(id); const money = (n) => n.toLocaleString(undefined, { style: "currency", currency: "USD" });

// Tabs window.AC_switchTab = function(tab){ document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active")); document.querySelector(`.tab-btn[data-tab="${tab}"]`).classList.add("active"); $("tab-simple").style.display = tab === "simple" ? "" : "none"; $("tab-advanced").style.display = tab === "advanced" ? "" : "none"; };

// SIMPLE window.AC_computeSimple = function(){ const B = Math.max(0, parseFloat($("s_block").value)||0); const a = Math.max(0, parseFloat($("s_attrition").value)||0) / 100; const P = Math.max(0, parseFloat($("s_pickup").value)||0); const r = Math.max(0, parseFloat($("s_adr").value)||0); const d = Math.max(0, Math.min(100, parseFloat($("s_damages_pct").value)||0)) / 100; const R = Math.max(0, parseFloat($("s_resell").value)||0);

const M = B * (1 - a); // minimum performance const allowable = B - M; // allowable drop const S0 = Math.max(0, M - P); // shortfall before resell const S = Math.max(0, S0 - R); // billable shortfall after resell const damages = S * r * d;

$("s_min_out").textContent = Math.round(M).toLocaleString(); $("s_allow_out").textContent = Math.round(allowable).toLocaleString(); $("s_short_out").textContent = Math.round(S).toLocaleString(); $("s_damages_out").textContent = money(damages); $("s_breakdown").textContent = `Shortfall ${Math.round(S)} × ADR ${money(r)} × ${Math.round(d*100)}% damages`; $("s_recap").textContent = `Block ${Math.round(B)}, Attrition ${Math.round(a*100)}%, Pickup ${Math.round(P)}, Resell ${Math.round(R)}`; };

window.AC_resetSimple = function(){ $("s_block").value = 200; $("s_attrition").value = 20; $("s_pickup").value = 150; $("s_adr").value = 179; $("s_damages_pct").value = 80; $("s_resell").value = 0; AC_computeSimple(); };

// ADVANCED GRID const tableBody = () => $("a_table").querySelector("tbody");

function rowTemplate(label="Night 1", contracted=80, pickup=70, rate=179){ const tr = document.createElement("tr"); tr.innerHTML = ` Duplicate Remove

`; const [dupBtn, remBtn] = tr.querySelectorAll("button"); dupBtn.onclick = () => { const vals = getRowValues(tr); const clone = rowTemplate(vals.label+" (copy)", vals.contracted, vals.pickup, vals.rate); tableBody().appendChild(clone); }; remBtn.onclick = () => { tr.remove(); }; return tr; }

function getRowValues(tr){ const tds = tr.querySelectorAll("td"); return { label: tds[0].querySelector("input").value.trim() || "Night", contracted: Math.max(0, parseFloat(tds[1].querySelector("input").value)||0), pickup: Math.max(0, parseFloat(tds[2].querySelector("input").value)||0), rate: Math.max(0, parseFloat(tds[3].querySelector("input").value)||0) }; }

function getGrid(){ const rows = []; tableBody().querySelectorAll("tr").forEach(tr => rows.push(getRowValues(tr))); return rows; }

window.AC_addRow = function(){ tableBody().appendChild(rowTemplate(`Night ${tableBody().children.length+1}`, 50, 40, 169)); }; window.AC_clearRows = function(){ tableBody().innerHTML = ""; };

// CSV: columns label/date,contracted,pickup,rate window.AC_loadCSV = function(evt){ const file = evt.target.files && evt.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { const text = e.target.result; const lines = text.split(/\r?\n/).filter(Boolean); if (lines.length h.trim().toLowerCase()); const col = (n) => headers.indexOf(n); const idxL = col("label") >= 0 ? col("label") : col("date"); const idxC = col("contracted"); const idxP = col("pickup"); const idxR = col("rate"); if (idxC x.trim()); if (!cells.length) continue; const label = idxL >= 0 ? cells[idxL] : `Night ${i}`; const contracted = parseFloat(cells[idxC]) || 0; const pickup = parseFloat(cells[idxP]) || 0; const rate = parseFloat(cells[idxR]) || 0; tableBody().appendChild(rowTemplate(label, contracted, pickup, rate)); } }; reader.readAsText(file); };

window.AC_computeAdvanced = function(){ const a = Math.max(0, parseFloat($("a_attrition").value)||0) / 100; const d = Math.max(0, Math.min(100, parseFloat($("a_damages_pct").value)||0)) / 100; const R = Math.max(0, parseFloat($("a_resell").value)||0); const method = document.querySelector('input[name="a_method"]:checked').value;

const rows = getGrid(); if (!rows.length){ alert("Add at least one row."); return; }

const B = rows.reduce((s,r)=>s + r.contracted, 0); const P = rows.reduce((s,r)=>s + r.pickup, 0); const avgRateWeighted = rows.reduce((s,r)=>s + (r.rate * r.contracted), 0) / Math.max(1, B);

let M = 0, allowable = 0, shortfall = 0, damages = 0, breakdown = "";

if (method === "cumulative"){ M = B * (1 - a); allowable = B - M; const S0 = Math.max(0, M - P); const S = Math.max(0, S0 - R); shortfall = S;

// Use weighted average rate for damages damages = S * avgRateWeighted * d; breakdown = `Shortfall ${Math.round(S)} × Avg ADR ${money(avgRateWeighted)} × ${Math.round(d*100)}%`; } else { // per-night let S_total = 0; let nightlyShortfalls = []; rows.forEach(r => { const Mi = r.contracted * (1 - a); const si = Math.max(0, Mi - r.pickup); S_total += si; nightlyShortfalls.push({ short: si, rate: r.rate }); });

// Apply resell credit to highest-rate nights first nightlyShortfalls.sort((x,y)=>y.rate - x.rate); let Rleft = R; let damagesSum = 0; nightlyShortfalls.forEach(n => { if (Rleft s + r.contracted*(1-a), 0); allowable = B - M; shortfall = Math.max(0, S_total - R); damages = damagesSum; breakdown = `Per-night: shortfall ${Math.round(S_total)} minus resell ${Math.round(R)} = ${Math.round(shortfall)} billable; damages at nightly ADR × ${Math.round(d*100)}%`; }

$("a_min_out").textContent = Math.round(M).toLocaleString(); $("a_allow_out").textContent = Math.round(allowable).toLocaleString(); $("a_short_out").textContent = Math.round(shortfall).toLocaleString(); $("a_damages_out").textContent = money(damages); $("a_breakdown").textContent = breakdown;

$("a_summary").textContent = `Block ${Math.round(B)}, Pickup ${Math.round(P)}, Attrition ${Math.round(a*100)}%, Damages ${Math.round(d*100)}%, Resell ${Math.round(R)}. Method: ${method === "cumulative" ? "Cumulative totals" : "Per-night minimums"}.`; };

window.AC_resetAdvanced = function(){ $("a_attrition").value = 20; $("a_damages_pct").value = 80; $("a_resell").value = 0; $("a_cum").checked = true; AC_clearRows(); // Seed a few example nights tableBody().appendChild(rowTemplate("Night 1", 80, 70, 179)); tableBody().appendChild(rowTemplate("Night 2", 70, 50, 179)); tableBody().appendChild(rowTemplate("Night 3", 50, 30, 179)); };

// Initialize AC_resetSimple(); AC_resetAdvanced(); })();

‍

What is hotel attrition?

Hotel attrition is the difference between the rooms you contracted and the rooms your group actually uses. Most group contracts allow a certain percentage of “slippage” without penalty. If your pickup falls below the minimum performance after that allowance, the hotel can charge damages based on the contract terms.

Attrition protects the hotel’s expected revenue while giving your group some flexibility. Your agreement will spell out the allowed attrition percentage, the method used to measure performance, and how damages are calculated.

How is attrition calculated?

There are two common methods:

Cumulative: Compare total pickup to one minimum across the entire stay. Nights can offset each other.

Per night: Each night has its own minimum. Shortfalls on one night are not offset by another night.

Typical damages are a percentage of the room rate used for damages. Contracts usually use the group ADR or a stated base rate. Some agreements allow a resell credit, which reduces your shortfall by the number of rooms the hotel resold.

Key terms you will see in contracts

Allowed attrition %: The slippage you can use without penalty, for example 20%.

Method: Cumulative or per night.

Damages % and rate basis: The percentage to charge and which rate to use, for example 80% of group ADR.

Resell or mitigation: Credit for rooms the hotel resold, sometimes capped at the shortfall.

Cutoff and wash: Dates that allow you to reduce the block before penalties apply.

Taxes and fees: Whether taxes or resort fees are included in damages. Many contracts exclude taxes from damages.

Quick example

Contracted room nights: 200

Allowed attrition: 20% → minimum performance = 160

Pickup: 150 → shortfall before resell = 10

Resell credit: 0 → billable shortfall = 10

ADR used for damages: $179

Damages percent: 80%

Damages = 10 × $179 × 0.80 = $1,432

Planner tips

Confirm the method. Per night is stricter than cumulative.

Apply wash or cutoff reductions first, then measure attrition.

Ask how resell credit is documented.

Check if damages include or exclude taxes and fees.

If your pattern shifts, revisit the block by night. Small moves can protect the minimum.

The math (clean formulas you can implement manually)

Cumulative method (single ADR)

Contracted room nights = BBB

Allowed attrition % = aaa

Minimum performance = M=B×(1−a)M = B \times (1 - a)M=B×(1−a)

Actual (or forecast) pickup = PPP

Shortfall (before resell) = S0=max⁡(0,M−P)S_0 = \max(0, M - P)S0​=max(0,M−P)

Resell credit rooms = RRR (if allowed)

Billable shortfall = S=max⁡(0,S0−R)S = \max(0, S_0 - R)S=max(0,S0​−R)

Group ADR used for damages = rrr

Damages % = ddd (e.g., 0.80 for 80%)

Damages = S×r×dS \times r \times dS×r×d(Optionally multiply by tax if contract requires, but usually it doesn’t.)

Per-night method (rooms-by-night) For each night iii:

BiB_iBi​ = contracted rooms, PiP_iPi​ = pickup, rir_iri​ = rate

Minimum Mi=Bi×(1−a)M_i = B_i \times (1 - a)Mi​=Bi​×(1−a)

Shortfall si=max⁡(0,Mi−Pi)s_i = \max(0, M_i - P_i)si​=max(0,Mi​−Pi​)

Billable shortfall after resell credit per night (if defined that way): si′s_i'si′​

Damages =∑isi′×ri×d= \sum_i s_i' \times r_i \times d=∑i​si′​×ri​×d

Tiny worked example (cumulative):

Contracted B=200B=200B=200, allowed attrition a=20%⇒M=160a=20\% \Rightarrow M=160a=20%⇒M=160

Pickup P=150⇒S0=10P=150 \Rightarrow S_0=10P=150⇒S0​=10

Resell credit R=0⇒S=10R=0 \Rightarrow S=10R=0⇒S=10

ADR r=$179r=\$179r=$179, damages % d=80%d=80\%d=80%

Damages =10×179×0.8=$1,432= 10 \times 179 \times 0.8 = \$1{,}432=10×179×0.8=$1,432 If the hotel resold 20 rooms and the contract credits resell, R=20⇒S=max⁡(0,10−20)=0⇒$0R=20 \Rightarrow S=\max(0,10-20)=0 \Rightarrow \$0R=20⇒S=max(0,10−20)=0⇒$0 damages.

Per-night example (shows why method matters): Block by night = 80 / 70 / 50; with 20% attrition → minimums = 64 / 56 / 40 Pickup = 70 / 50 / 30 → shortfalls = 0 / 6 / 10 → 16 total shortfall Cumulative totals: block 200, min 160, pickup 150 → shortfall 10. So per-night can be stricter than cumulative.

‍

‍

Table Of Contents
Know the Laws First
A hotel, a car, and a luggage
Engine streamlines business travel.
Join for Free
Share This Article:
Contact Member Support:
855-567-4683
Download on the App Store Get it on Google Play
Features
Dashboard + Reporting Billing Flexibility Groups + Events Incidentals Rewards + Loyalty 24/7 Support Our Hotels Book Corporate Travel Travel Policies
Group Bookings
Groups Overview Construction Crews Conferences + Events Sports Teams Offsites + Retreats Weddings Disaster Relief RoomBlocks by Engine
Who We're Made For
Travel Managers Operations Managers Finance Managers Travelers By Industry
Partner Hub
Platform Overview Increase Revenue Build Customer Loyalty Manage Reservations Team Communication Traveler Support Real-time Insights
Company
About Us Culture Careers Social Responsibility Hotel Partners Sign Up Member Login Contact Us Trust Center Partnerships Press
Resources
Pricing All Resources Templates Case Studies FAQs Help Center Accessibility
© Engine 2026. All Rights Reserved
Terms of Service Privacy Policy