Lesson 10 / 12
Cost Visibility
Team-based resource accounting: only 47.5% of a daily 1,376 fictional units can be directly assigned, each of three allocation rules misleads a different team in a different direction (the equal-share rule loads field +79.0 while the request rule loads meter-reading +140.7), only 15.6% of the bill drops after cost is shown while 29.7% never moves at all, and central accounting pays for itself at three teams but two teams opting out drags the five-team gain of 85 units down to 3 units.
Published Updated
In this lesson
- Splits total cost across five teams and separately counts directly assignable, shared, and unallocatable items
- Sweeps three allocation rules: measures which team each rule misleads and in which direction, as a deviation
- Measures visibility's effect on behavior and separates by name the item that is not under a team's control
Contents
The previous lesson embedded the rules in the pipeline: five teams complied without ever opening the policy engine, the number of exemptions was counted, and the rule’s invisibility was written down as a gain. For those same five teams, one thing is still invisible — how much they spend. The platform buys the resource and reads the bill on their behalf.
Unit cost, idle resources, and economies of scale were measured in M22/K06; here that bill is the input. This lesson’s question is a single division: how is the same amount split across five teams, how much cannot be split, and what changes once the split amount is shown to each team.
Splitting the Bill Across Five Teams
The example is again the fictional regional measurement network, and the five teams are fictional:
meter reading, validation, billing, field, reporting. The line items, prices, and usage measures are
fictional; the currency is fictional too and is called u, and it does not correspond to any real
price list.
PC25: there are nine cost items, and each item’s daily cost is written down. PC26: items are three types — directly assignable (carries a team tag), shared (cluster control plane, shared build and deploy pipeline, monitoring stack), and unallocatable; the last of these has no relationship to any team’s usage and is paid even with no team. PC27: a shared item’s actual driver is known in the model (concurrent jobs, build minutes, series count) but is not in the bill. PC28: allocation rules can use only measures readable from the bill: equal share, observed resource-usage ratio, request count. PC29: once cost is shown to a team, each item drops by its own audit share; this is a single-period reaction. PC30: the central accounting pipeline has a fixed daily cost, and so does a team standing up its own accounting.
Teams, items, and the bill are all a model built within the lesson; there is no real bill.
// cost/item.mjs — team-based accounting model: there is no real bill. Teams, line items, and // prices are a data structure; the currency is fictional and is called `u`, and it does not // correspond to any real price list. A shared item's *actual driver* is known in the model, // it is not in the bill; allocation rules can only use measures readable from the bill. export const TEAM = ["meter-reading", "validation", "billing", "field", "reporting"]; export const N = TEAM.length; // directly assignable: name, daily u per team, drop share after visibility export const DIRECT = [ ["service instances", [96, 54, 120, 38, 72], 0.18], ["block storage", [24, 18, 40, 12, 58], 0.10], ["temporary environment copies", [30, 22, 26, 10, 34], 0.55], ]; // shared: name, daily u, driver name, driver values (known in the model), drop share export const SHARED = [ ["cluster control plane", 210, "concurrent jobs", [34, 12, 28, 6, 9], 0.00], ["shared build and deploy", 168, "build minutes", [220, 180, 260, 40, 610], 0.12], ["monitoring stack", 146, "series count", [8200, 3100, 5400, 900, 2600], 0.30], ]; // unallocatable: has no relationship to any team's usage; paid even with no team export const UNALLOCATABLE = [ ["regional backbone network", 88], ["audit log retention", 46], ["standby region", 64], ]; // team measures readable from the bill export const METRIC = { usage: [0.31, 0.17, 0.29, 0.08, 0.15], // observed resource-usage ratio requests: [412, 96, 138, 21, 33], // daily thousand requests }; export const CENTRAL = 120; // fixed daily cost of the central accounting pipeline (u) export const PER_TEAM = 41; // daily cost of a team standing up its own accounting (u) export const sum = (v) => v.reduce((a, b) => a + b, 0); export const ratio = (v) => { const t = sum(v); return v.map((x) => x / t); }; export const RULE = { equal: () => TEAM.map(() => 1 / N), usage: () => ratio(METRIC.usage), requests: () => ratio(METRIC.requests), }; // each shared item's *actual* per-team share: every item is split by its own driver export const actualShare = () => TEAM.map((_, e) => sum(SHARED.map(([, u, , d]) => u * ratio(d)[e]))); // the deviation a rule produces on shared items: charged minus actual export const deviation = (rule) => { const p = RULE[rule](), totalU = sum(SHARED.map((x) => x[1])), actual = actualShare(); return TEAM.map((_, e) => totalU * p[e] - actual[e]); };
// cost/measure.mjs — item breakdown, the deviation of three allocation rules, visibility's // effect on behavior, and central accounting's threshold. All numbers derive from the model above. import { TEAM, N, DIRECT, SHARED, UNALLOCATABLE, RULE, CENTRAL, PER_TEAM, sum, deviation } from "./item.mjs"; const write = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("")); const t0 = process.hrtime.bigint(); const D = sum(DIRECT.map((x) => sum(x[1]))); const P = sum(SHARED.map((x) => x[1])); const B = sum(UNALLOCATABLE.map((x) => x[1])); const T = D + P + B, pctOf = (a) => `${(100 * a / T).toFixed(1)}%`; const COUNT = DIRECT.length + SHARED.length + UNALLOCATABLE.length; console.log(`${N} teams, ${COUNT} items, daily ${T} u (fictional currency)`); console.log("\n1. item breakdown: what is directly assigned, what is shared, what is unallocatable"); const A = [-30, 17, 11, 9, 18, 9]; write(A, "item", "type", "daily u", "share", "actual driver", "audit"); for (const [name, v, d] of DIRECT) write(A, name, "direct", sum(v), pctOf(sum(v)), "team tag", `${(100 * d).toFixed(0)}%`); for (const [name, u, driver, , d] of SHARED) write(A, name, "shared", u, pctOf(u), driver, `${(100 * d).toFixed(0)}%`); for (const [name, u] of UNALLOCATABLE) write(A, name, "unallocatable", u, pctOf(u), "none", "0%"); write(A, "total", "-", T, "100%", "-", "-"); console.log(` direct ${D} u (${pctOf(D)}), shared ${P} u (${pctOf(P)}),`); console.log(` unallocatable ${B} u (${pctOf(B)}); not directly assignable ${pctOf(P + B)}`); console.log("\n2. allocation rule sweep: charged minus actual (u/day, shared items)"); const C = [-11, ...TEAM.map(() => 15), 12]; write(C, "rule", ...TEAM, "abs. total"); const score = {}; for (const k of Object.keys(RULE)) { const s = deviation(k); score[k] = sum(s.map(Math.abs)); write(C, k, ...s.map((x) => (x >= 0 ? "+" : "") + x.toFixed(1)), score[k].toFixed(1)); } const best = Object.keys(score).reduce((a, b) => (score[a] <= score[b] ? a : b)); for (const k of Object.keys(RULE)) { const s = deviation(k), i = s.indexOf(Math.max(...s)), j = s.indexOf(Math.min(...s)); console.log(` ${k.padEnd(9)} charges most to ${TEAM[i]} +${s[i].toFixed(1)}, ` + `saves most for ${TEAM[j]} ${s[j].toFixed(1)}`); } console.log(` least wrong rule: ${best}`); console.log(` every rule also spreads the ${B} u unallocatable item; that amount's deviation`); console.log(" cannot be measured, because there is no actual share to compare against"); console.log("\n3. visibility's effect on behavior: after cost is shown to a team"); const E = [-30, 11, 9, 11, 30]; write(E, "item", "daily u", "audit", "dropped u", "reason it stays fixed"); const items = [ ...DIRECT.map(([name, v, d]) => [name, sum(v), d, "-"]), ...SHARED.map(([name, u, , , d]) => [name, u, d, d ? "-" : "platform decision"]), ...UNALLOCATABLE.map(([name, u]) => [name, u, 0, "not under the team's control"]), ]; let dropped = 0, fixedU = 0, fixedCount = 0; for (const [name, u, d, reason] of items) { dropped += u * d; if (d === 0) { fixedU += u; fixedCount++; } write(E, name, u, `${(100 * d).toFixed(0)}%`, (u * d).toFixed(1), reason); } console.log(` total drop ${dropped.toFixed(1)} u/day (${(100 * dropped / T).toFixed(1)}%)`); console.log(` never moves ${fixedCount} items, ${fixedU} u/day (${pctOf(fixedU)})`); console.log("\n4. central accounting's threshold and the cost of opting out"); const F = [-8, 13, 15, 11, 18]; write(F, "teams", "central u", "separate u", "gain", "pays for itself"); for (const n of [1, 2, 3, 5, 8]) { const separate = n * PER_TEAM; write(F, n, CENTRAL, separate, (separate - CENTRAL).toFixed(0), separate > CENTRAL ? "yes" : "no"); } const s = deviation(best), sorted = TEAM.map((e, i) => [e, Math.abs(s[i])]) .sort((a, b) => b[1] - a[1]).slice(0, 2); const optOut = sorted.length * PER_TEAM, gross = N * PER_TEAM - CENTRAL; const threshold = Math.ceil(CENTRAL / PER_TEAM); console.log(` threshold ${threshold} teams; gross gain at ${N} teams ${gross} u/day`); console.log(` under the ${best} rule, the ones deviating most, ${sorted.map((x) => x[0]).join(" and ")}, stand up`); console.log(` their own measurement: ${optOut} u/day of duplicated measurement, net gain ` + `${gross - optOut} u/day`); const ms = Number(process.hrtime.bigint() - t0) / 1e6; // raw measurement is not printed, the band is audited console.log(`\nfour tables and rule sweep in the 2000 ms band: ${ms < 2000 ? "yes" : "no"}`);
5 teams, 9 items, daily 1376 u (fictional currency) 1. item breakdown: what is directly assigned, what is shared, what is unallocatable item type daily u share actual driver audit service instances direct 380 27.6% team tag 18% block storage direct 152 11.0% team tag 10% temporary environment copies direct 122 8.9% team tag 55% cluster control plane shared 210 15.3% concurrent jobs 0% shared build and deploy shared 168 12.2% build minutes 12% monitoring stack shared 146 10.6% series count 30% regional backbone network unallocatable 88 6.4% none 0% audit log retention unallocatable 46 3.3% none 0% standby region unallocatable 64 4.7% none 0% total - 1376 100% - - direct 654 u (47.5%), shared 524 u (38.1%), unallocatable 198 u (14.4%); not directly assignable 52.5% 2. allocation rule sweep: charged minus actual (u/day, shared items) rule meter-reading validation billing field reporting abs. total equal -62.9 +31.0 -33.6 +79.0 -13.5 220.0 usage -5.3 +15.3 +13.5 +16.1 -39.7 89.8 requests +140.7 -1.9 -35.1 -10.1 -93.6 281.4 equal charges most to field +79.0, saves most for meter-reading -62.9 usage charges most to field +16.1, saves most for reporting -39.7 requests charges most to meter-reading +140.7, saves most for reporting -93.6 least wrong rule: usage every rule also spreads the 198 u unallocatable item; that amount's deviation cannot be measured, because there is no actual share to compare against 3. visibility's effect on behavior: after cost is shown to a team item daily u audit dropped u reason it stays fixed service instances 380 18% 68.4 - block storage 152 10% 15.2 - temporary environment copies 122 55% 67.1 - cluster control plane 210 0% 0.0 platform decision shared build and deploy 168 12% 20.2 - monitoring stack 146 30% 43.8 - regional backbone network 88 0% 0.0 not under the team's control audit log retention 46 0% 0.0 not under the team's control standby region 64 0% 0.0 not under the team's control total drop 214.7 u/day (15.6%) never moves 4 items, 408 u/day (29.7%) 4. central accounting's threshold and the cost of opting out teams central u separate u gain pays for itself 1 120 41 -79 no 2 120 82 -38 no 3 120 123 3 yes 5 120 205 85 yes 8 120 328 208 yes threshold 3 teams; gross gain at 5 teams 85 u/day under the usage rule, the ones deviating most, reporting and field, stand up their own measurement: 82 u/day of duplicated measurement, net gain 3 u/day four tables and rule sweep in the 2000 ms band: yes
The numbers are of the measurement class; their inputs are the assumptions above.
Half the Bill Cannot Be Directly Assigned
The first table splits the daily 1,376 units into three types. Directly assignable items add up to 654 units (47.5%): service instances, block storage, and temporary environment copies carry a team tag, and which team used how much is readable from the bill. The remaining 52.5% is not.
Of that, 524 units (38.1%) are shared items. The cluster control plane, the shared build and deploy pipeline, and the monitoring stack are each run once for all five teams; this is exactly the layer the platform took over. It appears as a single lump sum, and belongs to nobody.
That leaves 198 units (14.4%) of unallocatable items. The regional backbone network, audit log retention, and the standby region cost the same amount whether there are three teams or five; no team’s departure shrinks them. This item has no owner and no driver — when an allocation rule is applied to it, the number it produces is not a measurement, it is a decision.
Each Rule Misleads a Different Team in a Different Direction
The second table sweeps the three rules over the shared 524 units. The point of comparison is the actual share known in the model: each shared item is split by its own driver. The deviation is the amount the rule charges minus the actual share; a plus sign shows the team was overcharged, a minus sign shows it got off easy.
Equal share gives the crudest result (absolute total 220.0). The field team is overcharged +79.0 units: it is the smallest of the five teams, its concurrent jobs are six, its build minutes are forty, its series count is nine hundred — yet it pays one-fifth. Meter reading gets off −62.9 units; it is the heaviest user of the cluster control plane but never sees that in its bill.
Usage ratio is the least wrong rule (89.8), but it is not one-directional: reporting gets off by −39.7, because even though its resource usage is low, it alone accounts for 46% of build minutes. The observed usage ratio never sees the build pipeline’s driver at all.
Request count is the most misleading rule (281.4). Meter reading is charged +140.7 units because it has the highest count, 412 thousand requests a day; yet its cost per request is low. Reporting gets off by −93.6: its nightly batch job never turns into a request.
The three rules mislead three different teams in three different directions, and none corrects another’s error. On top of that, the 198-unit unallocatable item is still spread out under every rule, and that amount’s deviation cannot be measured — there is no actual share to compare it against. Under showback this is a tolerable wrong: the number is shown, nobody pays it. Under chargeback the same number comes out of a budget, and the argument shifts onto the rule itself.
What Visibility Moves
The third table shows the cost to each team and counts what drops. The total drop is 214.7 units (15.6%). The drop does not come from where it is expected: the highest response rate is in temporary environment copies (55%, 67.1 units) — a team can delete an unused environment itself. Service instances drop a similar amount thanks to their size (68.4 units), but their rate is only 18%; changing size requires a deployment. The monitoring stack drops 30%, because a team can thin out its own tags.
Four items never move at all: 408 units, 29.7% of the bill. The cluster control plane is a platform decision; the regional backbone network, audit log retention, and standby region are not under any team’s control — audit log retention, in fact, is itself the cost of the policy rule from the previous lesson. Showing these items on a team’s bill produces no drop, only a question.
This is where the abstraction leaks. A team started using the platform to avoid seeing its own infrastructure layer; now four items sit on its bill, and changing their amount means learning that layer anyway. Showing an item nobody controls is not visibility — it is noise.
Central Accounting’s Threshold
The fourth table asks whether the platform pays off at all. The central accounting pipeline costs a fixed 120 units a day; a team standing up its own accounting pays 41 units. The threshold is three teams: at one team, the central pipeline is 79 units more expensive; at two teams, 38 units more expensive. With few teams, platform accounting does not pay for itself, and the table shows this is arithmetic, not a preference.
At five teams, the gross gain is 85 units. Onto that come the ones who opt out: reporting (−39.7) and field (+16.1), the two that deviate most under the usage rule, do not trust the result and stand up their own measurement. Their duplicated measurement costs 82 units; the gross 85-unit gain drops to 3 units. Five teams run central accounting at a point where it almost pays nothing, and the reason is not the cost itself — it is distrust of the allocation rule.
Summary
- Only 47.5% of a daily 1,376 fictional units is directly assigned; 38.1% is shared, 14.4% is unallocatable — 52.5% of the bill cannot be written to a team without a rule.
- The three allocation rules mislead three different teams in opposite directions: equal share loads field by +79.0, request count loads meter reading by +140.7 and lets reporting off by −93.6; even usage ratio, the least wrong rule, has an absolute deviation of 89.8 units.
- The 198-unit unallocatable item is spread out under every rule, but its deviation cannot be measured; a wrong that is tolerable under showback comes out of a budget under chargeback.
- The bill drops 15.6% once cost is shown; the highest response is in temporary environment copies (55%). Four items never move at all — 408 units, 29.7% — because they are not under a team’s control.
- Central accounting pays for itself after three teams; at two teams it is 38 units more expensive. At five teams the gross gain is 85 units, but two teams that do not trust the rule standing up their own measurement drags the gain down to 3 units.
Next Step
This lesson’s numbers say how much the platform costs, but not who it benefits. The next lesson spreads the question out over time: the five teams do not move to the new arrangement all at once, they move in waves, and the wave’s size sets both how long the two arrangements run side by side and how many are left behind. The cost to a team left behind is a line item that never appears in this lesson’s table at all.
To keep your progress and take notes, Log in
My notes
Log in to take notes.