-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrunning_costs.py
More file actions
121 lines (102 loc) · 3.85 KB
/
running_costs.py
File metadata and controls
121 lines (102 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""
running_costs.py — Load running costs CSV and compute monthly field costs.
Cost types:
- onetime/per_rig: charged once per new rig added (COGS)
- monthly/per_rig: charged every month per active rig
- monthly/per_n_rigs: charged every month, scales per N rigs (e.g. 1 per 10 rigs)
- monthly/fixed: flat monthly cost regardless of rigs
- monthly/per_rig with scaling_param: depreciation (amount / scaling_param months)
Phase filtering:
- 'mvp': only active during MVP phase
- 'prod': only active during production phase
- 'all' or empty: active in both phases
"""
import csv
import math
def load_running_costs(filepath: str) -> list[dict]:
"""Load running costs CSV into a list of cost item dicts."""
items = []
with open(filepath, "r", encoding="utf-8-sig") as f:
reader = csv.DictReader(f)
for row in reader:
item = {
"cost_class": row["cost_class"].strip(),
"item": row["item"].strip(),
"amount": float(row["amount"]),
"frequency": row["frequency"].strip(),
"scaling": row["scaling"].strip(),
"scaling_param": float(row["scaling_param"]) if row["scaling_param"].strip() else None,
"phase": row["phase"].strip() if row["phase"].strip() else "all",
}
items.append(item)
return items
def is_active_phase(item_phase: str, current_phase: str) -> bool:
"""Check if a cost item is active in the current phase."""
if item_phase == "all":
return True
return item_phase == current_phase
def compute_monthly_running_costs(
cost_items: list[dict],
phase: str,
rig_count: int,
new_rigs: int,
) -> dict:
"""Compute running costs for a single month.
Returns dict with:
- total: total monthly running costs
- by_class: {cost_class: amount}
- by_item: [{item, cost_class, amount}]
- total_cogs: one-time costs for new rigs
- total_recurring: monthly recurring costs
- total_depreciation: monthly depreciation
"""
by_class = {}
by_item = []
total = 0.0
total_cogs = 0.0
total_recurring = 0.0
total_depreciation = 0.0
for item in cost_items:
if not is_active_phase(item["phase"], phase):
cost = 0.0
elif item["frequency"] == "onetime" and item["scaling"] == "per_rig":
cost = new_rigs * item["amount"]
total_cogs += cost
elif item["frequency"] == "monthly" and item["scaling"] == "per_rig":
if item["cost_class"] == "depreciation" and item["scaling_param"] is not None:
monthly_dep = item["amount"] / item["scaling_param"]
cost = rig_count * monthly_dep
total_depreciation += cost
else:
cost = rig_count * item["amount"]
total_recurring += cost
elif item["frequency"] == "monthly" and item["scaling"] == "per_n_rigs":
n = item["scaling_param"]
if rig_count > 0:
units = math.ceil(rig_count / n)
else:
units = 0
cost = units * item["amount"]
total_recurring += cost
elif item["frequency"] == "monthly" and item["scaling"] == "fixed":
cost = item["amount"]
total_recurring += cost
else:
cost = 0.0
total += cost
cls = item["cost_class"]
by_class[cls] = by_class.get(cls, 0) + cost
if cost > 0:
by_item.append({
"item": item["item"],
"cost_class": cls,
"amount": cost,
})
return {
"total": total,
"by_class": by_class,
"by_item": by_item,
"total_cogs": total_cogs,
"total_recurring": total_recurring,
"total_depreciation": total_depreciation,
}