Files
geMoldInsight/tests/test_model_ownership.py
cjw 2c9ba9d6b3 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>
2026-09-23 16:11:17 +08:00

79 lines
3.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""D3 模型拆分归属保护(批次 4,2026-09-17)+ D17 Human-in-Loop 闭环。
锁定三个拆分成果:
1. 三包模型全量注册后 mapper 可配置、32 表齐全(含 D17 新增 experience_feedback);
2. 单模块部署(inventory-only / moldinsight-only + auth)独立配置 mapper 成功——
跨模块 ORM relationship 已清零,任何一侧不注册对方模型也能工作;
3. 旧 shared.models.database 模块已删除且无兼容 facade(诚实原则:不留假象)。
"""
import importlib
import subprocess
import sys
from pathlib import Path
SRC = str(Path(__file__).resolve().parent.parent / "src")
EXPECTED_TABLES = {
# identity(shared.models.identity)
"users", "roles", "permissions", "user_roles", "role_permissions",
"user_activities", "system_logs",
# moldinsight.models
"stp_files", "geometry_data", "mesh_data", "html_files", "processing_tasks",
"mold_cavity_data", "feature_detections", "design_recommendations", "analysis_metrics",
# D17:老师傅经验反馈(Human-in-Loop 闭环,2026-09)
"experience_feedback",
# inventory.models
"products", "product_materials", "material_price_history", "material_suppliers",
"suppliers", "customers", "warehouses", "inventory", "stock_movements",
"purchase_orders", "purchase_order_items", "sales_orders", "sales_order_items",
"finance_transactions", "finance_allocations",
}
def test_full_registration_covers_all_32_tables():
import shared.models.identity # noqa: F401
import moldinsight.models # noqa: F401
import inventory.models # noqa: F401
from sqlalchemy.orm import configure_mappers
from shared.models.base import Base
configure_mappers()
assert set(Base.metadata.tables) == EXPECTED_TABLES
def test_single_module_deployments_configure_mappers_independently():
"""单模块注册子进程验证:inventory-only 与 moldinsight-only(含 auth identity)
均可在不 import 对方业务模型的情况下 configure_mappers 成功。
用子进程隔离,避免污染本进程的 mapper 注册表。"""
code = (
"import sys; sys.path.insert(0, r'%s')\n"
"from sqlalchemy.orm import configure_mappers\n"
"%s\n"
"configure_mappers()\n"
"print('ok')\n"
)
cases = [
# inventory-only:inventory 模型 + auth 必带的 identity
"import inventory.models, shared.models.identity",
# moldinsight-only:moldinsight 模型 + auth 必带的 identity
"import moldinsight.models, shared.models.identity",
]
for imports in cases:
proc = subprocess.run(
[sys.executable, "-c", code % (SRC, imports)],
capture_output=True, text=True, timeout=120,
)
assert proc.returncode == 0, f"{imports} 配置失败:\n{proc.stderr}"
assert proc.stdout.strip().endswith("ok")
def test_legacy_database_module_is_gone():
"""旧 shared.models.database 已物理删除,无兼容 facade。"""
try:
importlib.import_module("shared.models.database")
except ModuleNotFoundError:
pass
else:
raise AssertionError("shared.models.database 仍可导入——拆分后不允许残留兼容 facade")