From 9cb70267b6aa447e5928c1b70a818ed13df193db Mon Sep 17 00:00:00 2001
From: zhenghu <1831829219@qq.com>
Date: Mon, 13 Apr 2026 14:20:39 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=20YieldSmar?=
=?UTF-8?q?t=20=E5=86=9C=E4=B8=9A=E6=99=BA=E8=83=BD=E5=86=B3=E7=AD=96?=
=?UTF-8?q?=E7=B3=BB=E7=BB=9F=20=E5=9F=BA=E4=BA=8E=E5=A4=9A=E5=9B=A0?=
=?UTF-8?q?=E5=AD=90=20Cobb-Douglas=20=E4=BA=A7=E9=87=8F=E6=A8=A1=E5=9E=8B?=
=?UTF-8?q?=E7=9A=84=E4=BD=9C=E7=89=A9=E7=A7=8D=E6=A4=8D=E5=86=B3=E7=AD=96?=
=?UTF-8?q?=E6=94=AF=E6=8C=81=E5=BA=94=E7=94=A8=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增文件:
- app.py: Streamlit 主应用,包含产量预测模型、多作物数据库、
雷达图/敏感性分析可视化、作物推荐排行及智能建议面板
- main.py: 入口文件
- pyproject.toml: 项目配置(Python 3.14+,依赖 streamlit/plotly/pandas/numpy)
- Dockerfile: 基于 uv 镜像的容器化部署配置
- justfile: 任务自动化(运行/格式化/检查/清理)
- .gitignore: Python/IDE/缓存忽略规则
---
.gitignore | 29 +++
Dockerfile | 43 ++++
README.md | 90 +++++++-
app.py | 587 +++++++++++++++++++++++++++++++++++++++++++++++++
justfile | 30 +++
main.py | 6 +
pyproject.toml | 17 ++
7 files changed, 800 insertions(+), 2 deletions(-)
create mode 100644 .gitignore
create mode 100644 Dockerfile
create mode 100644 app.py
create mode 100644 justfile
create mode 100644 main.py
create mode 100644 pyproject.toml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7f69351
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+# Python
+__pycache__/
+*.py[cod]
+*.egg-info/
+dist/
+build/
+
+# Virtual environment
+.venv/
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+
+# Streamlit
+.streamlit_cache/
+
+# Ruff
+.ruff_cache/
+
+# UV
+*.lock
+
+# OS
+.DS_Store
+Thumbs.db
+/.doc/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..5fce0e5
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,43 @@
+# 使用 uv 官方镜像作为基础镜像(已包含 Python 3.14 和 uv)
+FROM 172.16.102.3:30648/astral-sh/uv:python3.14-bookworm
+
+# 设置工作目录
+WORKDIR /app
+
+# 配置 apt 使用阿里云镜像源
+RUN sed -i 's/httpredir.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources
+
+# 安装系统依赖
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ curl \
+ && rm -rf /var/lib/apt/lists/*
+
+# 复制项目配置文件和锁定文件
+COPY pyproject.toml justfile uv.lock ./
+
+# 配置 uv 使用阿里云镜像源(通过环境变量)
+ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
+
+# 安装 Python 依赖(使用 uv,锁定版本)
+RUN uv sync --frozen --no-dev
+
+# 复制应用代码和其他文件
+COPY . .
+
+# 暴露 Streamlit 默认端口
+EXPOSE 8000
+
+# 设置环境变量
+ENV STREAMLIT_SERVER_PORT=8000 \
+ STREAMLIT_SERVER_ADDRESS=0.0.0.0 \
+ STREAMLIT_SERVER_ENABLE_XSRF_PROTECTION=false \
+ STREAMLIT_SERVER_ENABLE_CORS=false \
+ STREAMLIT_SERVER_HEADLESS=true \
+ STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
+
+# 健康检查
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD curl -f http://localhost:8000/_stcore/health || exit 1
+
+# 运行 Streamlit 应用
+CMD ["uv", "run", "streamlit", "run", "app.py", "--server.port=8000", "--server.address=0.0.0.0"]
diff --git a/README.md b/README.md
index 8fe876d..1ba4394 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,89 @@
-# yield-smart-app
+# YieldSmart 农业智能决策系统
-农业智能决策系统
\ No newline at end of file
+基于多因子 Cobb-Douglas 产量模型的作物种植决策支持应用。
+
+## 功能特性
+
+- 🌾 多作物产量预测(水稻、小麦、玉米、大豆、油菜、棉花)
+- 📊 影响因子雷达图可视化
+- 🏅 作物推荐智能排行
+- 📈 产量敏感性分析(氮肥/降雨量)
+- 💡 智能种植建议与环境匹配评估
+
+## 技术栈
+
+- Python 3.14+
+- Streamlit 1.52.1
+- Plotly 6.5.0
+- Pandas 2.3.3
+- NumPy 2.3.5
+
+## 快速开始
+
+### 使用 uv(推荐)
+
+```bash
+# 安装依赖
+uv sync
+
+# 运行应用
+uv run streamlit run app.py
+```
+
+### 使用传统方式
+
+```bash
+# 创建虚拟环境
+python -m venv .venv
+source .venv/bin/activate
+
+# 安装依赖
+pip install -r requirements.txt
+
+# 运行应用
+streamlit run app.py
+```
+
+## 项目结构
+
+```
+YieldSmart/
+├── app.py # 主应用文件(Streamlit)
+├── main.py # 入口文件
+├── pyproject.toml # 项目配置
+├── justfile # 任务自动化
+├── Dockerfile # Docker 配置
+└── README.md # 项目文档
+```
+
+## 使用 just
+
+项目使用 justfile 进行任务管理:
+
+```bash
+# 查看所有可用命令
+just --list
+
+# 运行应用
+just run
+
+# 代码格式化
+just format
+
+# 代码检查
+just check
+```
+
+## Docker 部署
+
+```bash
+# 构建镜像
+docker build -t yieldsmart .
+
+# 运行容器
+docker run -p 8000:8000 yieldsmart
+```
+
+## 许可证
+
+MIT License
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..973643a
--- /dev/null
+++ b/app.py
@@ -0,0 +1,587 @@
+"""
+农业智能决策系统
+基于多因子 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)
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..410315f
--- /dev/null
+++ b/justfile
@@ -0,0 +1,30 @@
+# Justfile for YieldSmart 农业智能决策系统
+# Use `just ` to run tasks
+
+# Default task: show available commands
+default:
+ just --list
+
+# Run the Streamlit app
+run:
+ uv run streamlit run app.py
+
+# Install dependencies
+install:
+ uv add streamlit ruff plotly pandas numpy
+
+# Format code with ruff
+format:
+ uv run ruff format .
+
+# Check code with ruff
+check:
+ uv run ruff check .
+
+# Run all checks and formatting
+lint:
+ just format && just check
+
+# Clean up cache files
+clean:
+ rm -rf __pycache__ .ruff_cache .streamlit_cache
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..4e73d28
--- /dev/null
+++ b/main.py
@@ -0,0 +1,6 @@
+def main():
+ print("Hello from YieldSmart!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..3c0452a
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,17 @@
+[project]
+name = "yieldsmart"
+version = "0.1.0"
+description = "农业智能决策系统 - 基于多因子 Cobb-Douglas 产量模型的作物种植决策支持应用"
+readme = "README.md"
+requires-python = ">=3.14"
+dependencies = [
+ "numpy>=2.3.5",
+ "pandas>=2.3.3",
+ "plotly>=6.5.0",
+ "ruff>=0.14.8",
+ "streamlit>=1.52.1",
+]
+
+[[tool.uv.index]]
+url = "https://mirrors.aliyun.com/pypi/simple"
+default = true