from sqlalchemy.orm import Session from app.core.exceptions import AppError from app.models.training import SessionOrder from app.repositories.case_repository import CaseRepository from app.repositories.session_repository import SessionRepository from app.schemas.session import CreateOrderResponse, OrderItemResponse, OrderItemsResponse from app.services.runtime_memory import runtime_memory class OrderService: """检查检验服务:提供可申请项目和数据库固定结果返回。""" PHYSICAL_TYPES = {"physical", "physical_exam", "inspection", "palpation", "percussion", "auscultation"} PHYSICAL_KEYWORDS = ("体格", "体征", "查体", "听诊", "叩诊", "触诊") def __init__(self, db: Session) -> None: self.db = db self.case_repo = CaseRepository(db) self.session_repo = SessionRepository(db) def list_order_items(self, session_id: int, user_id: str) -> OrderItemsResponse: """检查项目列表:按会话病例返回可申请项目,不返回结果。""" session = self._get_session(session_id, user_id) items = self.case_repo.get_exam_items(session.case_id) return self._items_response(items) def list_physical_exam_items(self, session_id: int, user_id: str) -> OrderItemsResponse: """体格检查列表:从当前病例检查项中筛选体格检查类项目。""" session = self._get_session(session_id, user_id) items = [item for item in self.case_repo.get_exam_items(session.case_id) if self._is_physical_item(item)] return self._items_response(items) def list_auxiliary_exam_items(self, session_id: int, user_id: str) -> OrderItemsResponse: """辅助检查列表:从当前病例检查项中筛选非体格检查类项目。""" session = self._get_session(session_id, user_id) items = [item for item in self.case_repo.get_exam_items(session.case_id) if not self._is_physical_item(item)] return self._items_response(items) def create_physical_exam_order(self, session_id: int, user_id: str, item_code: str) -> CreateOrderResponse: """体格检查结果:复用检查申请逻辑,结果仍只来自数据库。""" return self._create_order(session_id, user_id, item_code, require_physical=True) def create_auxiliary_exam_order(self, session_id: int, user_id: str, item_code: str) -> CreateOrderResponse: """辅助检查结果:复用检查申请逻辑,结果仍只来自数据库。""" return self._create_order(session_id, user_id, item_code, require_physical=False) def _items_response(self, items) -> OrderItemsResponse: """检查列表响应:把 ORM 检查项转换成前端列表结构。""" return OrderItemsResponse( items=[ OrderItemResponse(item_code=item.item_code, item_name=item.item_name, item_type=item.item_type) for item in items ] ) def _is_physical_item(self, item) -> bool: """检查分类:按 item_type 和 category 识别体格检查,其他归入辅助检查。""" item_type = (item.item_type or "").lower() category = item.category or "" if item_type in self.PHYSICAL_TYPES: return True return any(keyword in category or keyword in item.item_name for keyword in self.PHYSICAL_KEYWORDS) def create_order(self, session_id: int, user_id: str, item_code: str) -> CreateOrderResponse: """检查申请:从数据库读取检查结果并写入当前会话记录。""" return self._create_order(session_id, user_id, item_code) def _create_order(self, session_id: int, user_id: str, item_code: str, require_physical: bool | None = None) -> CreateOrderResponse: """检查申请:统一校验会话、项目类型和幂等逻辑,检查结果只来自数据库。""" session = self._get_session(session_id, user_id) if session.status not in {"inquiry", "diagnosis", "treatment"}: raise AppError("SESSION_STATUS_INVALID", "current session does not allow ordering", 400) item = self.case_repo.get_exam_item(session.case_id, item_code) if not item: raise AppError("ORDER_ITEM_NOT_FOUND", "order item not found for current case", 404) if require_physical is not None and self._is_physical_item(item) != require_physical: raise AppError("ORDER_ITEM_TYPE_MISMATCH", "order item type does not match current exam endpoint", 400) existing_order = self.session_repo.get_order_by_item(session.id, item.item_code) if existing_order: return self._to_response(existing_order, already_ordered=True) order = self.session_repo.create_order( SessionOrder( session_id=session.id, user_id=user_id, case_id=session.case_id, case_exam_item_id=item.id, item_code=item.item_code, item_name=item.item_name, item_type=item.item_type, result_text=item.result_text, result_structured=item.result_structured, is_key=item.is_key, is_abnormal=item.is_abnormal, ) ) runtime_memory.add_message( session.memory_key or "", "tool", f"申请检查/检验:{order.item_name}。结果:{order.result_text}", {"item_code": order.item_code, "result_structured": order.result_structured}, ) return self._to_response(order, already_ordered=False) def _to_response(self, order: SessionOrder, already_ordered: bool) -> CreateOrderResponse: """检查响应:统一把会话检查记录转换为接口返回结构。""" return CreateOrderResponse( item_code=order.item_code, item_name=order.item_name, item_type=order.item_type, result_text=order.result_text, result_structured=order.result_structured, is_key=order.is_key, is_abnormal=order.is_abnormal, context_written=True, already_ordered=already_ordered, ) def _get_session(self, session_id: int, user_id: str): """会话校验:确认检查申请属于当前用户会话。""" session = self.session_repo.get_owned_session(session_id, user_id) if not session: raise AppError("SESSION_NOT_FOUND", "session not found or not owned by current user", 404) return session