jeffrey1963 commited on
Commit
e1839f1
·
verified ·
1 Parent(s): f65fa5c

Create repair_table_data.py

Browse files
Files changed (1) hide show
  1. repair_table_data.py +213 -0
repair_table_data.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # repair_table_data.py
2
+ # Full Iowa State "Accumulated Repair Costs as a Percentage of New List Price" (Table 3)
3
+ # Source figure: American Society of Agricultural Engineers (1996).
4
+ # Provides: REPAIR_TABLE, get_repair_fraction(machine, hours), list_machines()
5
+
6
+ from typing import Dict, Tuple, Optional, List
7
+ import re
8
+
9
+ # ----------------------------
10
+ # 1) Canonical table (fractions, not percents)
11
+ # ----------------------------
12
+ REPAIR_TABLE: Dict[str, Dict[int, float]] = {
13
+ # --- Tractors ---
14
+ "Two-wheel drive tractor": {
15
+ 1000: 0.01, 2000: 0.03, 3000: 0.06, 4000: 0.11, 5000: 0.18,
16
+ 6000: 0.25, 7000: 0.34, 8000: 0.45, 9000: 0.57, 10000: 0.70
17
+ },
18
+ "Four-wheel drive tractor": {
19
+ 1000: 0.00, 2000: 0.01, 3000: 0.03, 4000: 0.05, 5000: 0.08,
20
+ 6000: 0.11, 7000: 0.15, 8000: 0.19, 9000: 0.24, 10000: 0.30
21
+ },
22
+
23
+ # --- Tillage Implements ---
24
+ "Moldboard plow": {200:0.02, 400:0.06, 600:0.12, 800:0.19, 1000:0.29,
25
+ 1200:0.40, 1400:0.53, 1600:0.68, 1800:0.84, 2000:1.01},
26
+ "Heavy-duty disk": {200:0.01, 400:0.04, 600:0.08, 800:0.12, 1000:0.18,
27
+ 1200:0.25, 1400:0.32, 1600:0.40, 1800:0.49, 2000:0.58},
28
+ "Tandem disk": {200:0.01, 400:0.04, 600:0.08, 800:0.12, 1000:0.18,
29
+ 1200:0.25, 1400:0.32, 1600:0.40, 1800:0.49, 2000:0.58},
30
+ "Chisel plow": {200:0.03, 400:0.08, 600:0.14, 800:0.20, 1000:0.28,
31
+ 1200:0.36, 1400:0.45, 1600:0.54, 1800:0.64, 2000:0.74},
32
+ "Field cultivator": {200:0.03, 400:0.07, 600:0.13, 800:0.20, 1000:0.27,
33
+ 1200:0.35, 1400:0.43, 1600:0.52, 1800:0.61, 2000:0.71},
34
+ "Harrow": {200:0.02, 400:0.05, 600:0.08, 800:0.12, 1000:0.16,
35
+ 1200:0.22, 1400:0.29, 1600:0.37, 1800:0.46, 2000:0.56},
36
+ "Roller-packer, mulcher": {200:0.02, 400:0.05, 600:0.08, 800:0.12, 1000:0.16,
37
+ 1200:0.22, 1400:0.29, 1600:0.37, 1800:0.46, 2000:0.56},
38
+ "Rotary hoe": {200:0.02, 400:0.06, 600:0.11, 800:0.17, 1000:0.23,
39
+ 1200:0.29, 1400:0.37, 1600:0.44, 1800:0.52, 2000:0.60},
40
+ "Row crop cultivator": {200:0.00, 400:0.02, 600:0.06, 800:0.10, 1000:0.17,
41
+ 1200:0.25, 1400:0.36, 1600:0.48, 1800:0.62, 2000:0.78},
42
+
43
+ # --- Harvesters & Hay ---
44
+ "Corn picker": {200:0.00, 400:0.02, 600:0.04, 800:0.08, 1000:0.14,
45
+ 1200:0.21, 1400:0.30, 1600:0.41, 1800:0.54, 2000:0.69},
46
+ "Combine (pull)": {200:0.00, 400:0.02, 600:0.04, 800:0.08, 1000:0.14,
47
+ 1200:0.21, 1400:0.30, 1600:0.41, 1800:0.54, 2000:0.69},
48
+ "Potato harvester": {200:0.02, 400:0.05, 600:0.09, 800:0.14, 1000:0.19,
49
+ 1200:0.25, 1400:0.32, 1600:0.39, 1800:0.46, 2000:0.54},
50
+ "Mower-conditioner": {200:0.01, 400:0.04, 600:0.08, 800:0.13, 1000:0.18,
51
+ 1200:0.24, 1400:0.31, 1600:0.38, 1800:0.46, 2000:0.55},
52
+ "Mower-conditioner (rotary)": {200:0.01, 400:0.04, 600:0.08, 800:0.13, 1000:0.18,
53
+ 1200:0.24, 1400:0.31, 1600:0.38, 1800:0.46, 2000:0.55},
54
+ "Rake": {200:0.02, 400:0.05, 600:0.09, 800:0.12, 1000:0.16,
55
+ 1200:0.22, 1400:0.27, 1600:0.33, 1800:0.39, 2000:0.45},
56
+ "Rectangular baler": {200:0.01, 400:0.03, 600:0.06, 800:0.10, 1000:0.15,
57
+ 1200:0.20, 1400:0.26, 1600:0.32, 1800:0.38, 2000:0.45},
58
+ "Large square baler": {200:0.01, 400:0.03, 600:0.06, 800:0.10, 1000:0.15,
59
+ 1200:0.20, 1400:0.26, 1600:0.32, 1800:0.38, 2000:0.45},
60
+ "Forage harvester (pull)": {200:0.01, 400:0.03, 600:0.07, 800:0.10, 1000:0.15,
61
+ 1200:0.20, 1400:0.26, 1600:0.32, 1800:0.38, 2000:0.45},
62
+
63
+ "Forage harvester (SP)": {300:0.00, 600:0.01, 900:0.02, 1200:0.04, 1500:0.07,
64
+ 1800:0.10, 2100:0.13, 2400:0.17, 2700:0.22, 3000:0.27},
65
+ "Combine (SP)": {300:0.00, 600:0.01, 900:0.02, 1200:0.04, 1500:0.07,
66
+ 1800:0.10, 2100:0.13, 2400:0.17, 2700:0.22, 3000:0.27},
67
+ "Windrower (SP)": {300:0.01, 600:0.02, 900:0.04, 1200:0.09, 1500:0.15,
68
+ 1800:0.23, 2100:0.32, 2400:0.42, 2700:0.53, 3000:0.66},
69
+ "Cotton picker (SP)": {300:0.01, 600:0.04, 900:0.09, 1200:0.15, 1500:0.23,
70
+ 1800:0.32, 2100:0.42, 2400:0.53, 2700:0.66, 3000:0.79},
71
+
72
+ # --- Other Implements / Misc ---
73
+ "Mower (sickle)": {100:0.01, 200:0.03, 300:0.06, 400:0.10, 500:0.14,
74
+ 600:0.19, 700:0.25, 800:0.31, 900:0.38, 1000:0.46},
75
+ "Mower (rotary)": {100:0.00, 200:0.02, 300:0.04, 400:0.07, 500:0.11,
76
+ 600:0.16, 700:0.22, 800:0.28, 900:0.36, 1000:0.44},
77
+ "Large round baler": {100:0.01, 200:0.03, 300:0.06, 400:0.10, 500:0.15,
78
+ 600:0.20, 700:0.26, 800:0.33, 900:0.40, 1000:0.48},
79
+ "Sugar beet harvester": {100:0.03, 200:0.07, 300:0.12, 400:0.18, 500:0.24,
80
+ 600:0.30, 700:0.37, 800:0.44, 900:0.51, 1000:0.59},
81
+ "Rotary tiller": {100:0.01, 200:0.03, 300:0.06, 400:0.09, 500:0.13,
82
+ 600:0.17, 700:0.22, 800:0.27, 900:0.33, 1000:0.40},
83
+ "Row crop planter": {100:0.00, 200:0.01, 300:0.03, 400:0.05, 500:0.07,
84
+ 600:0.09, 700:0.11, 800:0.15, 900:0.20, 1000:0.26},
85
+ "Grain drill": {100:0.01, 200:0.03, 300:0.06, 400:0.09, 500:0.13,
86
+ 600:0.19, 700:0.26, 800:0.32, 900:0.40, 1000:0.47},
87
+ "Fertilizer spreader": {100:0.03, 200:0.08, 300:0.13, 400:0.19, 500:0.26,
88
+ 600:0.32, 700:0.40, 800:0.47, 900:0.55, 1000:0.63},
89
+
90
+ # --- Sprayers & Wagons ---
91
+ "Boom-type sprayer": {200:0.05, 400:0.12, 600:0.21, 800:0.31, 1000:0.41,
92
+ 1200:0.52, 1400:0.63, 1600:0.76, 1800:0.88, 2000:1.01},
93
+ "Air-carrier sprayer": {200:0.02, 400:0.05, 600:0.09, 800:0.14, 1000:0.20,
94
+ 1200:0.27, 1400:0.34, 1600:0.42, 1800:0.51, 2000:0.61},
95
+ "Bean puller-windrower": {200:0.02, 400:0.05, 600:0.09, 800:0.14, 1000:0.20,
96
+ 1200:0.27, 1400:0.34, 1600:0.42, 1800:0.51, 2000:0.61},
97
+ "Stalk chopper": {200:0.03, 400:0.08, 600:0.14, 800:0.20, 1000:0.28,
98
+ 1200:0.36, 1400:0.45, 1600:0.54, 1800:0.64, 2000:0.74},
99
+ "Forage blower": {200:0.01, 400:0.04, 600:0.09, 800:0.15, 1000:0.22,
100
+ 1200:0.29, 1400:0.37, 1600:0.46, 1800:0.56, 2000:0.67},
101
+ "Wagon": {200:0.01, 400:0.04, 600:0.07, 800:0.11, 1000:0.16,
102
+ 1200:0.21, 1400:0.27, 1600:0.34, 1800:0.41, 2000:0.49},
103
+ "Forage wagon": {200:0.02, 400:0.06, 600:0.10, 800:0.14, 1000:0.19,
104
+ 1200:0.24, 1400:0.29, 1600:0.35, 1800:0.41, 2000:0.47},
105
+ }
106
+
107
+ # ----------------------------
108
+ # 2) Name normalization / synonyms
109
+ # ----------------------------
110
+ _CANON = {k.lower(): k for k in REPAIR_TABLE.keys()}
111
+
112
+ _SYNONYMS = {
113
+ # tractors
114
+ "2wd": "Two-wheel drive tractor",
115
+ "two wheel drive": "Two-wheel drive tractor",
116
+ "two-wheel drive": "Two-wheel drive tractor",
117
+ "two wheel drive tractor": "Two-wheel drive tractor",
118
+
119
+ "4wd": "Four-wheel drive tractor",
120
+ "four wheel drive": "Four-wheel drive tractor",
121
+ "four-wheel drive": "Four-wheel drive tractor",
122
+ "four wheel drive tractor": "Four-wheel drive tractor",
123
+
124
+ # common short names
125
+ "tractor": "Two-wheel drive tractor", # default if just "tractor"
126
+ "sp forage harvester": "Forage harvester (SP)",
127
+ "sp combine": "Combine (SP)",
128
+ "sp windrower": "Windrower (SP)",
129
+ }
130
+
131
+ def _normalize_name(s: str) -> Optional[str]:
132
+ key = re.sub(r"\s+", " ", s.strip().lower())
133
+ if key in _CANON:
134
+ return _CANON[key]
135
+ if key in _SYNONYMS:
136
+ return _SYNONYMS[key]
137
+ # try loose contains
138
+ for alias, canon in _SYNONYMS.items():
139
+ if alias in key:
140
+ return canon
141
+ # last resort: pick a canonical that fully appears
142
+ for canon_lower, canon in _CANON.items():
143
+ if canon_lower in key:
144
+ return canon
145
+ return None
146
+
147
+ # ----------------------------
148
+ # 3) Interpolation / extrapolation
149
+ # ----------------------------
150
+ def _sorted_points(machine: str) -> List[Tuple[int, float]]:
151
+ pts = list(REPAIR_TABLE[machine].items())
152
+ pts.sort(key=lambda x: x[0])
153
+ return pts
154
+
155
+ def _interp(x0: float, y0: float, x1: float, y1: float, x: float) -> float:
156
+ if x1 == x0:
157
+ return y0
158
+ return y0 + (y1 - y0) * ((x - x0) / (x1 - x0))
159
+
160
+ def get_repair_fraction(machine_name: str, hours: float) -> Tuple[float, str, Tuple[int, float], Tuple[int, float]]:
161
+ """
162
+ Returns:
163
+ fraction (0..1+), canonical_machine_name, (x_lo,y_lo), (x_hi,y_hi)
164
+ Uses linear interpolation between nearest table points. Extrapolates gently
165
+ beyond edges (linear using last two points).
166
+ """
167
+ if hours < 0:
168
+ hours = 0.0
169
+
170
+ canon = _normalize_name(machine_name) or "Two-wheel drive tractor"
171
+ pts = _sorted_points(canon)
172
+ xs = [p[0] for p in pts]
173
+
174
+ # below range
175
+ if hours <= xs[0]:
176
+ x0, y0 = pts[0]
177
+ x1, y1 = pts[1]
178
+ y = _interp(x0, y0, x1, y1, hours)
179
+ return max(0.0, y), canon, (x0, y0), (x1, y1)
180
+
181
+ # above range
182
+ if hours >= xs[-1]:
183
+ x0, y0 = pts[-2]
184
+ x1, y1 = pts[-1]
185
+ y = _interp(x0, y0, x1, y1, hours)
186
+ return max(0.0, y), canon, (x0, y0), (x1, y1)
187
+
188
+ # interior: find bracketing points
189
+ for i in range(1, len(pts)):
190
+ x0, y0 = pts[i-1]
191
+ x1, y1 = pts[i]
192
+ if x0 <= hours <= x1:
193
+ y = _interp(x0, y0, x1, y1, hours)
194
+ return max(0.0, y), canon, (x0, y0), (x1, y1)
195
+
196
+ # should not reach here
197
+ x0, y0 = pts[0]
198
+ x1, y1 = pts[1]
199
+ return y0, canon, (x0, y0), (x1, y1)
200
+
201
+ # ----------------------------
202
+ # 4) Utilities
203
+ # ----------------------------
204
+ def list_machines() -> List[str]:
205
+ """Return the list of canonical machine names available in the table."""
206
+ return sorted(REPAIR_TABLE.keys())
207
+
208
+ def describe_machine_match(name: str) -> str:
209
+ """Debug helper: shows which canonical name a user string maps to."""
210
+ c = _normalize_name(name)
211
+ if not c:
212
+ return f"No match for '{name}'. Known: {', '.join(list_machines()[:8])}…"
213
+ return f"'{name}' → '{c}'"