"""病例检查项:C1 解析归一化、C3 落库校验。""" from decimal import Decimal, InvalidOperation from config.exceptions import AppError EXAM_ITEM_FIELDS = { 'item_code', 'item_name', 'item_type', 'category', 'result_text', 'result_structured', 'is_key', 'is_abnormal', 'score_weight', 'display_order', } def normalize_exam_items(raw_items) -> list: """归一化检查项列表;按 item_code 去重(保留首次出现)。""" if not raw_items: return [] if not isinstance(raw_items, list): raise AppError('CASE_VALIDATION_ERROR', 'exam_items 必须为数组', status_code=400) seen = set() normalized = [] for i, item in enumerate(raw_items): if not isinstance(item, dict): raise AppError('CASE_VALIDATION_ERROR', f'exam_items[{i}] 必须为对象', status_code=400) code = (item.get('item_code') or '').strip() name = (item.get('item_name') or '').strip() item_type = (item.get('item_type') or '').strip() result_text = (item.get('result_text') or '').strip() if not code: raise AppError('CASE_VALIDATION_ERROR', f'exam_items[{i}].item_code 必填', status_code=400) if not name: raise AppError('CASE_VALIDATION_ERROR', f'exam_items[{i}].item_name 必填', status_code=400) if not item_type: raise AppError('CASE_VALIDATION_ERROR', f'exam_items[{i}].item_type 必填', status_code=400) if not result_text: raise AppError('CASE_VALIDATION_ERROR', f'exam_items[{i}].result_text 必填', status_code=400) if code in seen: continue seen.add(code) try: weight = item.get('score_weight', 1) weight = Decimal(str(weight)) except (InvalidOperation, TypeError, ValueError): raise AppError( 'CASE_VALIDATION_ERROR', f'exam_items[{i}].score_weight 须为数字', status_code=400, ) if weight <= 0: raise AppError( 'CASE_VALIDATION_ERROR', f'exam_items[{i}].score_weight 须大于 0', status_code=400, ) normalized.append({ 'item_code': code[:64], 'item_name': name[:128], 'item_type': item_type[:32], 'category': (item.get('category') or '')[:64], 'result_text': result_text, 'result_structured': item.get('result_structured') if isinstance( item.get('result_structured'), dict ) else None, 'is_key': bool(item.get('is_key', False)), 'is_abnormal': bool(item.get('is_abnormal', False)), 'score_weight': float(weight), 'display_order': int(item.get('display_order', len(normalized) + 1)), }) return normalized def assert_no_duplicate_exam_items(items: list): """创建前再次校验同一病例内 item_code 不重复。""" codes = [it['item_code'] for it in items] if len(codes) != len(set(codes)): raise AppError( 'CASE_EXAM_ITEM_DUPLICATE', '同一病例内检查项 item_code 不能重复', status_code=400, )