Browse Source

refactor(api): add logging and error handling to all endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sherlock 3 weeks ago
parent
commit
75642c90ce
3 changed files with 80 additions and 92 deletions
  1. 18 9
      api/eval_report.py
  2. 53 45
      api/recommend.py
  3. 9 38
      api/report.py

+ 18 - 9
api/eval_report.py

@@ -1,24 +1,27 @@
-from config import load_service_config
 from database import MySqlDao
 from fastapi import APIRouter, status, HTTPException
 import os
 from .request_body import EvalReportRequest
 import requests
 from utils import ReportUtils, FileStreamUtils
+from core import get_logger
 
-cfgs = load_service_config()
+logger = get_logger("api.eval_report")
 dao = MySqlDao()
 router = APIRouter()
 
 @router.post('/eval_report')
 async def eval_report(request: EvalReportRequest):
     """生成并上传验证报告到阿里云文件数据库"""
+    logger.info(f"Eval report request: cultivacation_id={request.cultivacation_id}, city={request.city_uuid}, product={request.product_code}")
+
     reports_dir = os.path.join('./data/reports', request.city_uuid, request.product_code)
     report_util = ReportUtils(request.city_uuid, request.product_code)
-    
+
     # 获取report数据表中eval_table的file_id,如果不为空,直接返回结果,如果为空则先创建验证数据
     eval_file_id = dao.get_report_file_id(request.cultivacation_id)['val_table'].item()
     if eval_file_id:
+        logger.info(f"Existing eval report found: file_id={eval_file_id}")
         content = [
             {
                 "id": 1,
@@ -27,25 +30,31 @@ async def eval_report(request: EvalReportRequest):
             }
         ]
         return {"code": 200, "msg": "success", "data": {"evalReportInfo": content}}
-    
+
     # 获取推荐列表
     file_id = dao.get_report_file_id(request.cultivacation_id)['recommend_table'].item()
     if file_id is None:
+        logger.error(f"Recommend table missing for cultivacation_id={request.cultivacation_id}")
         return {"code": 405, "msg": "推荐表丢失,生成验证报告失败!", "data": {"reportInfo": "推荐表丢失,生成验证报告失败!"}}
-    
+
+    logger.info(f"Downloading recommend data: file_id={file_id}")
     recommend_data = FileStreamUtils.download_file(file_id)
     if recommend_data is None:
+        logger.error(f"Failed to download recommend data: file_id={file_id}")
         return {"code": 405, "msg": "下载推荐数据出错,生成验证报告失败!", "data": {"reportInfo": "下载推荐数据出错,生成验证报告失败!"}}
-    
+
     # 生成验证报告
+    logger.info(f"Generating eval report for period {request.start_time} to {request.end_time}")
     report_util.generate_eval_data(request.start_time, request.end_time, recommend_data)
-    
+
     # 上传报告
+    logger.info(f"Uploading eval report to {reports_dir}")
     eval_report = ['投放验证报告']
     file_id_map = FileStreamUtils.upload_files(reports_dir, eval_report)
-    
+
     dao.update_eval_report_data(request.cultivacation_id, file_id_map.get('投放验证报告'))
-    
+    logger.info(f"Eval report uploaded: file_id={file_id_map.get('投放验证报告')}")
+
     content = [
         {
             "id": 1,

+ 53 - 45
api/recommend.py

@@ -1,77 +1,85 @@
 from database import MySqlDao
-from fastapi import APIRouter, BackgroundTasks
+from fastapi import APIRouter, BackgroundTasks, HTTPException, status
 from .request_body import RecommendRequest
+from core import get_logger
 
 from models import Recommend
 import os
 from utils import FileStreamUtils, ReportUtils
 
+logger = get_logger("api.recommend")
 dao = MySqlDao()
-
 router = APIRouter()
 
+
 @router.post("/recommend")
 async def recommend(request: RecommendRequest, backgroundTasks: BackgroundTasks):
     """推荐接口"""
+    logger.info(f"Recommend request: city={request.city_uuid}, product={request.product_code}, recall={request.recall_cust_count}")
+
     gbdtlr_model_path = os.path.join("./models/rank/weights", request.city_uuid, "gbdtlr_model.pkl")
     if not os.path.exists(gbdtlr_model_path):
-        return {"code": 200, "msg": "model not defined", "data": {"recommendationInfo": "该城市的模型未训练,请先进行训练"}}
-    
-    # 初始化模型
+        logger.warning(f"Model not found: {gbdtlr_model_path}")
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="该城市的模型未训练,请先进行训练",
+        )
+
     recommend_model = Recommend(request.city_uuid)
-    
-    # 判断该品规是否是新品规
-    products_in_oreder = dao.get_product_from_order(request.city_uuid)["product_code"].unique().tolist()
-    if request.product_code in products_in_oreder:
+
+    products_in_order = dao.get_product_from_order(request.city_uuid)["product_code"].unique().tolist()
+    if request.product_code in products_in_order:
+        logger.info(f"Using GBDT-LR model for existing product {request.product_code}")
         recommend_list = recommend_model.get_recommend_list_by_gbdtlr(request.product_code, recall_count=request.recall_cust_count)
     else:
+        logger.info(f"Using Item2Vec model for new product {request.product_code}")
         recommend_list = recommend_model.get_recommend_list_by_item2vec(request.product_code, recall_count=request.recall_cust_count)
+
     recommend_data = recommend_model.get_recommend_and_delivery(recommend_list, delivery_count=request.delivery_count)
     request_data = []
     for index, data in enumerate(recommend_data):
-        id = index + 1
         request_data.append(
             {
-                "id": id,
+                "id": index + 1,
                 "cust_code": data["cust_code"],
                 "recommend_score": data["recommend_score"],
-                "delivery_count": data["delivery_count"]
+                "delivery_count": data["delivery_count"],
             }
         )
-    
-    # 异步执行报告生成任务
-    backgroundTasks.add_task(
-        generate_and_upload_report,
-        request
-    )
-    
+
+    logger.info(f"Recommend completed: {len(request_data)} customers recommended")
+
+    backgroundTasks.add_task(generate_and_upload_report, request)
+
     return {"code": 200, "msg": "success", "data": {"recommendationInfo": request_data}}
 
+
 def generate_and_upload_report(request: RecommendRequest):
     """生成并上传报告到阿里云文件数据库"""
-    # 生成相关报告
-    report_util = ReportUtils(request.city_uuid, request.product_code)
-    report_util.generate_all_data(request.recall_cust_count, request.delivery_count)
-    
-    # 上传报告
-    reports_dir = os.path.join('./data/reports', request.city_uuid, request.product_code)
-    report_files = [
-        '卷烟信息表',
-        '品规商户特征关系表',
-        '相似卷烟表',
-        '商户售卖推荐表'
-    ]
-    file_id_map = FileStreamUtils.upload_files(reports_dir, report_files)
-    
-    # 将返回的file_id保存到数据库中
-    data_dict = {
-        'cultivacation_id': request.cultivacation_id,
-        'city_uuid': request.city_uuid,
-        'limit_cycle_name': request.limit_cycle_name,
-        'product_code': request.product_code,
-        'product_info_table': file_id_map.get('卷烟信息表'),
-        'relation_table': file_id_map.get('品规商户特征关系表'),
-        'similarity_product_table': file_id_map.get('相似卷烟表'),
-        'recommend_table': file_id_map.get('商户售卖推荐表'),
-    }
-    dao.insert_report(data_dict)
+    logger.info(f"Background task started: generating report for {request.city_uuid}/{request.product_code}")
+    try:
+        report_util = ReportUtils(request.city_uuid, request.product_code)
+        report_util.generate_all_data(request.recall_cust_count, request.delivery_count)
+
+        reports_dir = os.path.join("./data/reports", request.city_uuid, request.product_code)
+        report_files = ["卷烟信息表", "品规商户特征关系表", "相似卷烟表", "商户售卖推荐表"]
+        file_id_map = FileStreamUtils.upload_files(reports_dir, report_files)
+
+        if file_id_map is None:
+            logger.error(f"Report upload failed for {request.city_uuid}/{request.product_code}")
+            return
+
+        data_dict = {
+            "cultivacation_id": request.cultivacation_id,
+            "city_uuid": request.city_uuid,
+            "limit_cycle_name": request.limit_cycle_name,
+            "product_code": request.product_code,
+            "product_info_table": file_id_map.get("卷烟信息表"),
+            "relation_table": file_id_map.get("品规商户特征关系表"),
+            "similarity_product_table": file_id_map.get("相似卷烟表"),
+            "recommend_table": file_id_map.get("商户售卖推荐表"),
+        }
+        dao.insert_report(data_dict)
+        logger.info(f"Background task completed: report uploaded for {request.city_uuid}/{request.product_code}")
+    except Exception as e:
+        logger.error(f"Background task failed: {e}", exc_info=True)

+ 9 - 38
api/report.py

@@ -1,68 +1,39 @@
 from database import MySqlDao
 from fastapi import APIRouter, status, HTTPException
-import os
 from .request_body import ReportRequest
+from core import get_logger
 
-dao =MySqlDao()
+logger = get_logger("api.report")
+dao = MySqlDao()
 router = APIRouter()
 
 @router.post("/report")
 async def report(request: ReportRequest):
     """获取推荐相关报告接口"""
+    logger.info(f"Report request: cultivacation_id={request.cultivacation_id}")
+
     file_id_record = dao.get_report_file_id(request.cultivacation_id)
     if file_id_record.empty:
         raise HTTPException(
             status_code=status.HTTP_404_NOT_FOUND,
             detail="Reports not found"
         )
-    
+
     file_id_map = {
         '卷烟信息表': file_id_record['product_info_table'].item(),
         '品规商户特征关系表': file_id_record['relation_table'].item(),
         '相似卷烟表': file_id_record['similarity_product_table'].item()
     }
-    
+
     request_data = []
     for index, filename in enumerate(file_id_map):
         request_data.append(
             {
-                "id": index+1,
+                "id": index + 1,
                 "filename": filename,
                 "file_id": file_id_map.get(filename)
             }
         )
-        
-    return {"code": 200, "msg": "success", "data": {"reportInfo": request_data}}
 
-# @router.post("/report")
-# async def report_api(request: ReportRequest):
-#     """获取推荐相关报告接口"""
-#     files_id_path = os.path.join("./data/reports", request.city_uuid, request.product_code, "files_id.txt")
-#     if not os.path.exists(files_id_path):
-#         raise HTTPException(
-#             status_code=status.HTTP_404_NOT_FOUND,
-#             detail="Reports not found"
-#         )
-    
-#     with open(files_id_path, 'r', encoding="utf-8") as file:
-#         lines = file.readlines()
-        
-#     if lines[0].strip() == "failed":
-#         raise HTTPException(
-#             status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
-#             detail="reports upload failed"
-#         )
-    
-#     request_data = []
-#     for index, line in enumerate(lines):
-#         filename, file_id = line.strip().split(",")
-#         request_data.append(
-#             {
-#                 "id": index+1,
-#                 "filename": filename,
-#                 "file_id": file_id
-#             }
-#         )
-    
-#     return {"code": 200, "msg": "success", "data": {"reportInfo": request_data}}
+    return {"code": 200, "msg": "success", "data": {"reportInfo": request_data}}