""" 农业智能决策系统 基于多因子 Cobb-Douglas 产量模型的作物种植决策支持应用 """ import streamlit as st import numpy as np import pandas as pd import plotly.graph_objects as go import plotly.express as px from plotly.subplots import make_subplots # ─── Page Config ──────────────────────────────────────────────────────────── st.set_page_config( page_title="农业智能决策系统", page_icon="🌾", layout="wide", initial_sidebar_state="expanded", ) # ─── Custom CSS ────────────────────────────────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) # ─── Crop Database ─────────────────────────────────────────────────────────── CROPS = { "水稻": { "emoji": "🌾", "optimal": {"ph": (6.0, 7.0), "N": (80, 120), "P": (30, 60), "K": (40, 80), "rainfall": (150, 250), "temp": (22, 30)}, "base_yield": 7500, # kg/ha "color": "#4ade80" }, "小麦": { "emoji": "🌿", "optimal": {"ph": (6.0, 7.5), "N": (60, 100), "P": (20, 50), "K": (30, 60), "rainfall": (60, 120), "temp": (15, 22)}, "base_yield": 6000, "color": "#f59e0b" }, "玉米": { "emoji": "🌽", "optimal": {"ph": (5.8, 7.0), "N": (100, 150), "P": (40, 70), "K": (60, 100), "rainfall": (100, 180), "temp": (20, 28)}, "base_yield": 8500, "color": "#fbbf24" }, "大豆": { "emoji": "🫘", "optimal": {"ph": (6.0, 7.0), "N": (20, 50), "P": (30, 60), "K": (40, 80), "rainfall": (80, 150), "temp": (18, 26)}, "base_yield": 3500, "color": "#a78bfa" }, "油菜": { "emoji": "🌻", "optimal": {"ph": (6.0, 7.5), "N": (80, 130), "P": (30, 60), "K": (50, 90), "rainfall": (80, 130), "temp": (15, 20)}, "base_yield": 3000, "color": "#f97316" }, "棉花": { "emoji": "☁️", "optimal": {"ph": (6.0, 8.0), "N": (60, 100), "P": (20, 45), "K": (40, 70), "rainfall": (70, 120), "temp": (25, 32)}, "base_yield": 4500, "color": "#e2e8f0" }, } # ─── Yield Model ───────────────────────────────────────────────────────────── def compute_factor(value, optimal_low, optimal_high, penalty=0.5): """Score 0-1: 1 if in optimal range, decays outside.""" mid = (optimal_low + optimal_high) / 2 width = (optimal_high - optimal_low) / 2 + 1e-9 if optimal_low <= value <= optimal_high: return 1.0 dist = min(abs(value - optimal_low), abs(value - optimal_high)) return max(0.0, 1.0 - penalty * (dist / width)) def predict_yield(crop_name, ph, N, P, K, rainfall, temp, pesticide, area): crop = CROPS[crop_name] opt = crop["optimal"] f_ph = compute_factor(ph, *opt["ph"], penalty=0.6) f_N = compute_factor(N, *opt["N"], penalty=0.4) f_P = compute_factor(P, *opt["P"], penalty=0.4) f_K = compute_factor(K, *opt["K"], penalty=0.4) f_rain = compute_factor(rainfall, *opt["rainfall"], penalty=0.5) f_temp = compute_factor(temp, *opt["temp"], penalty=0.7) f_pest = 0.5 + 0.5 * min(pesticide / 100, 1.0) # Cobb-Douglas style yield function nutrient_idx = (f_N * f_P * f_K) ** (1/3) soil_idx = f_ph climate_idx = (f_rain * f_temp) ** 0.5 total_factor = soil_idx ** 0.2 * nutrient_idx ** 0.4 * climate_idx ** 0.3 * f_pest ** 0.1 yield_per_ha = crop["base_yield"] * total_factor total_yield = yield_per_ha * area factors = { "土壤pH": f_ph, "氮(N)": f_N, "磷(P)": f_P, "钾(K)": f_K, "降雨量": f_rain, "温度": f_temp, "农药": f_pest } return yield_per_ha, total_yield, factors def rank_crops(ph, N, P, K, rainfall, temp, pesticide, area): results = [] for name in CROPS: yph, ytotal, factors = predict_yield(name, ph, N, P, K, rainfall, temp, pesticide, area) score = np.mean(list(factors.values())) results.append({ "crop": name, "emoji": CROPS[name]["emoji"], "yield_ha": yph, "total_yield": ytotal, "score": score, "color": CROPS[name]["color"], "factors": factors }) results.sort(key=lambda x: x["score"], reverse=True) return results # ─── Sidebar Inputs ────────────────────────────────────────────────────────── with st.sidebar: st.markdown('
🌾 农业决策
', unsafe_allow_html=True) st.markdown('
SMART FARMING SYSTEM v2.0
', unsafe_allow_html=True) st.markdown("---") st.markdown('
🧪 土壤参数
', unsafe_allow_html=True) col1, col2 = st.columns(2) with col1: ph = st.slider("pH 值", 4.0, 9.0, 6.5, 0.1) N = st.slider("氮 N (mg/kg)", 0, 200, 90, 5) with col2: P = st.slider("磷 P (mg/kg)", 0, 100, 45, 5) K = st.slider("钾 K (mg/kg)", 0, 150, 60, 5) st.markdown('
🌦 气象数据
', unsafe_allow_html=True) col3, col4 = st.columns(2) with col3: rainfall = st.slider("降雨量 (mm/月)", 0, 400, 120, 10) with col4: temp = st.slider("温度 (°C)", 0, 45, 22, 1) st.markdown('
🌱 种植参数
', unsafe_allow_html=True) area = st.number_input("种植面积 (公顷)", 0.1, 10000.0, 100.0, 10.0) pesticide = st.slider("农药用量 (kg/ha)", 0, 200, 50, 5) st.markdown('
🎯 目标作物
', unsafe_allow_html=True) selected_crop = st.selectbox( "选择分析作物", list(CROPS.keys()), format_func=lambda x: f"{CROPS[x]['emoji']} {x}" ) # ─── Compute ────────────────────────────────────────────────────────────────── yph, ytotal, factors = predict_yield(selected_crop, ph, N, P, K, rainfall, temp, pesticide, area) rankings = rank_crops(ph, N, P, K, rainfall, temp, pesticide, area) best_crop = rankings[0] # ─── Main Layout ───────────────────────────────────────────────────────────── st.markdown(f"""
农业智能决策系统
YIELD = f(SOIL · WEATHER · PESTICIDE)  |  基于多因子 Cobb-Douglas 产量模型
""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # KPI row k1, k2, k3, k4 = st.columns(4) with k1: st.markdown(f"""
{yph:,.0f}
kg / 公顷
{CROPS[selected_crop]['emoji']} {selected_crop} 单产
""", unsafe_allow_html=True) with k2: st.markdown(f"""
{ytotal/1000:,.1f}
吨 / 总产量
📦 {area:.0f} 公顷总产
""", unsafe_allow_html=True) with k3: overall = np.mean(list(factors.values())) st.markdown(f"""
{overall*100:.1f}%
综合适宜度
🎯 环境匹配指数
""", unsafe_allow_html=True) with k4: st.markdown(f"""
{best_crop['emoji']}
{best_crop['crop']} ({best_crop['score']*100:.0f}%)
🏆 最优推荐作物
""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # ─── Charts Row ────────────────────────────────────────────────────────────── col_left, col_right = st.columns([3, 2]) with col_left: st.markdown('
📊 影响因子雷达图
', unsafe_allow_html=True) factor_names = list(factors.keys()) factor_vals = [round(v * 100, 1) for v in factors.values()] factor_names_closed = factor_names + [factor_names[0]] factor_vals_closed = factor_vals + [factor_vals[0]] fig_radar = go.Figure() fig_radar.add_trace(go.Scatterpolar( r=factor_vals_closed, theta=factor_names_closed, fill='toself', fillcolor='rgba(74,222,128,0.15)', line=dict(color='#4ade80', width=2), name=selected_crop, )) fig_radar.add_trace(go.Scatterpolar( r=[100]*len(factor_names_closed), theta=factor_names_closed, line=dict(color='rgba(255,255,255,0.1)', width=1, dash='dot'), mode='lines', name='理想值', )) fig_radar.update_layout( polar=dict( bgcolor='rgba(0,0,0,0)', radialaxis=dict(range=[0, 100], showticklabels=True, tickfont=dict(color='#64748b', size=9), gridcolor='rgba(255,255,255,0.06)'), angularaxis=dict(tickfont=dict(color='#e2e8f0', size=11), gridcolor='rgba(255,255,255,0.08)'), ), paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict(color='#e2e8f0'), legend=dict(orientation='h', y=-0.12, font=dict(size=10)), margin=dict(t=20, b=40, l=40, r=40), height=320, ) st.plotly_chart(fig_radar, use_container_width=True) with col_right: st.markdown('
🏅 作物推荐排行
', unsafe_allow_html=True) for i, r in enumerate(rankings[:4]): rank_icons = ["🥇", "🥈", "🥉", "4️⃣"] bar_width = int(r['score'] * 100) bar_color = r['color'] st.markdown(f"""
{rank_icons[i]} {r['emoji']} {r['crop']}
{r['score']*100:.1f}%
{r['yield_ha']:,.0f} kg/ha  ·  总产 {r['total_yield']/1000:,.1f} 吨
""", unsafe_allow_html=True) # ─── Sensitivity Analysis ───────────────────────────────────────────────────── st.markdown('
📈 产量敏感性分析
', unsafe_allow_html=True) sa_col1, sa_col2 = st.columns(2) with sa_col1: N_range = np.linspace(0, 200, 60) y_N = [predict_yield(selected_crop, ph, n, P, K, rainfall, temp, pesticide, 1)[0] for n in N_range] fig_N = go.Figure() fig_N.add_trace(go.Scatter( x=N_range, y=y_N, mode='lines', line=dict(color='#4ade80', width=2.5), fill='tozeroy', fillcolor='rgba(74,222,128,0.08)', name='产量' )) fig_N.add_vline(x=N, line=dict(color='#f59e0b', width=1.5, dash='dot'), annotation_text=f"当前 {N}", annotation_font_color='#f59e0b') fig_N.update_layout( title=dict(text="氮肥用量 vs 产量", font=dict(color='#94a3b8', size=12)), xaxis=dict(title="氮 N (mg/kg)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'), yaxis=dict(title="产量 (kg/ha)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'), paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict(color='#e2e8f0', size=10), margin=dict(t=36, b=36, l=50, r=20), height=220, showlegend=False, ) st.plotly_chart(fig_N, use_container_width=True) with sa_col2: rain_range = np.linspace(0, 400, 60) y_rain = [predict_yield(selected_crop, ph, N, P, K, r, temp, pesticide, 1)[0] for r in rain_range] fig_R = go.Figure() fig_R.add_trace(go.Scatter( x=rain_range, y=y_rain, mode='lines', line=dict(color='#38bdf8', width=2.5), fill='tozeroy', fillcolor='rgba(56,189,248,0.08)', name='产量' )) fig_R.add_vline(x=rainfall, line=dict(color='#f59e0b', width=1.5, dash='dot'), annotation_text=f"当前 {rainfall}mm", annotation_font_color='#f59e0b') fig_R.update_layout( title=dict(text="月降雨量 vs 产量", font=dict(color='#94a3b8', size=12)), xaxis=dict(title="降雨量 (mm/月)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'), yaxis=dict(title="产量 (kg/ha)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'), paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict(color='#e2e8f0', size=10), margin=dict(t=36, b=36, l=50, r=20), height=220, showlegend=False, ) st.plotly_chart(fig_R, use_container_width=True) # ─── All Crops Comparison Bar Chart ─────────────────────────────────────────── st.markdown('
🌐 全作物产量对比
', unsafe_allow_html=True) crop_names = [f"{r['emoji']} {r['crop']}" for r in rankings] crop_yields = [r['yield_ha'] for r in rankings] crop_colors = [r['color'] for r in rankings] fig_bar = go.Figure() fig_bar.add_trace(go.Bar( x=crop_names, y=crop_yields, marker=dict(color=crop_colors, opacity=0.85, line=dict(color='rgba(255,255,255,0.2)', width=1)), text=[f"{y:,.0f}" for y in crop_yields], textposition='outside', textfont=dict(color='#94a3b8', size=10, family='JetBrains Mono'), )) fig_bar.update_layout( xaxis=dict(color='#64748b', gridcolor='rgba(255,255,255,0.04)'), yaxis=dict(title="预期产量 (kg/ha)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'), paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', font=dict(color='#e2e8f0', size=11), margin=dict(t=20, b=30, l=60, r=20), height=240, showlegend=False, ) st.plotly_chart(fig_bar, use_container_width=True) # ─── Advisory Panel ─────────────────────────────────────────────────────────── st.markdown('
💡 智能建议
', unsafe_allow_html=True) adv1, adv2 = st.columns(2) with adv1: crop_opt = CROPS[selected_crop]["optimal"] advisories = [] if not (crop_opt["ph"][0] <= ph <= crop_opt["ph"][1]): advisories.append(("warn", f"pH {ph} 偏离 {selected_crop} 适宜范围 {crop_opt['ph']},建议{'施石灰' if ph < crop_opt['ph'][0] else '施硫磺'}调节")) else: advisories.append(("good", f"土壤 pH {ph} 处于 {selected_crop} 适宜范围内 ✓")) if N < crop_opt["N"][0]: advisories.append(("warn", f"氮肥不足({N} vs 建议 {crop_opt['N'][0]}-{crop_opt['N'][1]} mg/kg),建议追施尿素")) elif N > crop_opt["N"][1]: advisories.append(("warn", f"氮肥过量({N} mg/kg),可能造成徒长,建议减施")) else: advisories.append(("good", f"氮肥水平 {N} mg/kg 适宜 ✓")) if rainfall < crop_opt["rainfall"][0]: advisories.append(("warn", f"降雨量不足,建议增加灌溉(缺水 {crop_opt['rainfall'][0]-rainfall} mm)")) elif rainfall > crop_opt["rainfall"][1]: advisories.append(("warn", f"降雨量偏多,注意防涝排水")) else: advisories.append(("good", f"降雨量 {rainfall}mm 适合 {selected_crop} 生长 ✓")) for typ, msg in advisories: css_class = "alert-good" if typ == "good" else "alert-warn" st.markdown(f'
{msg}
', unsafe_allow_html=True) with adv2: st.markdown(f"""
当前环境参数下适宜种植:
""", unsafe_allow_html=True) badges = "".join([ f'{r["emoji"]} {r["crop"]} {r["score"]*100:.0f}%' for r in rankings if r['score'] > 0.6 ]) st.markdown(f'{badges}
', unsafe_allow_html=True) st.markdown(f"""
最优方案:{best_crop['emoji']} {best_crop['crop']}
预期单产:{best_crop['yield_ha']:,.0f} kg/ha
{area:.0f}公顷总产:{best_crop['total_yield']/1000:,.1f} 吨
""", unsafe_allow_html=True) # ─── Footer ─────────────────────────────────────────────────────────────────── st.markdown("
", unsafe_allow_html=True) st.markdown("""
YIELD = f(Soil, Weather, Pesticide)  |  Cobb-Douglas Multi-Factor Model  |  农业智能决策系统
""", unsafe_allow_html=True)