2c9ba9d6b3
新增 experience_feedback 表(32 表迁移,alembic head b7d1f4a92c3e),
老师傅对系统推荐方案给出"采纳 / 调整 / 拒绝"反馈,按"产品指纹 +
工艺参数"为键跨任务匹配,下次同指纹产品分析自动消费。
变更内容:
- src/moldinsight/models/experience_feedback.py(new)ORM:Base 单点来源、
跨模块裸 FK(user_id / processing_task_id / stp_file_id)、不建 ORM
relationship;fingerprint JSON 列存跨任务匹配键
- src/moldinsight/models/__init__.py 导出 ExperienceFeedback
- migrations/versions/b7d1f4a92c3e_add_experience_feedback.py(new)32 表
迁移;fingerprint 列 PG 下加 GIN 索引(jsonb_path_query 支持)
- src/shared/database/init_db.py 加 3 个权限码(view_experience_feedback /
feedback_experience_hint / manage_experience_feedback)+ 新角色
process_engineer;admin 角色 permissions 同步补齐;init_permissions /
init_roles 改为按 code 比对(新增保留已有 id,避免 FK 引用失效)——
修复既有 DB 启动期漏掉新权限的幂等 bug
- src/moldinsight/services/experience_feedback_service.py(new)service:
compute_fingerprint 分桶(bbox_aspect / volume_bucket / face_bucket /
undercut_class / material_family / is_foam)/ record_feedback(D9 边界:
service.flush + 路由 commit;D17 衰减:同 stp_file_id 整体续期 90 天 TTL,
无 celery beat 依赖)/ list_hints_for_task / resolve_for_process_params
- src/moldinsight/api/experience_feedback_router.py(new)路由:Pydantic
模型写在路由文件内(项目硬规则);POST /api/tasks/{task_id}/experience-feedback
+ GET /api/tasks/{task_id}/experience-hints;归属 TaskQueryService.ensure_task_access
+ User.has_permission 全仓首次调用点
- src/moldinsight/api/__init__.py ROUTE_MODULES 注册新路由
- tests/test_model_ownership.py EXPECTED_TABLES 加 experience_feedback
(31→32)
- tests/test_experience_feedback_fingerprint.py(new)分桶参数化覆盖
bbox / volume / face / undercut / material / is_foam 各边界值
- tests/test_experience_feedback_router.py(new)API 契约 9 例
(401/403/422/200 路径 + 衰减续期 + 任务归属校验 + ORM 注册收口)
- docs/STATUS.md 顶部加 2026-09-23 批 1 日志条目
- docs/TECH_DEBT.md D17 加批 1 已完成描述 + 剩余工作清单
- docs/API_CONTRACT.md §3.2 加 D17 端点表格
测试基线:185 passed, 9 skipped(净增 59 测试)。
Co-Authored-By: Claude Code <noreply@anthropic.com>
71 lines
3.1 KiB
Python
71 lines
3.1 KiB
Python
from fastapi import APIRouter
|
||
import importlib
|
||
|
||
from shared.config.settings import settings
|
||
from shared.utils.logger import get_logger
|
||
from moldinsight.api.route_registry import route_load_status
|
||
from moldinsight.api.html_report_router import include_into as include_html_report
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
router = APIRouter()
|
||
|
||
# 业务路由装载清单:新增路由必须登记于此。
|
||
# 失败语义(原 _safe_include 仅 WARNING 跳过,进程带病启动不可感知):
|
||
# - 非 DEBUG:记录进 route_load_status["failed"],/api/health 呈现 degraded
|
||
# - DEBUG:直接抛错 fail fast——开发环境路由缺失必须当场暴露
|
||
ROUTE_MODULES = [
|
||
# (label, module_path, debug_only)
|
||
("健康检查", "moldinsight.api.health_router", False),
|
||
("上传", "moldinsight.api.upload_router", False),
|
||
("批量", "moldinsight.api.batch_router", False),
|
||
("任务", "moldinsight.api.task_router", False),
|
||
("历史", "moldinsight.api.history_router", False),
|
||
("CAM", "moldinsight.api.cam_router", False),
|
||
("设计", "moldinsight.api.design_router", False),
|
||
("成本", "moldinsight.api.cost_router", False),
|
||
("加工", "moldinsight.api.machining_router", False),
|
||
("导出", "moldinsight.api.export_router", False),
|
||
("铝价", "moldinsight.api.aluminum_price_routes", False),
|
||
("老师傅经验反馈", "moldinsight.api.experience_feedback_router", False), # D17 Human-in-Loop
|
||
# 调试端点会 dump 全量任务数据,仅 DEBUG 模式注册(双重防线:还需登录)
|
||
("调试", "moldinsight.api.debug_router", True),
|
||
]
|
||
|
||
|
||
def _safe_include(label: str, module_path: str, debug_only: bool = False):
|
||
if debug_only and not settings.DEBUG:
|
||
route_load_status["disabled"].append({"label": label, "module": module_path})
|
||
return
|
||
try:
|
||
module = importlib.import_module(module_path)
|
||
router_obj = getattr(module, "router", None)
|
||
if router_obj is None:
|
||
raise ValueError("未找到 router 对象")
|
||
router.include_router(router_obj)
|
||
route_load_status["loaded"].append({"label": label, "module": module_path})
|
||
logger.info(f"{label} 路由加载成功")
|
||
except Exception as exc:
|
||
route_load_status["failed"].append(
|
||
{"label": label, "module": module_path, "error": str(exc)}
|
||
)
|
||
logger.error(f"{label} 路由加载失败: {exc}")
|
||
if settings.DEBUG:
|
||
raise
|
||
|
||
|
||
for _label, _module_path, _debug_only in ROUTE_MODULES:
|
||
_safe_include(_label, _module_path, _debug_only)
|
||
|
||
|
||
def register_moldinsight_routers(app):
|
||
"""注册 moldinsight 全部业务路由到 app(D3 收敛:入口侧单点调用)。
|
||
|
||
- /api 聚合路由:各子路由模块装载失败经 ROUTE_MODULES/route_load_status 呈现
|
||
(/api/health degraded,DEBUG fail-fast),见 _safe_include
|
||
- HTML 报告代理挂根路径 /html/{filename}(URL 形状与原 StaticFiles 一致),
|
||
失败同样登记 route_load_status(html_report_router.include_into)
|
||
"""
|
||
app.include_router(router, prefix="/api")
|
||
include_html_report(app)
|