84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
"""病例检查项: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,
|
|
)
|