sustain · Stage 4
engineering economicsで信頼性と容量へ投資する
直接費だけでなく機会費用、運用時間、信頼性損失、容量制約を同じ入力へ結び、unit economicsと感度分析から投資判断を再現する。
到達目標
直接費、機会費用、運用費、信頼性損失から比較可能なtotal costを導ける
- 機会費用、運用時間、信頼性、容量を含む投資比較
- 価格精度と入力限界を明示した投資判断
需要、容量、headroom、constraint breachをunit economicsへ結べる
- 機会費用、運用時間、信頼性、容量を含む投資比較
- 安価、低unit cost、容量適合、事業価値の違いを説明する5分発表
一つの仮定だけを変える感度分析で選択が変わる境界を説明できる
- 機会費用、運用時間、信頼性、容量を含む投資比較
- demand growthだけを変えた選択の感度分析
能力の進行
recognize
請求額、engineering時間、運用時間、reliability loss、capacityを別のcost driverとして識別できる
証拠: 機会費用、運用時間、信頼性、容量を含む投資比較
explain
最小direct costが最良の事業判断とは限らない理由をunit economicsで説明できる
証拠: 安価、低unit cost、容量適合、事業価値の違いを説明する5分発表
apply
固定入力からtotal cost、unit cost、capacity headroomを再計算できる
証拠: 機会費用、運用時間、信頼性、容量を含む投資比較
diagnose
価格精度の誇張、埋没費用、二重計上、需要予測の単一点依存を反証できる
証拠: 価格精度と入力限界を明示した投資判断
lead
product、finance、reliability、capacityのownerと感度範囲を合意できる
証拠: demand growthだけを変えた選択の感度分析
なぜ重要か
engineering投資はcloud invoiceだけでは比較できない。実装に使う時間は他の価値を遅らせるopportunity costになり、手作業はoperations hoursを消費し、障害はreliability costを生み、capacity不足は安価な案を実行不能にする。
FinOps Frameworkのunit economicsはcostをbusiness valueの単位へ結ぶ。AWS Well-ArchitectedとGoogle Cloud Well-Architectedのcost guidanceも、最安serviceの選択ではなく、workload要求、利用量、事業価値、継続的な見直しを扱う。
メンタルモデル
投資候補をcostの一点ではなく、制約を満たしたうえで価値を届けるoptionとして比べる。まず単位と期間を揃え、次にcapacity breachを除外し、残った候補をunit economicsとsensitivityで比較する。
costは精密な一値に見えても仮定の積み重ねである。best case、expected、worst caseを分けるsensitivityが、どの入力で選択が反転するかを示す。過去に支払ったsunk costは将来選択のbenefitへ足さない。
注記
図を読む際の補足情報です。
- Direct: fixtureで与えた取得・実行費を置く。
- Opportunity: engineering hoursと一時間の代替価値を掛ける。
- Operations: operations hoursと運用単価を掛ける。
- Reliability: 障害確率、時間、時間当たり影響を掛ける。
- Capacity: 需要と供給からheadroomとbreachを導く。
- Decision: 制約違反を先に、unit costを次に比較する。
投資候補ごとにcost構成とcapacity制約を同じ軸で比較し、どの順で判断するか。
| 項目 | direct costlesson-defined取得・実行費。provider quoteではない。 | opportunity costengineering hours × 100/hour。 | operations costoperations hours × 80/hour。 | reliability lossfailure probability × incident hours × 10000/hour。 | total・capacity・unit cost四cost合計、required capacity、served units、headroom、total/served units。 | demand-growth sensitivitybase demand 800でgrowthだけを変え、constraint breachをunit costより先に評価する。 |
|---|---|---|---|---|---|---|
| scale-upsynthetic fixture: capacity 1000、direct 12000、engineering 40h、operations 20h、failure 0.05×8h。 | 12000 | 40×100 = 4000 | 20×80 = 1600 | 0.05×8×10000 = 4000 | 12000 + 40×100 + 20×80 + 0.05×8×10000 = 21600; 21600/800 = 27.00/unit | growth 0.25でrequired capacity 1000、headroom 0; 0.25超でbreach |
| automationsynthetic fixture: capacity 1600、direct 16000、engineering 120h、operations 5h、failure 0.01×2h。 | 16000 | 120×100 = 12000 | 5×80 = 400 | 0.01×2×10000 = 200 | 16000 + 120×100 + 5×80 + 0.01×2×10000 = 28600; 28600/800 = 35.75/unit | growth 0.5でrequired capacity 1200、headroom 400、automationを選択 |
制約違反候補を先に除き、direct・opportunity・operations・reliabilityを合算したtotal costとunit costを比較し、一変数の感度境界を説明できる。
動く例で考える
scale-upとautomationの投資比較
- 前提
- 二候補のlesson-defined synthetic fixtureを同じ期間で比較する。金額はproduction forecastでもprovider quoteでもない。
- 入力
- direct cost、engineering hours、operations hours、incident確率と影響、capacity、base demand 800 unitsを与える。
- 操作
- 各cost componentとtotal cost、served units当たりunit cost、capacity headroomを導く。transferではdemand growthだけを0から0.5へ変える。
- 観測
- baselineではscale-upのunit costが低い。需要が1.5倍になるとscale-upはrequired capacityを満たさず、同じ候補とcost入力でもautomationが選ばれる。
- 結論
- command successとして表を生成できても、system outcomeとして必要容量とreliability制約を満たすとは限らない。制約と感度を含む選択が必要である。
python3.13 - <<'PY'
import json
HARNESS = "engineering_economics_lab_v1"
BASE_DEMAND = 800
BASELINE_ASSUMPTIONS = {
"demand_growth": 0.0,
"engineering_hour_value": 100,
"operations_hour_cost": 80,
"loss_per_incident_hour": 10000,
}
TRANSFER_ASSUMPTIONS = {
"engineering_hour_value": 100,
"operations_hour_cost": 80,
"loss_per_incident_hour": 10000,
"demand_growth": 0.5,
}
INVESTMENT_CANDIDATES = [
{
"id": "scale-up",
"direct_cost": 12000,
"engineering_hours": 40,
"operations_hours": 20,
"failure_probability": 0.05,
"expected_incident_hours": 8,
"capacity": 1000,
},
{
"id": "automation",
"direct_cost": 16000,
"engineering_hours": 120,
"operations_hours": 5,
"failure_probability": 0.01,
"expected_incident_hours": 2,
"capacity": 1600,
},
]
PRICE_CONTEXT = {
"kind": "synthetic-fixture",
"claim_current_market_prices": False,
"limitation": (
"地域、通貨、税、契約、discount、観測日を含まない"
),
}
COMMAND_INPUT = {"exit_code": 0}
ASSUMPTION_FIELDS = {
"demand_growth",
"engineering_hour_value",
"operations_hour_cost",
"loss_per_incident_hour",
}
CANDIDATE_FIELDS = {
"id",
"direct_cost",
"engineering_hours",
"operations_hours",
"failure_probability",
"expected_incident_hours",
"capacity",
}
def is_number(value):
return type(value) in {int, float} and type(value) is not bool
def validate_assumption_transfer(baseline, transferred):
for assumptions in (baseline, transferred):
if (
type(assumptions) is not dict
or set(assumptions) != ASSUMPTION_FIELDS
or not all(
is_number(value) and value >= 0
for value in assumptions.values()
)
):
raise AssertionError(
"economics-transfer-invariant: invalid assumption schema"
)
changed = [
field
for field in sorted(set(baseline) | set(transferred))
if (
field not in baseline
or field not in transferred
or baseline[field] != transferred[field]
)
]
if changed != ["demand_growth"]:
raise AssertionError(
"economics-transfer-invariant: transfer changed wrong input"
)
return changed
def validate_candidate_snapshot(candidates):
valid = (
type(candidates) is list
and bool(candidates)
and all(
type(candidate) is dict
and set(candidate) == CANDIDATE_FIELDS
and type(candidate["id"]) is str
and bool(candidate["id"])
and all(
is_number(candidate[field])
and candidate[field] >= 0
for field in CANDIDATE_FIELDS - {"id"}
)
and candidate["capacity"] > 0
and candidate["failure_probability"] <= 1
for candidate in candidates
)
and len({candidate["id"] for candidate in candidates})
== len(candidates)
)
if not valid:
raise AssertionError(
"economics-candidate-invariant: invalid candidate schema"
)
def fixed_candidate_snapshot():
# JSON値から別々のfull snapshotを作り、ID一致だけでnested costや
# capacityのdriftを見逃さない。
return json.loads(
json.dumps(INVESTMENT_CANDIDATES, ensure_ascii=False)
)
def validate_economics_context():
if (
not is_number(BASE_DEMAND)
or BASE_DEMAND <= 0
or type(PRICE_CONTEXT) is not dict
or set(PRICE_CONTEXT)
!= {"kind", "claim_current_market_prices", "limitation"}
or type(PRICE_CONTEXT["kind"]) is not str
or not PRICE_CONTEXT["kind"]
or type(PRICE_CONTEXT["claim_current_market_prices"]) is not bool
or type(PRICE_CONTEXT["limitation"]) is not str
or not PRICE_CONTEXT["limitation"]
or type(COMMAND_INPUT) is not dict
or set(COMMAND_INPUT) != {"exit_code"}
or type(COMMAND_INPUT["exit_code"]) is not int
):
raise AssertionError(
"economics-candidate-invariant: invalid context schema"
)
def compare_investment(candidate, assumptions):
required_capacity = round(
BASE_DEMAND * (1 + assumptions["demand_growth"])
)
direct_cost = candidate["direct_cost"]
opportunity_cost = (
candidate["engineering_hours"]
* assumptions["engineering_hour_value"]
)
operations_cost = (
candidate["operations_hours"]
* assumptions["operations_hour_cost"]
)
reliability_cost = (
candidate["failure_probability"]
* candidate["expected_incident_hours"]
* assumptions["loss_per_incident_hour"]
)
total_cost = direct_cost + opportunity_cost + operations_cost + reliability_cost
served_units = min(candidate["capacity"], required_capacity)
capacity_headroom = candidate["capacity"] - required_capacity
constraint_breaches = int(capacity_headroom < 0)
return {
"id": candidate["id"],
"direct_cost": direct_cost,
"opportunity_cost": opportunity_cost,
"operations_cost": operations_cost,
"reliability_cost": reliability_cost,
"total_cost": total_cost,
"served_units": served_units,
"unit_cost": total_cost / served_units,
"operations_hours": candidate["operations_hours"],
"capacity": candidate["capacity"],
"required_capacity": required_capacity,
"capacity_headroom": capacity_headroom,
"constraint_breaches": constraint_breaches,
}
def compare_all(candidates, assumptions):
return [
compare_investment(candidate, assumptions)
for candidate in candidates
]
def select_investment(comparisons):
return min(
comparisons,
key=lambda option: (
option["constraint_breaches"],
option["unit_cost"],
),
)["id"]
def main():
validate_economics_context()
changed_fields = validate_assumption_transfer(
BASELINE_ASSUMPTIONS,
TRANSFER_ASSUMPTIONS,
)
validate_candidate_snapshot(INVESTMENT_CANDIDATES)
baseline_assumptions = json.loads(
json.dumps(BASELINE_ASSUMPTIONS, ensure_ascii=False)
)
transferred_assumptions = json.loads(
json.dumps(TRANSFER_ASSUMPTIONS, ensure_ascii=False)
)
baseline_candidates = fixed_candidate_snapshot()
transferred_candidates = fixed_candidate_snapshot()
validate_candidate_snapshot(baseline_candidates)
validate_candidate_snapshot(transferred_candidates)
baseline = compare_all(
baseline_candidates,
baseline_assumptions,
)
if baseline_candidates != transferred_candidates:
raise AssertionError(
"economics-candidate-invariant: candidate snapshots drifted"
)
transferred = compare_all(
transferred_candidates,
transferred_assumptions,
)
for option in baseline + transferred:
component_sum = (
option["direct_cost"]
+ option["opportunity_cost"]
+ option["operations_cost"]
+ option["reliability_cost"]
)
assert (
option["total_cost"] == component_sum
), "economics-causal-invariant"
assert (
option["unit_cost"]
== option["total_cost"] / option["served_units"]
), "economics-unit-invariant"
assert (
option["capacity_headroom"]
== option["capacity"] - option["required_capacity"]
), "economics-capacity-invariant"
baseline_selected = select_investment(baseline)
transferred_selected = select_investment(transferred)
same_candidates = baseline_candidates == transferred_candidates
sensitivity_recomputed = (
baseline[0]["required_capacity"]
!= transferred[0]["required_capacity"]
)
system_outcome = all(
option["constraint_breaches"] == 0
for option in baseline
if option["id"] == baseline_selected
)
assert baseline_selected == "scale-up", "economics-baseline-invariant"
assert transferred_selected == "automation", "economics-transfer-invariant"
report = {
"harness": HARNESS,
"investment_comparison": baseline,
"decision": {
"selected_option": baseline_selected,
"selection_order": [
"constraint_breaches",
"unit_cost",
],
},
"sensitivity_comparison": transferred,
"demand_growth_transfer": {
"changed_assumption": "demand-growth",
"changed_fields": changed_fields,
"baseline_assumptions": baseline_assumptions,
"transferred_assumptions": transferred_assumptions,
"same_investment_candidates": same_candidates,
"baseline_candidate_snapshot": baseline_candidates,
"transferred_candidate_snapshot": transferred_candidates,
"baseline_selected_option": baseline_selected,
"transferred_selected_option": transferred_selected,
"baseline_demand_growth": baseline_assumptions[
"demand_growth"
],
"transferred_demand_growth": transferred_assumptions[
"demand_growth"
],
"sensitivity_recomputed": sensitivity_recomputed,
},
"price_claim": {
"current_price_claimed": PRICE_CONTEXT[
"claim_current_market_prices"
],
"fixture_only": PRICE_CONTEXT["kind"]
== "synthetic-fixture",
"accuracy_limitation": PRICE_CONTEXT["limitation"],
},
"fixture_metadata": {
"kind": "synthetic",
"provenance": "lesson-defined engineering economics fixture",
"limitations": PRICE_CONTEXT["limitation"],
"synthetic_or_observed_explicit": True,
},
"runtime_bound": {
"records": (
len(INVESTMENT_CANDIDATES)
* len([baseline_assumptions, transferred_assumptions])
),
"subprocesses": 0,
"maximum_iterations": len(baseline_candidates),
},
"external_network_used": False,
"command_success_distinction": {
"command_completed": COMMAND_INPUT["exit_code"] == 0,
"system_outcome_checked": system_outcome,
"command_success_equals_system_outcome": (
COMMAND_INPUT["exit_code"] == 0
and not system_outcome
),
"outcome_evidence": {
"selected": baseline_selected,
"constraint_breaches": [
option["constraint_breaches"]
for option in baseline
if option["id"] == baseline_selected
],
},
},
"mastery_evidence": {
"lab_steps": [
{
"step": index,
"evidence": evidence,
}
for index, evidence in enumerate(
[
PRICE_CONTEXT,
baseline_candidates,
[
option["capacity_headroom"]
for option in baseline
],
baseline_selected,
transferred_selected,
],
start=1,
)
],
"assessments": [
{
"assessment": 1,
"evidence": [
option["total_cost"]
for option in baseline
],
},
{
"assessment": 2,
"evidence": PRICE_CONTEXT["limitation"],
},
],
"rubric_dimensions": [
"technical-correctness",
"judgment",
"evidence",
"communication",
],
"transfer": {
"task": (
"demand growthだけを変え、同じ投資候補の"
"unit economicsと選択を感度分析で再評価する"
),
"changed_assumption": "demand-growth",
"evidence": {
"baseline": baseline_selected,
"transferred": transferred_selected,
},
},
},
}
print(json.dumps(report, ensure_ascii=False, sort_keys=True))
try:
main()
except AssertionError as error:
raise SystemExit(str(error)) from None
PY
注記
図を読む際の補足情報です。
- 金額とcapacityはlesson-defined synthetic fixtureであり、provider quoteや予測精度を主張しない。
- 候補はまずconstraintを満たすかで絞り、その後にunit costと負の帰結を比較する。
- 需要成長以外の仮定は固定し、一変数の感度として読む。
同じ合成fixtureで需要成長だけを変えたとき、costより先にcapacity constraintが判断をどう変えるか。
| 項目 | baseline需要800成長前のrequired capacityとunit costを比較する。 | 需要25%増・1000scale-upのheadroomが0になる境界。 | 需要50%増・1200capacity constraint違反を先に判定する。 | 判断feasibility、cost、再評価条件をまとめる。 |
|---|---|---|---|---|
| scale-upcapacity 1000、total cost 21600の合成候補。 | headroom 200; 27.00/unit; feasible | headroom 0; constraint境界 | 200不足; constraint違反 | baselineは低cost。25%超で再評価 |
| automationcapacity 1600、total cost 28600の合成候補。 | headroom 800; 35.75/unit; feasible | headroom 600; feasible | headroom 400; feasible | 50%増では唯一feasible。高い初期costも併記 |
baselineでは低costのscale-upを選べても、需要50%増ではcapacity constraint違反によりautomationだけがfeasibleになる境界を説明できる。
トレードオフと失敗モード
| 観測 | 判断 | 次の行動 | 残す証拠 |
|---|---|---|---|
| capacity breachあり | その需要条件では選ばない | 需要抑制、分割、別候補を比較 | required capacityとheadroom |
| 制約内でunit costが低い | 小さく採用 | 実績を観測して入力を校正 | served unit、cost、reliability |
| 仮定で選択が反転 | 段階投資 | 反転入力を先に測定 | sensitivity rangeとowner |
| 価格時点が不明 | 予算断定を停止 | official quoteと契約を更新 | 地域、通貨、期間、観測日 |
- 誤診: direct costが最小なので最も経済的である。反証: opportunity cost、operations hours、reliability loss、capacity breachを同じ期間と単位で加える。
- 誤診: 小数点まで計算したのでprice accuracyが高い。反証: 金額の出自、観測日、地域、契約、uncertaintyを示し、入力範囲でsensitivityを再実行する。
知識チェック
- engineering hoursをopportunity costへ変換するときの二重計上をどう防ぐか。
- unit costが低くてもcapacity不足の候補を選べない理由を説明せよ。
- reliabilityを金額へ換算できない場合、どの制約と定量証拠を残すか。
- command successとsystem outcomeを分ける投資後の観測を三つ挙げよ。
出典と次の学習
FinOps FrameworkのUnit EconomicsとArchitecting and Workload Placement、AWS Well-Architected Cost Optimization Pillar、Google Cloud Well-Architected cost optimization pillarを、事業価値、workload制約、継続改善の一次guidanceとして使った。
次はproviderのcurrent pricing、組織のengineering cost、incident実績、capacity観測を同じschemaへ入れ、fixtureとの差とprice accuracyの限界を明記した投資reviewを行う。
実践ラボ
二つのcapacity投資をunit economicsで比較する
提出成果物: 機会費用、運用時間、信頼性、容量を含む投資比較
- 価格ではなくlesson-defined synthetic inputsであることと限界を記録する
- 各候補のdirect cost、engineering hours、operations hours、reliability inputsを固定する
- 需要からrequired capacity、headroom、constraint breachを導く
- total costとserved unitあたりcostを計算してbaselineを選ぶ
- demand growthだけを変え同じ候補を感度分析して選択を再評価する
説明して理解を確かめる
5分で、FinOps Frameworkのunit economics、AWSとGoogle Cloudのcost guidanceを価格表としてではなく事業価値とworkload制約を結ぶ判断枠として使う理由を説明する。
アセスメント
問い: direct costが最小の候補に運用時間とincident riskが集中する。比較式をどう直すか。
期待する証拠: opportunity cost、operations cost、reliability cost、二重計上防止、入力owner
問い: provider価格を確認せずにこのfixtureからproduction予算を断定できるか。
期待する証拠: synthetic fixture、currencyと期間、地域と契約、価格更新日、sensitivity range
別問題へ転用する
demand growthだけを変え、同じ投資候補のunit economicsと選択を感度分析で再評価する
復習スケジュール
- 1日後
opportunity costをdirect costと分ける理由は何か
- 7日後
capacity constraintをunit costより先に評価する条件は何か
- 30日後
価格精度を誇張せず投資判断を更新するには何を記録するか
- 90日後
opportunity costをdirect costと分ける理由は何か
評価ルーブリック
| 観点 | 未達 | 発展途上 | 熟達 | 卓越 |
|---|---|---|---|---|
| technical-correctness | direct costだけを比較する | 複数costはあるが単位、期間、capacity constraintが一致しない | 全cost driver、served units、headroom、constraint breachを同じ入力から導く | 二重計上、uncertainty、option value、需要分布でも式の境界を維持する |
| judgment | 最安値を自動的に選ぶ | 信頼性を挙げるが事業影響へ換算しない | cost、reliability、capacity、可逆性、時間軸から選ぶ | 投資を小さなoptionへ分け、観測に応じて中止または拡大できる |
| evidence | 金額だけを示し入力出自がない | 計算表はあるが価格時点とsynthetic限界がない | 入力、式、単位、結果、制約、感度、限界を再実行可能に残す | 実績との差を継続校正し、仮定変更が選択へ伝播する証拠を持つ |
| communication | 安いまたは高いとだけ報告する | total costはあるが事業単位とtrade-offが読めない | どの仮定、cost driver、制約が選択を決めたか説明する | engineering、finance、product、operationsが同じ感度表で異議を検証できる |
出典
以下の外部資料は利用者が選択したときだけ開きます。
- FinOps Framework Capability: Unit Economics (accessed 2026-07-31) (primary)
- FinOps Framework Capability: Architecting & Workload Placement (accessed 2026-07-31) (primary)
- AWS Well-Architected Framework: Cost Optimization Pillar (June 27, 2024) (primary)
- Google Cloud Well-Architected Framework: Cost optimization pillar (accessed 2026-07-31) (primary)