"""实验室样本处理数据 — 聚合市场/生产/质检已发生业务数据与部门字段回写。 Excel 仅作为字段目录示例;列表以协议样本为主表,关联样本接收与生产接收, 补充表只承载源系统没有的列,且不得覆盖源表字段。 """ from __future__ import annotations import json import uuid from datetime import datetime from decimal import Decimal from typing import Any, Dict, List, Optional, Tuple from types import SimpleNamespace from sqlalchemy import or_, text from sqlalchemy.orm import Session from app.models.models import ( BizCellSampleType, BizCollectionHospital, BizCollectionProtocol, BizProtocolCollectionInfo, BizProtocolPartyCustomer, BizProtocolSample, BizSampleProductionReception, BizSampleReception, BizSampleReceptionCheckResult, ) from app.services.lab_sample_processing_fields import ( DATASET_HSC, DATASET_MSC, DATASET_UBMC, DEPT_EDIT_PERMS, PERM_LIST, PERM_MANAGER, SOURCE_EXTRA, SOURCE_PRODUCTION, SOURCE_PROTOCOL, SOURCE_RECEPTION, editable_depts_for_user, field_map, fields_for, resolve_dataset_by_type_name, ) def _new_id() -> str: return uuid.uuid4().hex.upper() def _fmt_dt(v: Any) -> Optional[str]: if v is None: return None if isinstance(v, datetime): return v.strftime("%Y-%m-%d %H:%M:%S") return str(v) def _fmt_num(v: Any) -> Any: if v is None: return None if isinstance(v, Decimal): return float(v) return v def _parse_extra(raw: Optional[str]) -> Dict[str, Any]: if not raw: return {} try: data = json.loads(raw) except Exception: return {} return data if isinstance(data, dict) else {} def user_acl(db: Session, user: dict) -> Dict[str, Any]: from app.core.menu_guard import user_has_menu_perm is_manager = user_has_menu_perm(db, user, PERM_MANAGER) dept_flags = { dept: user_has_menu_perm(db, user, code) for dept, code in DEPT_EDIT_PERMS.items() } editable = editable_depts_for_user(is_manager=is_manager, dept_flags=dept_flags) return { "canList": user_has_menu_perm(db, user, PERM_LIST) or is_manager or any(dept_flags.values()), "isManager": is_manager, "editableDepts": sorted(editable), "deptFlags": dept_flags, } def _type_name_filters(dataset: str): if dataset == DATASET_HSC: return "%造血%" if dataset == DATASET_MSC: return "%间充质%" if dataset == DATASET_UBMC: return "%单个核%" return None def _code_prefix_filters(dataset: str) -> List[str]: if dataset == DATASET_HSC: return ["B%"] if dataset == DATASET_MSC: return ["BC%", "UC%"] if dataset == DATASET_UBMC: return ["UBMC%"] return [] def _match_keyword(row: Dict[str, Any], kw: str) -> bool: if not kw: return True text = ( f"{row.get('sampleCode') or ''} " f"{row.get('protocolSampleCode') or ''} " f"{row.get('motherName') or ''}" ) return kw.lower() in text.lower() def _infectious_label(code: Optional[str]) -> Optional[str]: c = ("" if code is None else str(code)).strip() if not c: return None if c == "zc": return "正常" if c == "bzc": return "不正常" return c def _infectious_code(label: Optional[Any]) -> Optional[str]: if label is None: return None s = str(label).strip() if not s: return None if s == "正常": return "zc" if s == "不正常": return "bzc" return s def _yes_no_from_bool(flag: Optional[bool]) -> Optional[str]: if flag is None: return None return "是" if flag else "否" def _derive_receive_check(flags: Dict[str, bool]) -> Optional[str]: if not flags: return None skip = {"知情同意书", "健康调查表", "样本信息正确,标识清晰"} preferred = [ "针路密闭", "容器无破损", "外观正常", "样本无破损", "针卡关紧", "两端系紧", "温度4-25", "采集至到库时间≤24小时", ] parts = [name for name in preferred if flags.get(name)] if not parts: parts = [name for name, checked in flags.items() if checked and name not in skip] return "、".join(parts) if parts else None def _check_flag_map( db: Session, tenant_no: str, reception_ids: List[str] ) -> Dict[str, Dict[str, bool]]: ids = [i for i in reception_ids if i] if not ids: return {} rows = ( db.query(BizSampleReceptionCheckResult) .filter( BizSampleReceptionCheckResult.tenant_no == tenant_no, BizSampleReceptionCheckResult.del_flag == 0, BizSampleReceptionCheckResult.reception_id.in_(ids), ) .all() ) out: Dict[str, Dict[str, bool]] = {} for r in rows: bucket = out.setdefault(r.reception_id, {}) name = (r.item_name or "").strip() if name: bucket[name] = bool(r.checked) return out def _pick_extra( extras: Dict[str, Any], *codes: Optional[str] ) -> Dict[str, Any]: for code in codes: key = (code or "").strip() if key and key in extras: return _parse_extra(extras[key].payload_json) return {} def _match_date_range(row: Dict[str, Any], date_from: Optional[str], date_to: Optional[str]) -> bool: if not date_from and not date_to: return True for key in ("receptionTime", "collectTime", "cryoTime", "storageDateP2"): val = row.get(key) if not val: continue day = str(val)[:10].replace(".", "-").replace("/", "-") # normalize compact dates like 2025-9-8 parts = day.split("-") if len(parts) == 3: try: day = f"{int(parts[0]):04d}-{int(parts[1]):02d}-{int(parts[2]):02d}" except Exception: pass if date_from and day < date_from: continue if date_to and day > date_to: continue return True return False def list_rows( db: Session, *, tenant_no: str, dataset: str, keyword: Optional[str] = None, date_from: Optional[str] = None, date_to: Optional[str] = None, page: int = 1, page_size: int = 20, ) -> Tuple[List[Dict[str, Any]], int]: from app.models.models import BizLabSampleProcessingExtra type_like = _type_name_filters(dataset) code_likes = _code_prefix_filters(dataset) q = ( db.query( BizProtocolSample, BizCellSampleType, BizProtocolPartyCustomer, BizProtocolCollectionInfo, BizCollectionHospital, ) .outerjoin( BizCellSampleType, (BizCellSampleType.id == BizProtocolSample.sample_type_id) & (BizCellSampleType.del_flag == 0), ) .outerjoin( BizProtocolPartyCustomer, (BizProtocolPartyCustomer.collection_protocol_id == BizProtocolSample.collection_protocol_id) & (BizProtocolPartyCustomer.tenant_no == tenant_no) & (BizProtocolPartyCustomer.del_flag == 0), ) .outerjoin( BizProtocolCollectionInfo, (BizProtocolCollectionInfo.collection_protocol_id == BizProtocolSample.collection_protocol_id) & (BizProtocolCollectionInfo.tenant_no == tenant_no) & (BizProtocolCollectionInfo.del_flag == 0), ) .outerjoin( BizCollectionHospital, (BizCollectionHospital.id == BizProtocolCollectionInfo.hospital_id) & (BizCollectionHospital.del_flag == 0), ) .filter( BizProtocolSample.tenant_no == tenant_no, BizProtocolSample.del_flag == 0, BizProtocolSample.sample_code.isnot(None), BizProtocolSample.sample_code != "", ) ) type_or_code = [] if type_like: type_or_code.append(BizCellSampleType.type_name.like(type_like)) for pref in code_likes: type_or_code.append(BizProtocolSample.sample_code.like(pref)) if type_or_code: q = q.filter(or_(*type_or_code)) rows = q.order_by(BizProtocolSample.create_time.desc()).all() filtered = [] for sample, stype, party, coll, hospital in rows: type_name = (stype.type_name if stype else "") or "" code = sample.sample_code or "" if resolve_dataset_by_type_name(type_name, code) != dataset: continue filtered.append((sample, stype, party, coll, hospital)) extras = { e.sample_code: e for e in db.query(BizLabSampleProcessingExtra) .filter( BizLabSampleProcessingExtra.tenant_no == tenant_no, BizLabSampleProcessingExtra.del_flag == 0, BizLabSampleProcessingExtra.dataset == dataset, ) .all() } # 仅聚合真实协议样本;补充表只挂到已有样本上,不再单独展示 Excel 演示行 codes = [s.sample_code for s, *_ in filtered if s.sample_code] sample_ids = [s.id for s, *_ in filtered if s.id] receptions_by_sample_id: Dict[str, BizSampleReception] = {} receptions_by_code: Dict[str, BizSampleReception] = {} productions: Dict[str, BizSampleProductionReception] = {} if sample_ids or codes: recv_filters = [] if sample_ids: recv_filters.append(BizSampleReception.protocol_sample_id.in_(sample_ids)) if codes: recv_filters.append(BizSampleReception.sample_code.in_(codes)) for r in ( db.query(BizSampleReception) .filter( BizSampleReception.tenant_no == tenant_no, BizSampleReception.del_flag == 0, or_(*recv_filters) if recv_filters else False, ) .order_by(BizSampleReception.reception_time.desc()) .all() ): if r.protocol_sample_id: receptions_by_sample_id.setdefault(r.protocol_sample_id, r) if r.sample_code: receptions_by_code.setdefault(r.sample_code, r) if receptions_by_sample_id or receptions_by_code: rids = list( { *(r.id for r in receptions_by_sample_id.values()), *(r.id for r in receptions_by_code.values()), } ) for p in ( db.query(BizSampleProductionReception) .filter( BizSampleProductionReception.tenant_no == tenant_no, BizSampleProductionReception.del_flag == 0, BizSampleProductionReception.reception_id.in_(rids), ) .order_by(BizSampleProductionReception.process_time.desc()) .all() ): productions.setdefault(p.reception_id, p) protocol_ids = sorted( { s.collection_protocol_id for s, *_ in filtered if getattr(s, "collection_protocol_id", None) } ) protocols: Dict[str, BizCollectionProtocol] = {} if protocol_ids: for p in ( db.query(BizCollectionProtocol) .filter( BizCollectionProtocol.tenant_no == tenant_no, BizCollectionProtocol.del_flag == 0, BizCollectionProtocol.id.in_(protocol_ids), ) .all() ): protocols[p.id] = p reception_ids_for_flags = list( { *(r.id for r in receptions_by_sample_id.values() if r and r.id), *(r.id for r in receptions_by_code.values() if r and r.id), } ) check_flags_by_reception = _check_flag_map(db, tenant_no, reception_ids_for_flags) built_map: Dict[str, Dict[str, Any]] = {} for sample, stype, party, coll, hospital in filtered: code = sample.sample_code reception = receptions_by_sample_id.get(sample.id) or receptions_by_code.get(code) production = productions.get(reception.id) if reception else None lab_code = (reception.lab_storage_code if reception else None) or "" extra = _pick_extra(extras, lab_code, code) protocol = protocols.get(sample.collection_protocol_id) if sample.collection_protocol_id else None check_flags = check_flags_by_reception.get(reception.id, {}) if reception else {} row = _build_row( dataset=dataset, sample=sample, type_name=(stype.type_name if stype else None), party=party, coll=coll, hospital=hospital, reception=reception, production=production, extra=extra, protocol=protocol, check_flags=check_flags, ) built_map[row.get("sampleCode") or code] = row # 单个核:业务已发生数据主要在科研捐献接收(lab_storage_code=UBMC*),无细胞储存协议样本时仍需展示 if dataset == DATASET_UBMC: ubmc_recs = ( db.query(BizSampleReception) .filter( BizSampleReception.tenant_no == tenant_no, BizSampleReception.del_flag == 0, BizSampleReception.lab_storage_code.like("UBMC%"), ) .order_by(BizSampleReception.reception_time.desc()) .all() ) mother_by_donation: Dict[str, str] = {} donation_ids = sorted( {r.research_donation_id for r in ubmc_recs if r.research_donation_id} ) if donation_ids: rows = db.execute( text( "SELECT research_donation_id, mother_name FROM biz_research_donation_party " "WHERE del_flag=0 AND tenant_no=:tenant_no AND research_donation_id IN (" + ",".join(f":d{i}" for i in range(len(donation_ids))) + ")" ), { "tenant_no": tenant_no, **{f"d{i}": did for i, did in enumerate(donation_ids)}, }, ).fetchall() for did, mother in rows: if did and mother and did not in mother_by_donation: mother_by_donation[str(did)] = mother missing_rids = [ r.id for r in ubmc_recs if r.id and r.id not in productions ] if missing_rids: for p in ( db.query(BizSampleProductionReception) .filter( BizSampleProductionReception.tenant_no == tenant_no, BizSampleProductionReception.del_flag == 0, BizSampleProductionReception.reception_id.in_(missing_rids), ) .order_by(BizSampleProductionReception.process_time.desc()) .all() ): productions.setdefault(p.reception_id, p) ubmc_flags = _check_flag_map( db, tenant_no, [r.id for r in ubmc_recs if r.id and r.id not in check_flags_by_reception], ) check_flags_by_reception.update(ubmc_flags) for reception in ubmc_recs: code = (reception.lab_storage_code or reception.sample_code or "").strip() if not code or code in built_map: continue production = productions.get(reception.id) extra = _pick_extra(extras, code, reception.sample_code) mother = mother_by_donation.get(str(reception.research_donation_id or "")) fake_sample = SimpleNamespace( id=reception.donation_sample_id or reception.id, sample_code=reception.sample_code or code, collection_protocol_id=None, ) fake_party = SimpleNamespace(mother_name=mother) if mother else None built_map[code] = _build_row( dataset=dataset, sample=fake_sample, type_name="脐血单个核细胞", party=fake_party, coll=None, hospital=None, reception=reception, production=production, extra=extra, protocol=None, check_flags=check_flags_by_reception.get(reception.id, {}), ) kw = (keyword or "").strip() built = [] for row in built_map.values(): if not _match_keyword(row, kw): continue if not _match_date_range(row, date_from, date_to): continue built.append(row) # 稳定排序:优先按样本编码 built.sort(key=lambda r: str(r.get("sampleCode") or "")) total = len(built) page = max(1, int(page or 1)) page_size = max(1, min(5000, int(page_size or 20))) start = (page - 1) * page_size return built[start : start + page_size], total def _build_row( *, dataset: str, sample: Optional[BizProtocolSample], type_name: Optional[str], party: Optional[BizProtocolPartyCustomer], coll: Optional[BizProtocolCollectionInfo], hospital: Optional[BizCollectionHospital], reception: Optional[BizSampleReception], production: Optional[BizSampleProductionReception], extra: Dict[str, Any], protocol: Optional[BizCollectionProtocol] = None, check_flags: Optional[Dict[str, bool]] = None, ) -> Dict[str, Any]: flags = check_flags or {} protocol_code = ( (sample.sample_code if sample else None) or extra.get("protocolSampleCode") or "" ) lab_code = ((reception.lab_storage_code if reception else None) or "").strip() # 台账「细胞编码」展示实验室入库编码(B000…),协议样本号保留检索 display_code = lab_code if lab_code and len(lab_code) > 1 else ( protocol_code or extra.get("sampleCode") or "" ) protocol_vals = { "sample_code": display_code, "mother_name": party.mother_name if party else None, "sample_collect_time": coll.sample_collect_time if coll else None, "hospital_name": hospital.hospital_name if hospital else None, } reception_vals = {} if reception: reception_vals = { "reception_time": reception.reception_time, "receive_temperature": reception.receive_temperature, "handler_name": reception.handler_name, "remark": reception.remark, "lab_storage_code": reception.lab_storage_code, } production_vals = {} if production: production_vals = { "original_blood_weight_g": production.original_blood_weight_g, "hes_volume_ml": production.hes_volume_ml, "blood_type": production.blood_type, "infectious_four_test_result": production.infectious_four_test_result, "enriched_blood_weight_g": production.enriched_blood_weight_g, "cryopreservation_volume_ml": production.cryopreservation_volume_ml, "handler_name": production.handler_name, "remark": production.remark, "original_blood_volume_ml": production.original_blood_volume_ml, "nucleated_cell_count_1e8": production.nucleated_cell_count_1e8, "inoculation_amount": production.inoculation_amount, "cryopreservation_time": production.cryopreservation_time, "storage_total_tubes": production.storage_total_tubes, "available_cell_count_1e7": production.available_cell_count_1e7, } data: Dict[str, Any] = { "id": (sample.id if sample else f"extra:{dataset}:{display_code}"), "dataset": dataset, "sampleCode": display_code, "protocolSampleCode": protocol_code or None, "sampleTypeName": type_name, "protocolSampleId": sample.id if sample else None, "collectionProtocolId": sample.collection_protocol_id if sample else None, "receptionId": reception.id if reception else None, "productionReceptionId": production.id if production else None, "fromExtraOnly": sample is None, } for f in fields_for(dataset): val = None if f.source == SOURCE_PROTOCOL: val = protocol_vals.get(f.attr) elif f.source == SOURCE_RECEPTION: val = reception_vals.get(f.attr) elif f.source == SOURCE_PRODUCTION: val = production_vals.get(f.attr) elif f.source == SOURCE_EXTRA: # 仅源系统没有的列走补充表;业务源表字段不被补充表覆盖 val = extra.get(f.key) if isinstance(val, datetime): val = _fmt_dt(val) elif isinstance(val, Decimal): val = _fmt_num(val) data[f.key] = val data["sampleCode"] = display_code # 从协议采集信息 / 附件 / 收样检查项推导补充列(补充表有值时不覆盖) if not data.get("hasMaternalBlood"): if coll is not None: data["hasMaternalBlood"] = "是" if coll.maternal_blood_collect_time else "否" if not data.get("hasConsent"): if protocol and (protocol.consent_form_urls or "").strip(): data["hasConsent"] = "是" else: flagged = _yes_no_from_bool(flags.get("知情同意书")) if flagged is not None: data["hasConsent"] = flagged elif protocol is not None: data["hasConsent"] = "否" if not data.get("hasHealthSurvey"): if protocol and (protocol.health_survey_form_urls or "").strip(): data["hasHealthSurvey"] = "是" else: flagged = _yes_no_from_bool(flags.get("健康调查表")) if flagged is not None: data["hasHealthSurvey"] = flagged elif protocol is not None: data["hasHealthSurvey"] = "否" if not data.get("labelClear"): data["labelClear"] = _yes_no_from_bool(flags.get("样本信息正确,标识清晰")) if not data.get("receiveCheck"): data["receiveCheck"] = _derive_receive_check(flags) if data.get("infectiousFourCard") is not None: data["infectiousFourCard"] = _infectious_label(data.get("infectiousFourCard")) return data def upsert_extra_payload( db: Session, *, tenant_no: str, dataset: str, sample_code: str, payload: Dict[str, Any], operator_id: str = "import", replace: bool = False, ) -> None: from app.models.models import BizLabSampleProcessingExtra code = (sample_code or "").strip() if not code: raise ValueError("样本编码不能为空") now = datetime.now() row = ( db.query(BizLabSampleProcessingExtra) .filter( BizLabSampleProcessingExtra.tenant_no == tenant_no, BizLabSampleProcessingExtra.dataset == dataset, BizLabSampleProcessingExtra.sample_code == code, BizLabSampleProcessingExtra.del_flag == 0, ) .first() ) current = {} if replace else _parse_extra(row.payload_json if row else None) for k, v in (payload or {}).items(): if v in (None, ""): continue current[k] = v current["sampleCode"] = code if row is None: db.add( BizLabSampleProcessingExtra( id=_new_id(), dataset=dataset, sample_code=code, payload_json=json.dumps(current, ensure_ascii=False), tenant_no=tenant_no, create_by=operator_id, create_dept_id="", create_time=now, update_by=operator_id, update_time=now, del_flag=0, ) ) else: row.payload_json = json.dumps(current, ensure_ascii=False) row.update_by = operator_id row.update_time = now def update_row( db: Session, *, tenant_no: str, dataset: str, sample_code: str, payload: Dict[str, Any], editable_depts: set, operator_id: str, ) -> Dict[str, Any]: code = (sample_code or "").strip() if not code: raise ValueError("样本编码不能为空") fmap = field_map(dataset) if not fmap: raise ValueError("未知数据集") denied = [] accepted: Dict[str, Any] = {} for key, value in (payload or {}).items(): f = fmap.get(key) if not f: continue if f.dept not in editable_depts: denied.append(key) continue accepted[key] = value if denied: raise PermissionError(f"无权编辑字段: {', '.join(denied)}") if not accepted: raise ValueError("没有可保存的字段") reception = ( db.query(BizSampleReception) .filter( BizSampleReception.tenant_no == tenant_no, BizSampleReception.del_flag == 0, or_( BizSampleReception.lab_storage_code == code, BizSampleReception.sample_code == code, ), ) .order_by(BizSampleReception.reception_time.desc()) .first() ) sample = ( db.query(BizProtocolSample) .filter( BizProtocolSample.tenant_no == tenant_no, BizProtocolSample.sample_code == code, BizProtocolSample.del_flag == 0, ) .first() ) if sample is None and reception is not None and reception.protocol_sample_id: sample = ( db.query(BizProtocolSample) .filter( BizProtocolSample.tenant_no == tenant_no, BizProtocolSample.id == reception.protocol_sample_id, BizProtocolSample.del_flag == 0, ) .first() ) if reception is None and sample is not None: reception = ( db.query(BizSampleReception) .filter( BizSampleReception.tenant_no == tenant_no, BizSampleReception.del_flag == 0, or_( BizSampleReception.protocol_sample_id == sample.id, BizSampleReception.sample_code == sample.sample_code, ), ) .order_by(BizSampleReception.reception_time.desc()) .first() ) production = None if reception: production = ( db.query(BizSampleProductionReception) .filter( BizSampleProductionReception.tenant_no == tenant_no, BizSampleProductionReception.reception_id == reception.id, BizSampleProductionReception.del_flag == 0, ) .order_by(BizSampleProductionReception.process_time.desc()) .first() ) party = None coll = None if sample: party = ( db.query(BizProtocolPartyCustomer) .filter( BizProtocolPartyCustomer.collection_protocol_id == sample.collection_protocol_id, BizProtocolPartyCustomer.tenant_no == tenant_no, BizProtocolPartyCustomer.del_flag == 0, ) .first() ) coll = ( db.query(BizProtocolCollectionInfo) .filter( BizProtocolCollectionInfo.collection_protocol_id == sample.collection_protocol_id, BizProtocolCollectionInfo.tenant_no == tenant_no, BizProtocolCollectionInfo.del_flag == 0, ) .first() ) now = datetime.now() extra_payload: Dict[str, Any] = {} def _set_dt(obj, attr, value): if value in (None, ""): setattr(obj, attr, None) return if isinstance(value, datetime): setattr(obj, attr, value) return text = str(value).strip().replace("/", "-").replace(".", "-") for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d"): try: setattr(obj, attr, datetime.strptime(text, fmt)) return except ValueError: continue setattr(obj, attr, value) for key, value in accepted.items(): f = fmap[key] wrote = False if f.source == SOURCE_PROTOCOL and sample is not None: if f.attr == "mother_name" and party is not None: party.mother_name = None if value in (None, "") else str(value) party.update_by = operator_id party.update_time = now wrote = True elif f.attr == "sample_collect_time" and coll is not None: _set_dt(coll, "sample_collect_time", value) coll.update_by = operator_id coll.update_time = now wrote = True elif f.source == SOURCE_RECEPTION and reception is not None: if f.attr == "reception_time": _set_dt(reception, f.attr, value) elif f.attr in {"receive_temperature", "handler_name", "remark", "lab_storage_code"}: setattr(reception, f.attr, None if value in (None, "") else value) reception.update_by = operator_id reception.update_time = now wrote = True elif f.source == SOURCE_PRODUCTION and production is not None: numeric_attrs = { "original_blood_weight_g", "hes_volume_ml", "enriched_blood_weight_g", "cryopreservation_volume_ml", "original_blood_volume_ml", "nucleated_cell_count_1e8", "available_cell_count_1e7", } if f.attr == "cryopreservation_time": _set_dt(production, f.attr, value) elif f.attr == "storage_total_tubes": production.storage_total_tubes = None if value in (None, "") else int(value) elif f.attr in numeric_attrs: production_val = None if value in (None, "") else Decimal(str(value)) setattr(production, f.attr, production_val) elif f.attr == "infectious_four_test_result": production.infectious_four_test_result = ( None if value in (None, "") else _infectious_code(value) ) elif f.attr in { "blood_type", "handler_name", "remark", "inoculation_amount", }: setattr(production, f.attr, None if value in (None, "") else value) production.update_by = operator_id production.update_time = now wrote = True # 补充表始终保留一份,保证导入台账与无权源表场景可编辑 extra_payload[key] = value _ = wrote upsert_extra_payload( db, tenant_no=tenant_no, dataset=dataset, sample_code=code, payload=extra_payload, operator_id=operator_id, replace=False, ) db.commit() rows, _ = list_rows(db, tenant_no=tenant_no, dataset=dataset, keyword=code, page=1, page_size=20) for row in rows: if row.get("sampleCode") == code or row.get("protocolSampleCode") == code: return row raise LookupError("保存成功但读取失败") def export_rows( db: Session, *, tenant_no: str, dataset: str, keyword: Optional[str] = None, date_from: Optional[str] = None, date_to: Optional[str] = None, ) -> Tuple[List[str], List[List[Any]]]: rows, _ = list_rows( db, tenant_no=tenant_no, dataset=dataset, keyword=keyword, date_from=date_from, date_to=date_to, page=1, page_size=5000, ) headers = [f.label for f in fields_for(dataset)] keys = [f.key for f in fields_for(dataset)] body = [[row.get(k) for k in keys] for row in rows] return headers, body