第一个文件:# save_model.py
import joblib, os
from sklearn.ensemble import RandomForestClassifier
import numpy as np
# 生成模拟数据并训练
X = np.random.rand(300, 3) # plan_ratio, actual_ratio, remain_hours
y = np.random.choice([0, 1, 2], 300) # 0滞后 1正常 2超前
clf = RandomForestClassifier(n_estimators=200, random_state=42).fit(X, y)
# 创建 models 目录并保存
os.makedirs("models", exist_ok=True)
joblib.dump(clf, "models/progress_model.pkl")
print("✅ progress_model.pkl 已生成")
能正常运行
第二个文件:
# 导入必要的库和模块
from fastapi import FastAPI
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.exceptions import HTTPException, RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field, field_validator, ValidationInfo
import joblib
import numpy as np
from datetime import datetime, timedelta
from typing import Dict, List, Optional
# ==================== FastAPI 实例配置 ====================
# 创建FastAPI应用实例,并配置元数据信息
app = FastAPI(
title="工程进度智能分析系统", # API标题
description="基于建材用量分析的工程进度状态评估服务", # API描述
version="2.0.0", # API版本
contact={ # 联系方式
"name": "技术支持",
"email": "support@engineering.ai"
},
license_info={ # 许可证信息
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
docs_url="/docs", # Swagger文档路径
redoc_url=None, # 禁用Redoc文档
)
# ==================== 数据模型定义 ====================
class MaterialInput(BaseModel):
"""单种建材输入参数模型"""
total_planned: float = Field(..., gt=0, example=1000, description="计划总量(必须大于0)")
total_used: float = Field(..., ge=0, example=300, description="已用量(不能小于0)")
start_date: str = Field(..., example="2023-01-01", description="计划开始日期(YYYY-MM-DD)")
end_date: str = Field(..., example="2023-12-31", description="计划结束日期(YYYY-MM-DD)")
current_date: str = Field(default_factory=lambda: datetime.now().strftime('%Y-%m-%d'),
description="分析基准日期(默认当天)")
@field_validator('end_date')
@classmethod
def validate_dates(cls, v: str, info: ValidationInfo) -> str:
"""验证日期有效性:结束日期必须晚于开始日期"""
try:
start_date_str = info.data.get('start_date')
if not start_date_str:
raise ValueError("需要提供start_date")
start = datetime.strptime(start_date_str, '%Y-%m-%d')
end = datetime.strptime(v, '%Y-%m-%d')
if end <= start:
raise ValueError("结束日期必须晚于开始日期")
return v
except ValueError as e:
raise ValueError(f"日期验证失败: {str(e)}")
@field_validator('total_used')
@classmethod
def validate_usage(cls, v: float, info: ValidationInfo) -> float:
"""验证用量有效性:已用量不能超过计划总量"""
if 'total_planned' in info.data and v > info.data['total_planned']:
raise ValueError("已用量不能超过计划总量")
return v
class ProgressRequest(BaseModel):
"""进度分析请求参数模型"""
materials: Dict[str, MaterialInput] = Field(
...,
example={
"水泥": {
"total_planned": 1000,
"total_used": 300,
"start_date": "2023-01-01",
"end_date": "2023-12-31"
}
},
description="建材名称到参数的映射"
)
method: str = Field(
default="weighted",
enum=["strict", "weighted", "average"],
description="""进度计算方法:
strict-所有建材达标才算正常 |
weighted-按重要度加权计算 |
average-简单平均"""
)
weights: Optional[Dict[str, float]] = Field(
None,
description="当method=weighted时需要的权重配置(自动归一化)"
)
class MaterialProgress(BaseModel):
"""单种建材进度分析结果模型"""
progress_rate: float = Field(..., example=0.3, description="完成进度比例(0~1)")
planned_rate: float = Field(..., example=0.5, description="计划进度比例(0~1)")
status: str = Field(..., example="滞后", description="进度状态:滞后|正常|超前")
days_remaining: int = Field(..., example=100, description="预计剩余天数")
completion_date: str = Field(..., example="2023-12-15", description="预计完成日期")
class ProgressResponse(BaseModel):
"""进度分析响应结果模型"""
overall_status: str = Field(..., example="滞后", description="整体进度状态")
materials: Dict[str, MaterialProgress] = Field(
...,
description="每种建材的详细分析结果"
)
metrics: dict = Field(
...,
example={
"avg_progress": 0.45,
"min_progress": 0.3,
"max_progress": 0.6
},
description="综合统计指标"
)
recommendations: List[str] = Field(
...,
example=["建议增加钢筋供应量", "混凝土浇筑进度滞后需加快"],
description="改进建议列表"
)
# ==================== 进度分析核心逻辑 ====================
def calculate_material_progress(material: MaterialInput) -> MaterialProgress:
"""
计算单种建材进度
参数:
material: 建材输入参数
返回:
MaterialProgress: 包含进度计算结果的对象
"""
# 日期解析
start_date = datetime.strptime(material.start_date, '%Y-%m-%d')
end_date = datetime.strptime(material.end_date, '%Y-%m-%d')
current_date = datetime.strptime(material.current_date, '%Y-%m-%d')
# 计算计划进度
total_days = (end_date - start_date).days # 总计划天数
elapsed_days = (current_date - start_date).days # 已过去天数
# 计算计划进度比例
if elapsed_days <= 0:
planned_progress = 0.0 # 未开始
elif elapsed_days >= total_days:
planned_progress = 1.0 # 已超期
else:
planned_progress = elapsed_days / total_days # 计划进度比例
# 计算实际进度比例
actual_progress = material.total_used / material.total_planned
# 预计完成时间计算
if actual_progress <= 0:
remaining_days = float('inf') # 无穷大表示无法预测
completion_date = "无法预测(零消耗)"
else:
# 计算使用速率和剩余天数
usage_rate = material.total_used / max(1, elapsed_days)
remaining_amount = material.total_planned - material.total_used
remaining_days = remaining_amount / usage_rate if usage_rate > 0 else float('inf')
completion_date = (current_date + timedelta(days=remaining_days)).strftime('%Y-%m-%d')
# 判断进度状态(允许10%的缓冲区间)
if actual_progress < planned_progress * 0.9:
status = "滞后"
elif actual_progress > planned_progress * 1.1:
status = "超前"
else:
status = "正常"
# 返回计算结果对象
return MaterialProgress(
progress_rate=round(actual_progress, 4),
planned_rate=round(planned_progress, 4),
status=status,
days_remaining=int(round(remaining_days)),
completion_date=completion_date
)
def calculate_overall_progress(
materials: Dict[str, MaterialProgress],
method: str = "weighted",
weights: Optional[Dict[str, float]] = None
) -> dict:
"""
计算整体进度状态
参数:
materials: 各建材的进度计算结果字典
method: 计算方法 (strict|weighted|average)
weights: 权重配置(仅weighted方法需要)
返回:
dict: 包含整体状态和指标的字典
"""
# 提取所有进度值和计划值
progress_values = [m.progress_rate for m in materials.values()]
planned_values = [m.planned_rate for m in materials.values()]
# 计算综合指标
metrics = {
"avg_progress": round(sum(progress_values) / len(progress_values), 4), # 平均进度
"min_progress": round(min(progress_values), 4), # 最小进度
"max_progress": round(max(progress_values), 4), # 最大进度
"avg_planned": round(sum(planned_values) / len(planned_values), 4) # 平均计划进度
}
# 根据不同的计算方法确定整体状态
if method == "strict":
# 严格模式:所有材料达标才算正常
status = "正常"
for m in materials.values():
if m.status == "滞后":
status = "滞后"
break
elif m.status == "超前":
status = "超前"
elif method == "weighted":
# 加权计算模式
if not weights:
weights = {name: 1.0 for name in materials} # 默认权重为1
# 权重归一化处理
total_weight = sum(weights.get(name, 0) for name in materials)
if total_weight <= 0:
total_weight = 1.0
# 计算加权分数
weighted_sum = 0
for name, progress in materials.items():
weight = weights.get(name, 0) / total_weight
# 滞后=-1, 正常=0, 超前=1
score = -1 if progress.status == "滞后" else (1 if progress.status == "超前" else 0)
weighted_sum += score * weight
# 根据加权分数确定状态
if weighted_sum < -0.3:
status = "滞后"
elif weighted_sum > 0.3:
status = "超前"
else:
status = "正常"
else: # average
# 简单平均模式
if metrics['avg_progress'] < metrics['avg_planned'] * 0.95:
status = "滞后"
elif metrics['avg_progress'] > metrics['avg_planned'] * 1.05:
status = "超前"
else:
status = "正常"
return {
"overall_status": status,
"metrics": metrics
}
def generate_recommendations(response_data: dict) -> List[str]:
"""生成改进建议"""
recommendations = []
if response_data["overall_status"] == "滞后":
recommendations.append("建议召开进度协调会,分析滞后原因")
for material, data in response_data["materials"].items():
if data["status"] == "滞后":
rec = f"{material}:当前进度{data['progress_rate'] * 100:.1f}%,"
rec += f"计划应达{data['planned_rate'] * 100:.1f}%。"
rec += f"预计完成日期比计划晚{data['days_remaining']}天"
recommendations.append(rec)
return recommendations
# ==================== API接口 ====================
@app.post(
"/analyze/progress",
response_model=ProgressResponse,
summary="建材进度综合分析",
description="基于多类建材的消耗情况评估整体工程进度",
tags=["分析服务"]
)
async def analyze_progress(request: ProgressRequest):
"""
工程进度分析主接口
参数:
request: 包含建材数据和计算方法的请求对象
返回:
ProgressResponse: 包含详细分析结果的响应对象
"""
try:
# 计算每种建材的进度
material_results = {
name: calculate_material_progress(data)
for name, data in request.materials.items()
}
# 计算整体进度
overall = calculate_overall_progress(
material_results,
request.method,
request.weights
)
# 生成建议
recommendations = generate_recommendations({
"overall_status": overall["overall_status"],
"materials": material_results
})
# 返回响应对象
return ProgressResponse(
overall_status=overall["overall_status"],
materials=material_results,
metrics=overall["metrics"],
recommendations=recommendations
)
except Exception as e:
# 异常处理
raise HTTPException(status_code=400, detail=str(e))
# ==================== 健康检查 ====================
@app.get("/", summary="服务健康检查", tags=["系统管理"])
async def health_check():
"""服务健康检查端点"""
return {
"status": "running",
"version": app.version,
"service": "material-progress-analysis"
}
# ==================== 错误处理 ====================
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
"""处理请求参数验证错误"""
errors = []
for error in exc.errors():
field = "->".join(str(loc) for loc in error["loc"]) # 拼接错误字段路径
errors.append(f"{field} {error['msg']}") # 格式化错误信息
# 返回标准化的错误响应
return JSONResponse(
status_code=422,
content={
"detail": "参数验证失败",
"errors": errors,
"suggestion": "请参考API文档检查参数格式"
},
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080) # 修改端口号为8080
运行之后报错:D:\anaconda\envs\jh\python.exe C:\Users\31477\main.py
Traceback (most recent call last):
File "C:\Users\31477\main.py", line 6, in <module>
from pydantic import BaseModel, Field, field_validator, ValidationInfo
ImportError: cannot import name 'field_validator' from 'pydantic' (D:\anaconda\envs\jh\lib\site-packages\pydantic\__init__.cp39-win_amd64.pyd)
Process finished with exit code 1
第三个文件:import subprocess
import webbrowser
import time
import os
import sys
# 获取当前脚本的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
# 1. 启动 FastAPI 服务(后台)
cmd = [sys.executable, "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
proc = subprocess.Popen(cmd, cwd=current_dir)
# 2. 等 2 秒后打开浏览器
time.sleep(2)
webbrowser.open("http://127.0.0.1:8000/docs")
# 3. 保持脚本运行(按 Ctrl+C 结束)
try:
proc.wait()
except KeyboardInterrupt:
proc.terminate()
运行之后报错 :D:\anaconda\envs\jh\python.exe C:\Users\31477\lunch2.py
Traceback (most recent call last):
File "D:\anaconda\envs\jh\lib\runpy.py", line 197, in _run_module_as_main
return _run_code(code, main_globals, None,
File "D:\anaconda\envs\jh\lib\runpy.py", line 87, in _run_code
exec(code, run_globals)
File "D:\anaconda\envs\jh\lib\site-packages\uvicorn\__main__.py", line 4, in <module>
uvicorn.main()
File "D:\anaconda\envs\jh\lib\site-packages\click\core.py", line 1161, in __call__
return self.main(*args, **kwargs)
File "D:\anaconda\envs\jh\lib\site-packages\click\core.py", line 1082, in main
rv = self.invoke(ctx)
File "D:\anaconda\envs\jh\lib\site-packages\click\core.py", line 1443, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "D:\anaconda\envs\jh\lib\site-packages\click\core.py", line 788, in invoke
return __callback(*args, **kwargs)
File "D:\anaconda\envs\jh\lib\site-packages\uvicorn\main.py", line 413, in main
run(
File "D:\anaconda\envs\jh\lib\site-packages\uvicorn\main.py", line 580, in run
server.run()
File "D:\anaconda\envs\jh\lib\site-packages\uvicorn\server.py", line 67, in run
return asyncio.run(self.serve(sockets=sockets))
File "D:\anaconda\envs\jh\lib\asyncio\runners.py", line 44, in run
return loop.run_until_complete(main)
File "D:\anaconda\envs\jh\lib\asyncio\base_events.py", line 647, in run_until_complete
return future.result()
File "D:\anaconda\envs\jh\lib\site-packages\uvicorn\server.py", line 71, in serve
await self._serve(sockets)
File "D:\anaconda\envs\jh\lib\site-packages\uvicorn\server.py", line 78, in _serve
config.load()
File "D:\anaconda\envs\jh\lib\site-packages\uvicorn\config.py", line 436, in load
self.loaded_app = import_from_string(self.app)
File "D:\anaconda\envs\jh\lib\site-packages\uvicorn\importer.py", line 19, in import_from_string
module = importlib.import_module(module_str)
File "D:\anaconda\envs\jh\lib\importlib\__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 850, in exec_module
File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
File "C:\Users\31477\main.py", line 6, in <module>
from pydantic import BaseModel, Field, field_validator, ValidationInfo
ImportError: cannot import name 'field_validator' from 'pydantic' (D:\anaconda\envs\jh\lib\site-packages\pydantic\__init__.cp39-win_amd64.pyd)
Process finished with exit code 0
并且弹出网页但无法访问。
怎么办