D17 批 1:Human-in-Loop 老师傅经验反馈(数据 + 权限 + 写入 API)
新增 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>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""moldinsight 域模型:老师傅经验反馈表。
|
||||
|
||||
Human-in-Loop 闭环(D17,2026-09):老师傅对系统推荐方案给出"采纳 / 调整 / 拒绝"
|
||||
反馈,按"产品指纹 + 工艺参数"为索引跨任务匹配;下次同指纹产品分析自动消费
|
||||
(OCC worker payload 透传 → MultiSchemeMoldPlanner → PartingSchemeScorer 加成)。
|
||||
|
||||
跨模块桥接只保留裸 FK,不建 ORM relationship(base.py 约定):
|
||||
- processing_task_id -> processing_tasks.id
|
||||
- stp_file_id -> stp_files.id
|
||||
- user_id -> users.id
|
||||
|
||||
写入 feedback 时由 service 层填充 fingerprint JSON(bbox_aspect / volume_bucket /
|
||||
face_bucket / undercut_class / material_family / is_foam)。fingerprint 是跨任务
|
||||
匹配的索引列(PG 下有 GIN 索引支持 jsonb_path_query 类查询)。
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, JSON, Float, ForeignKey, Index
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from shared.models.base import Base
|
||||
|
||||
|
||||
class ExperienceFeedback(Base):
|
||||
"""老师傅经验反馈:方案级整体反馈 + 跨任务指纹匹配。
|
||||
|
||||
关键字段:
|
||||
- feedback_status: 'adopted' / 'adjust' / 'rejected'
|
||||
- fingerprint: 跨任务匹配键,结构见 moldinsight.services.experience_feedback_service
|
||||
- expires_at: 90 天 TTL;写新反馈时同 stp_file_id 整体续期(D17 衰减机制)
|
||||
"""
|
||||
__tablename__ = "experience_feedback"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# ── 任务归属(跨模块裸 FK,CASCADE 随任务 / 文件清理)──
|
||||
processing_task_id = Column(
|
||||
Integer,
|
||||
ForeignKey("processing_tasks.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
stp_file_id = Column(
|
||||
Integer,
|
||||
ForeignKey("stp_files.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
# ── 方案标识 ──
|
||||
scheme_id = Column(String(64), nullable=False, index=True)
|
||||
scheme_axis = Column(String(1), nullable=False)
|
||||
scheme_method = Column(String(50), nullable=True)
|
||||
|
||||
# ── 反馈主体 ──
|
||||
feedback_status = Column(String(20), nullable=False, index=True)
|
||||
feedback_reason = Column(Text, nullable=True)
|
||||
adjust_suggestion = Column(Text, nullable=True)
|
||||
|
||||
# ── 上下文快照(用于回放)──
|
||||
process_params_snapshot = Column(JSON, nullable=True)
|
||||
fingerprint = Column(JSON, nullable=False)
|
||||
confidence_at_submit = Column(Float, nullable=True)
|
||||
score_at_submit = Column(Float, nullable=True)
|
||||
|
||||
# ── 审计(user_id RESTRICT:禁止级联删,保留审计归因)──
|
||||
user_id = Column(
|
||||
Integer,
|
||||
ForeignKey("users.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
role_code = Column(String(50), nullable=False)
|
||||
|
||||
# ── 时间戳与衰减 ──
|
||||
created_at = Column(DateTime, default=func.now(), index=True)
|
||||
expires_at = Column(DateTime, nullable=True, index=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"ix_experience_feedback_stp_axis_status",
|
||||
"stp_file_id",
|
||||
"scheme_axis",
|
||||
"feedback_status",
|
||||
),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<ExperienceFeedback(id={self.id}, stp_file_id={self.stp_file_id}, "
|
||||
f"scheme_id='{self.scheme_id}', axis='{self.scheme_axis}', "
|
||||
f"status='{self.feedback_status}')>"
|
||||
)
|
||||
Reference in New Issue
Block a user