jeffrey1963 commited on
Commit
3d6e7e4
·
verified ·
1 Parent(s): 4ebcf5d

Upload 2 files

Browse files
Files changed (2) hide show
  1. app_jerry_logistic.py +336 -0
  2. requirements_logistic.txt +4 -0
app_jerry_logistic.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ import json, re, math
4
+ import numpy as np
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+
8
+ # --------------------
9
+ # State/HUD structure
10
+ # --------------------
11
+ DEFAULTS = {
12
+ "model": "logistic",
13
+ # Logistic yield: Y(X) = L / (1 + exp(-k*(X - x0)))
14
+ "L": None, # max yield
15
+ "k": None, # slope
16
+ "x0": None, # inflection X
17
+ # Prices & costs
18
+ "Px": None, # input price ($/lb of fert) -> MIC
19
+ "Py": None, # output price ($/unit yield)
20
+ "Other": 0.0, # other cost per acre (fixed wrt X)
21
+ # Range
22
+ "x_min": 0.0,
23
+ "x_max": 150.0,
24
+ "x_step": 5.0,
25
+ # Computed last table/plot
26
+ "table_ready": False,
27
+ "want_plot": False,
28
+ "last_table_cols": [],
29
+ "last_focus_x": None # for showing sample calc on a specific X
30
+ }
31
+
32
+ HELP = '''Say things like:
33
+ - "use logistic: L=1200, k=0.06, x0=60"
34
+ - "set Py=0.5 and Px=0.25 and other cost=300"
35
+ - "range 0 to 150 by 5"
36
+ - "make table"
37
+ - "graph yield" (or "plot mpp" / "plot profit")
38
+ - "compute APP" / "compute MPP" / "compute MVP" / "compute MIC"
39
+ - "show production stages"
40
+ - "calculate profit"
41
+ - "find optimal" (uses rule MVP = MIC in Stage II)
42
+ - "change Py to 0.6" or "change Px 0.3"
43
+ '''
44
+
45
+ # --------------------
46
+ # Core economics
47
+ # --------------------
48
+ def Y_logistic(X, L, k, x0):
49
+ return L / (1.0 + np.exp(-k * (X - x0)))
50
+
51
+ def MPP_logistic(X, L, k, x0):
52
+ # derivative of logistic: dY/dX = L*k*exp(-k*(X - x0)) / (1 + exp(-k*(X - x0)))^2
53
+ e = np.exp(-k * (X - x0))
54
+ return L * k * e / (1.0 + e)**2
55
+
56
+ def table_from_hud(hud):
57
+ L, k, x0 = hud["L"], hud["k"], hud["x0"]
58
+ x_min, x_max, x_step = hud["x_min"], hud["x_max"], hud["x_step"]
59
+ if None in (L, k, x0):
60
+ raise ValueError("Set logistic params first (L, k, x0).")
61
+ X = np.arange(float(x_min), float(x_max) + 1e-9, float(x_step))
62
+ Y = Y_logistic(X, L, k, x0)
63
+ APP = np.divide(Y, X, out=np.zeros_like(Y), where=X>0)
64
+ MPP = MPP_logistic(X, L, k, x0)
65
+ data = {
66
+ "X": X,
67
+ "Y": Y,
68
+ "APP": APP,
69
+ "MPP": MPP
70
+ }
71
+ # Add MVP, MIC, Profit if prices exist
72
+ if hud.get("Py") is not None:
73
+ data["MVP"] = MPP * float(hud["Py"])
74
+ if hud.get("Px") is not None:
75
+ data["MIC"] = np.full_like(X, float(hud["Px"]), dtype=float)
76
+ if hud.get("Py") is not None and hud.get("Px") is not None:
77
+ Other = float(hud.get("Other", 0.0))
78
+ data["Profit"] = Y * float(hud["Py"]) - X * float(hud["Px"]) - Other
79
+ df = pd.DataFrame(data)
80
+ # Stage classification
81
+ stage = []
82
+ for i, row in df.iterrows():
83
+ if row["MPP"] > 0:
84
+ if row["APP"] >= row["MPP"]:
85
+ s = "I"
86
+ else:
87
+ s = "II"
88
+ else:
89
+ s = "III"
90
+ stage.append(s)
91
+ df["Stage"] = stage
92
+ return df
93
+
94
+ def find_optimal(hud):
95
+ # Find X that maximizes profit by MVP = MIC (Stage II)
96
+ df = table_from_hud(hud)
97
+ if "MVP" not in df.columns or "MIC" not in df.columns:
98
+ raise ValueError("Need Py (for MVP) and Px (for MIC).")
99
+ # Find index minimizing |MVP - MIC| with MPP>0 (Stage II)
100
+ mask = (df["Stage"] == "II")
101
+ if not mask.any():
102
+ # fallback: allow all
103
+ mask = df["MPP"] > 0
104
+ sub = df[mask].copy()
105
+ sub["gap"] = (sub["MVP"] - sub["MIC"]).abs()
106
+ j = sub["gap"].idxmin()
107
+ row = df.loc[j]
108
+ return row # contains X, Y, Profit, etc.
109
+
110
+ # --------------------
111
+ # NLP parser
112
+ # --------------------
113
+ def parse(user, hud):
114
+ updated = []
115
+
116
+ txt = user.strip()
117
+
118
+ # logistic params
119
+ # "L=1200, k=0.06, x0=60" (order flexible)
120
+ mL = re.search(r'\bL\s*=\s*([0-9]*\.?[0-9]+)', txt, flags=re.I)
121
+ mk = re.search(r'\bk\s*=\s*([0-9]*\.?[0-9]+)', txt, flags=re.I)
122
+ mx0 = re.search(r'\bx0\s*=\s*([0-9]*\.?[0-9]+)', txt, flags=re.I)
123
+ if mL:
124
+ hud["L"] = float(mL.group(1)); updated.append("L")
125
+ if mk:
126
+ hud["k"] = float(mk.group(1)); updated.append("k")
127
+ if mx0:
128
+ hud["x0"] = float(mx0.group(1)); updated.append("x0")
129
+
130
+ if re.search(r'logistic', txt, flags=re.I):
131
+ hud["model"] = "logistic"; updated.append("model")
132
+
133
+ # prices
134
+ mPx = re.search(r'(?:Px|MIC|input price)\s*=?\s*([0-9]*\.?[0-9]+)', txt, flags=re.I)
135
+ mPy = re.search(r'(?:Py|output price)\s*=?\s*([0-9]*\.?[0-9]+)', txt, flags=re.I)
136
+ mOther = re.search(r'(?:Other|other cost)\s*=?\s*([0-9]*\.?[0-9]+)', txt, flags=re.I)
137
+ if mPx:
138
+ hud["Px"] = float(mPx.group(1)); updated.append("Px")
139
+ if mPy:
140
+ hud["Py"] = float(mPy.group(1)); updated.append("Py")
141
+ if mOther:
142
+ hud["Other"] = float(mOther.group(1)); updated.append("Other")
143
+
144
+ # quick "change Py to 0.6" etc.
145
+ mchg = re.search(r'change\s+(px|py|other)\s*(?:to|=)\s*([0-9]*\.?[0-9]+)', txt, flags=re.I)
146
+ if mchg:
147
+ key = mchg.group(1).title()
148
+ hud[key] = float(mchg.group(2)); updated.append(key)
149
+
150
+ # range
151
+ mrange = re.search(r'(?:range|fertilize|apply|from)\s*([0-9]*\.?[0-9]+)\s*(?:to|-)\s*([0-9]*\.?[0-9]+)\s*(?:by|in|step)\s*([0-9]*\.?[0-9]+)', txt, flags=re.I)
152
+ if mrange:
153
+ hud["x_min"] = float(mrange.group(1)); updated.append("x_min")
154
+ hud["x_max"] = float(mrange.group(2)); updated.append("x_max")
155
+ hud["x_step"] = float(mrange.group(3)); updated.append("x_step")
156
+
157
+ # focus X for blackboard example: "at X=80" or "at 80 lbs"
158
+ mfocus = re.search(r'at\s*(?:x\s*=?\s*)?([0-9]*\.?[0-9]+)', txt, flags=re.I)
159
+ if mfocus:
160
+ hud["last_focus_x"] = float(mfocus.group(1)); updated.append("last_focus_x")
161
+
162
+ # intents
163
+ intent = None
164
+ if re.search(r'\bmake table|\btable', txt, flags=re.I):
165
+ intent = "table"
166
+ if re.search(r'graph|plot', txt, flags=re.I):
167
+ # accept "plot yield", "plot mpp", "plot profit", default "yield"
168
+ intent = "plot"
169
+ if re.search(r'compute\s+app|\bAPP\b', txt, flags=re.I):
170
+ intent = "app"
171
+ if re.search(r'compute\s+mpp|\bMPP\b', txt, flags=re.I):
172
+ intent = "mpp"
173
+ if re.search(r'compute\s+mvp|\bMVP\b', txt, flags=re.I):
174
+ intent = "mvp"
175
+ if re.search(r'compute\s+mic|\bMIC\b', txt, flags=re.I):
176
+ intent = "mic"
177
+ if re.search(r'profit', txt, flags=re.I):
178
+ intent = "profit"
179
+ if re.search(r'optimal|find optimal|mvp\s*=\s*mic', txt, flags=re.I):
180
+ intent = "optimal"
181
+ if re.search(r'stage', txt, flags=re.I):
182
+ intent = "stage"
183
+ if re.search(r'help', txt, flags=re.I):
184
+ intent = "help"
185
+
186
+ return hud, updated, intent, txt
187
+
188
+ # --------------------
189
+ # Blackboard examples
190
+ # --------------------
191
+ def sample_work(hud, df):
192
+ # Return a short textual sample calculation at a representative X.
193
+ if hud.get("last_focus_x") is not None:
194
+ x0 = hud["last_focus_x"]
195
+ j = int(round((x0 - hud["x_min"]) / hud["x_step"]))
196
+ j = max(0, min(j, len(df)-1))
197
+ else:
198
+ j = len(df)//2
199
+ row = df.iloc[j]
200
+ lines = []
201
+ lines.append(f"At X = {row['X']:.3g}:")
202
+ lines.append(f" Y = L/(1+exp(-k*(X-x0))) with L={hud['L']}, k={hud['k']}, x0={hud['x0']}")
203
+ lines.append(f" => Y ≈ {row['Y']:.4g}")
204
+ lines.append(f" APP = Y/X ≈ {row['APP']:.4g}")
205
+ lines.append(f" MPP = dY/dX ≈ {row['MPP']:.4g}")
206
+ if 'MVP' in df.columns:
207
+ lines.append(f" MVP = MPP·Py ≈ {row['MVP']:.4g}")
208
+ if 'MIC' in df.columns:
209
+ lines.append(f" MIC = Px ≈ {row['MIC']:.4g}")
210
+ if 'Profit' in df.columns:
211
+ lines.append(f" Profit = Py·Y − Px·X − Other ≈ {row['Profit']:.4g}")
212
+ lines.append(f" Stage = {row['Stage']}")
213
+ return "\n".join(lines)
214
+
215
+ # --------------------
216
+ # Controller
217
+ # --------------------
218
+ def controller(user_text, hud_json):
219
+ # load/ensure hud
220
+ try:
221
+ hud = json.loads(hud_json) if hud_json else {}
222
+ except Exception:
223
+ hud = {}
224
+ for k, v in DEFAULTS.items():
225
+ hud.setdefault(k, v)
226
+
227
+ hud, updated, intent, raw = parse(user_text, hud)
228
+
229
+ # Decide action
230
+ reply = ""
231
+ table = pd.DataFrame()
232
+ plot = None
233
+ blackboard = ""
234
+
235
+ # Some actions require table
236
+ need_table = intent in ("table","plot","app","mpp","mvp","mic","profit","stage","optimal")
237
+ if need_table:
238
+ try:
239
+ df = table_from_hud(hud)
240
+ except Exception as e:
241
+ reply = f"Jerry → {e} \n\n{HELP}"
242
+ return reply, json.dumps(hud, indent=2), ", ".join(updated) or "(none)", blackboard, table, plot
243
+
244
+ if intent == "help" or intent is None:
245
+ reply = "Jerry → How can I help?\n\n" + HELP
246
+
247
+ elif intent == "table":
248
+ hud["table_ready"] = True
249
+ hud["last_table_cols"] = list(df.columns)
250
+ reply = f"Built table for X from {hud['x_min']} to {hud['x_max']} by {hud['x_step']}."
251
+ table = df
252
+ blackboard = sample_work(hud, df)
253
+
254
+ elif intent == "plot":
255
+ # choose what to plot
256
+ metric = "Y"
257
+ if re.search(r'mpp', raw, flags=re.I): metric = "MPP"
258
+ elif re.search(r'app', raw, flags=re.I): metric = "APP"
259
+ elif re.search(r'mvp', raw, flags=re.I) and "MVP" in df.columns: metric = "MVP"
260
+ elif re.search(r'profit', raw, flags=re.I) and "Profit" in df.columns: metric = "Profit"
261
+ fig = plt.figure()
262
+ ax = fig.add_subplot(111)
263
+ ax.plot(df["X"], df[metric])
264
+ ax.set_xlabel("X (fertilizer)")
265
+ ax.set_ylabel(metric)
266
+ ax.set_title(f"{metric} vs X")
267
+ plot = fig
268
+ table = df
269
+ reply = f"Plotted {metric} vs X."
270
+ blackboard = sample_work(hud, df)
271
+
272
+ elif intent in ("app","mpp","mvp","mic","profit","stage"):
273
+ table = df
274
+ parts = []
275
+ if intent in ("app","stage"): parts.append("APP")
276
+ if intent in ("mpp","stage"): parts.append("MPP")
277
+ if intent in ("mvp","stage") and "MVP" in df.columns: parts.append("MVP")
278
+ if intent in ("mic","stage") and "MIC" in df.columns: parts.append("MIC")
279
+ if intent in ("profit","stage") and "Profit" in df.columns: parts.append("Profit")
280
+ if intent == "stage": parts.append("Stage")
281
+ show = [c for c in parts if c in df.columns] + ["X"]
282
+ show = list(dict.fromkeys(["X"] + show)) # ensure X first, unique
283
+ reply = "Computed metrics. Showing relevant columns."
284
+ table = df[show]
285
+ blackboard = sample_work(hud, df)
286
+ if intent == "mvp" and "MVP" not in df.columns:
287
+ reply += " (Set Py first)"
288
+ if intent == "mic" and "MIC" not in df.columns:
289
+ reply += " (Set Px first)"
290
+ if intent == "profit" and "Profit" not in df.columns:
291
+ reply += " (Set Px and Py first)"
292
+
293
+ elif intent == "optimal":
294
+ try:
295
+ row = find_optimal(hud)
296
+ reply = (f"Optimal (MVP ≈ MIC in Stage II):\n"
297
+ f"X* ≈ {row['X']:.4g}, Y* ≈ {row['Y']:.4g}, "
298
+ f"MVP ≈ {row.get('MVP', float('nan')):.4g}, MIC ≈ {row.get('MIC', float('nan')):.4g}")
299
+ if 'Profit' in row:
300
+ reply += f", Profit* ≈ {row['Profit']:.4g}"
301
+ df = table_from_hud(hud)
302
+ cols = ['X','Y','MPP']
303
+ if 'MVP' in df.columns: cols.append('MVP')
304
+ if 'MIC' in df.columns: cols.append('MIC')
305
+ if 'Profit' in df.columns: cols.append('Profit')
306
+ table = df[cols]
307
+ blackboard = sample_work(hud, df) + "\n\nRule: choose X where MVP = MIC (in Stage II)."
308
+ except Exception as e:
309
+ reply = f"Jerry → {e} \n(Set Px and Py, and logistic params)."
310
+
311
+ else:
312
+ reply = "Okay — updated HUD. Ask me to 'make table', 'plot yield', 'compute MPP', 'profit', 'find optimal', etc."
313
+
314
+ return reply, json.dumps(hud, indent=2), ", ".join(updated) or "(none)", blackboard, table, plot
315
+
316
+ with gr.Blocks() as demo:
317
+ gr.Markdown("### Jerry — MIC/Profit Coach (Logistic Yield, NLP HUD)")
318
+ with gr.Row():
319
+ with gr.Column(scale=1):
320
+ user = gr.Textbox(label="Talk to Jerry", value="use logistic L=1200, k=0.06, x0=60; set Py=0.5, Px=0.25, other cost=300; range 0 to 150 by 5; make table", lines=4)
321
+ btn = gr.Button("Ask Jerry", variant="primary")
322
+ with gr.Column(scale=1):
323
+ out = gr.Textbox(label="Jerry says", lines=10)
324
+ with gr.Row():
325
+ with gr.Column(scale=1):
326
+ hud = gr.Textbox(label="Student HUD — what Jerry sees (persists)", value=json.dumps(DEFAULTS, indent=2), lines=18)
327
+ with gr.Column(scale=1):
328
+ changed = gr.Textbox(label="Fields updated by last prompt", value="(none)")
329
+ black = gr.Textbox(label="Blackboard — show the work", value="(none)", lines=12)
330
+ table = gr.Dataframe(label="Main Output Table", wrap=True)
331
+ plot = gr.Plot(label="Figure (on demand)")
332
+
333
+ btn.click(controller, inputs=[user, hud], outputs=[out, hud, changed, black, table, plot])
334
+
335
+ if __name__ == "__main__":
336
+ demo.launch()
requirements_logistic.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=4.44.0
2
+ pandas
3
+ numpy
4
+ matplotlib