felicity-lims/felicity/apps/analysis/utils.py

428 lines
17 KiB
Python
Raw Normal View History

import logging
from datetime import datetime
2024-07-24 04:30:01 +08:00
from typing import List
2023-12-20 02:01:46 +08:00
from sqlalchemy import or_
from felicity.apps.analysis import schemas
2024-07-24 04:30:01 +08:00
from felicity.apps.analysis.entities.analysis import SampleType
2024-07-28 03:52:31 +08:00
from felicity.apps.analysis.entities.results import (AnalysisResult,
result_verification)
2024-07-29 04:19:12 +08:00
from felicity.apps.analysis.enum import SampleState
2024-07-28 03:52:31 +08:00
from felicity.apps.analysis.services.analysis import (AnalysisService,
ProfileService,
SampleService,
SampleTypeService)
from felicity.apps.analysis.services.result import (AnalysisResultService,
ResultMutationService)
2024-07-29 04:19:12 +08:00
from felicity.apps.analysis.workflow.analysis_result import AnalysisResultWorkFlow
from felicity.apps.analysis.workflow.sample import SampleWorkFlow
2024-07-24 04:30:01 +08:00
from felicity.apps.billing.enum import DiscountType, DiscountValueType
2024-02-16 23:48:19 +08:00
from felicity.apps.billing.schemas import (AnalysisDiscountCreate,
AnalysisPriceCreate,
ProfileDiscountCreate,
ProfilePriceCreate)
2024-07-28 03:52:31 +08:00
from felicity.apps.billing.services import (AnalysisDiscountService,
AnalysisPriceService,
ProfileDiscountService,
ProfilePriceService)
from felicity.apps.job.enum import (JobAction, JobCategory, JobPriority,
JobState)
from felicity.apps.job.schemas import JobCreate
2024-07-28 03:52:31 +08:00
from felicity.apps.job.services import JobService
2024-07-24 04:30:01 +08:00
from felicity.apps.reflex.services import ReflexEngineService
2024-07-28 03:52:31 +08:00
from felicity.apps.shipment.services import ShippedSampleService
2024-07-24 04:30:01 +08:00
from felicity.apps.user.entities import User
from felicity.apps.user.services import UserService
2024-07-29 04:19:12 +08:00
from felicity.apps.worksheet.workflow import WorkSheetWorkFlow
2024-07-28 03:52:31 +08:00
from felicity.utils import has_value_or_is_truthy
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
2024-07-24 04:30:01 +08:00
QC_SAMPLE = {"name": "QC Sample", "description": "QC Sample", "abbr": "QCS"}
2024-07-28 03:52:31 +08:00
2024-04-12 23:14:48 +08:00
async def get_qc_sample_type() -> SampleType:
2024-07-24 04:30:01 +08:00
st_service = SampleTypeService()
st = await st_service.get(name=QC_SAMPLE.get("name"))
if not st:
2024-06-30 14:58:51 +08:00
st_in = schemas.SampleTypeCreate(**QC_SAMPLE)
2024-07-24 04:30:01 +08:00
st = await st_service.create(st_in)
return st
2024-04-12 23:14:48 +08:00
async def get_last_verificator(result_uid: str) -> User | None:
2024-07-24 04:30:01 +08:00
ar_service = AnalysisResultService()
user_service = UserService()
data = await ar_service.repository.query_table(
2023-09-11 13:02:05 +08:00
table=result_verification, result_uid=result_uid
)
if not data:
return None
2024-07-24 04:30:01 +08:00
return await user_service.get(uid=data[-1])
2023-09-11 13:02:05 +08:00
2024-07-28 03:52:31 +08:00
async def sample_search(
model, status: str, text: str, client_uid: str
) -> list[SampleType]:
"""No pagination"""
2024-07-24 04:30:01 +08:00
sample_service = SampleService()
filters = []
_or_text_ = {}
if has_value_or_is_truthy(text):
arg_list = [
"sample_id__ilike",
"analysis_request___patient___first_name__ilike",
"analysis_request___patient___last_name__ilike",
"analysis_request___patient___client_patient_id__ilike",
"analysis_request___client_request_id__ilike",
]
for _arg in arg_list:
_or_text_[_arg] = f"%{text}%"
text_filters = {or_: _or_text_}
filters.append(text_filters)
if client_uid:
filters.append({"analysis_request___client_uid__exact": client_uid})
if status:
filters.append({"status__exact": status})
filters.append({"internal_use__ne": True})
2024-07-24 04:30:01 +08:00
return await sample_service.filter(filters=filters, sort_attrs=["uid"])
2024-07-28 03:52:31 +08:00
async def retest_from_result_uids(
uids: list[str], user: User
) -> tuple[list[AnalysisResult], list[AnalysisResult]]:
2024-07-24 04:30:01 +08:00
analysis_result_service = AnalysisResultService()
2024-07-29 04:19:12 +08:00
analysis_result_wf = AnalysisResultWorkFlow()
2024-07-24 04:30:01 +08:00
2024-04-12 23:14:48 +08:00
originals: list[AnalysisResult] = []
retests: list[AnalysisResult] = []
for _ar_uid in uids:
2024-07-24 04:30:01 +08:00
a_result = await analysis_result_service.get(uid=_ar_uid)
if not a_result:
raise Exception(f"AnalysisResult with uid {_ar_uid} not found")
2024-07-29 04:19:12 +08:00
_retest, a_result = await analysis_result_wf.retest(
2024-07-28 03:52:31 +08:00
a_result.uid,
2024-07-29 05:29:12 +08:00
retested_by=user, action="verify"
)
if _retest:
retests.append(_retest)
originals.append(a_result)
return retests, originals
2024-07-28 03:52:31 +08:00
async def results_submitter(
analysis_results: List[dict], submitter: User
) -> list[AnalysisResult]:
2024-07-24 04:30:01 +08:00
analysis_result_service = AnalysisResultService()
2024-07-29 04:19:12 +08:00
sample_wf = SampleWorkFlow()
worksheet_wf = WorkSheetWorkFlow()
analysis_result_wf = AnalysisResultWorkFlow()
2024-07-24 04:30:01 +08:00
2024-04-12 23:14:48 +08:00
return_results: list[AnalysisResult] = []
2024-07-29 04:19:12 +08:00
_skipped, _submitted = await analysis_result_wf.submit(
analysis_results, submitter
)
return_results.extend(_skipped)
2024-07-29 05:29:12 +08:00
for a_result in _submitted:
# mutate result
await result_mutator(a_result)
# try to submit sample
2024-07-29 04:19:12 +08:00
try:
await sample_wf.submit(a_result.sample_uid, submitted_by=submitter)
except Exception as e:
await sample_wf.revert(a_result.sample_uid, by_uid=submitter.uid)
logger.warning(e)
# try to submit associated worksheet
2024-07-29 04:19:12 +08:00
if a_result.worksheet_uid:
try:
await worksheet_wf.submit(a_result.worksheet_uid, submitter=submitter)
except Exception as e:
await worksheet_wf.revert(a_result.worksheet_uid, by_uid=submitter.uid)
logger.warning(e)
return_results.append(a_result)
return return_results
2024-04-12 23:14:48 +08:00
async def verify_from_result_uids(uids: list[str], user: User) -> list[AnalysisResult]:
2024-07-24 04:30:01 +08:00
job_service = JobService()
sample_service = SampleService()
shipped_sample_service = ShippedSampleService()
2024-07-29 04:19:12 +08:00
worksheet_wf = WorkSheetWorkFlow()
analysis_result_wf = AnalysisResultWorkFlow()
sample_wf = SampleWorkFlow()
2024-07-24 04:30:01 +08:00
2024-07-29 04:19:12 +08:00
approved = await analysis_result_wf.approve(uids, user)
2024-07-29 04:19:12 +08:00
for a_result in approved:
2024-02-21 19:41:44 +08:00
# Do Reflex Testing
logger.info(f"ReflexUtil .... running")
2024-07-24 04:30:01 +08:00
await ReflexEngineService(analysis_result=a_result, user=user).do_reflex()
2024-02-21 19:41:44 +08:00
logger.info(f"ReflexUtil .... done")
# try to verify associated sample
2024-07-29 04:19:12 +08:00
sample_verified=False
try:
sample_verified, _ = await sample_wf.approve([a_result.sample_uid], submitted_by=user)
except Exception as e:
await sample_wf.revert(a_result.sample_uid, by_uid=user.uid)
logger.warning(e)
# try to submit associated worksheet
if a_result.worksheet_uid:
2024-07-29 04:19:12 +08:00
try:
await worksheet_wf.approve(a_result.worksheet_uid, verified_by=user)
except Exception as e:
await worksheet_wf.revert(a_result.worksheet_uid, by_uid=user.uid)
logger.warning(e)
2024-06-28 03:47:02 +08:00
# If referral then send results and mark sample as published
2024-07-24 04:30:01 +08:00
shipped = await shipped_sample_service.get(sample_uid=a_result.sample_uid)
2024-07-29 04:19:12 +08:00
# TODO: decouple this and fire events that will trigger the shipment etc
2023-09-11 13:02:05 +08:00
if shipped:
# 1. create a Job to send the result
job_schema = JobCreate(
2024-07-24 04:30:01 +08:00
action=JobAction.SHIPPED_REPORT,
category=JobCategory.SHIPMENT,
priority=JobPriority.MEDIUM,
job_id=shipped.uid,
2024-07-24 04:30:01 +08:00
status=JobState.PENDING,
creator_uid=user.uid,
2023-09-11 13:02:05 +08:00
data={"target": "result", "uid": a_result.uid},
)
2024-07-24 04:30:01 +08:00
await job_service.create(job_schema)
2024-06-28 03:47:02 +08:00
# 2. if sample is verified, then all results are verified, send all samples
if sample_verified:
# 1. create a Job to send results
job_schema = JobCreate(
2024-07-24 04:30:01 +08:00
action=JobAction.SHIPPED_REPORT,
category=JobCategory.SHIPMENT,
priority=JobPriority.MEDIUM,
job_id=shipped.uid,
2024-07-24 04:30:01 +08:00
status=JobState.PENDING,
creator_uid=user.uid,
2023-09-11 13:02:05 +08:00
data={"target": "sample", "uid": a_result.sample_uid},
)
2024-07-24 04:30:01 +08:00
await job_service.create(job_schema)
# 2. mark sample as published
2024-07-28 03:52:31 +08:00
await sample_service.change_status(
a_result.sample_uid, SampleState.PUBLISHED
)
2024-07-29 04:19:12 +08:00
return approved
2024-07-24 04:30:01 +08:00
async def result_mutator(result: AnalysisResult) -> None:
result_mutation_service = ResultMutationService()
2024-07-28 03:52:31 +08:00
analysis_result_service = AnalysisResultService()
2024-07-24 04:30:01 +08:00
result_in = result.result
correction_factors = result.analysis.correction_factors
specifications = result.analysis.specifications
detection_limits = result.analysis.detection_limits
uncertainties = result.analysis.uncertainties
if isinstance(result.result, int):
# Correction factor
for cf in correction_factors:
if (
2024-07-28 03:52:31 +08:00
cf.instrument_uid == result.laboratory_instrument_uid
and cf.method_uid == result.method_uid
):
2024-07-24 04:30:01 +08:00
await result_mutation_service.create(
obj_in={
"result_uid": result.uid,
"before": result.result,
"after": result.result * cf.factor,
"mutation": f"Multiplied the result {result.result} with a correction factor of {cf.factor}",
"date": datetime.now(),
}
)
result.result = result.result * cf.factor
# Specifications: Take more priority than DL
for spec in specifications:
# Min
if result.result < spec.min_warn:
2024-07-24 04:30:01 +08:00
await result_mutation_service.create(
obj_in={
"result_uid": result.uid,
"before": result.result,
"after": spec.min_report,
"mutation": f"Result was less than the minimun warning specification {spec.min_warn} and must be reported as {spec.min_report}",
"date": datetime.now(),
}
)
result.result = spec.min_report
elif result.result < spec.min:
result.result = result.result
# Max
if result.result > spec.max_warn:
2024-07-24 04:30:01 +08:00
await result_mutation_service.create(
obj_in={
"result_uid": result.uid,
"before": result.result,
"after": spec.max_report,
"mutation": f"Result was greater than the maximun warning specification {spec.max_warn} and must be reported as {spec.max_report}",
"date": datetime.now(),
}
)
result.result = spec.max_report
elif result.result > spec.max:
result.result = result.result
# Detection Limit Check
for dlim in detection_limits:
if result.result < dlim.lower_limit:
2024-07-24 04:30:01 +08:00
await result_mutation_service.create(
obj_in={
"result_uid": result.uid,
"before": result.result,
"after": f"< {dlim.lower_limit}",
"mutation": f"Result fell below the Lower Detection Limit {dlim.lower_limit} and must be reported as < {dlim.lower_limit}",
"date": datetime.now(),
}
)
result.result = f"< {dlim.lower_limit}"
if result.result > dlim.upper_limit:
2024-07-24 04:30:01 +08:00
await result_mutation_service.create(
obj_in={
"result_uid": result.uid,
"before": result.result,
"after": f"> {dlim.upper_limit}",
"mutation": f"Result fell Above the Upper Detection Limit {dlim.upper_limit} and must be reported as > {dlim.upper_limit}",
"date": datetime.now(),
}
)
result.result = f"> {dlim.upper_limit}"
# uncertainty
if isinstance(result.result, int):
for uncert in uncertainties:
if uncert.min <= result.result <= uncert.max:
2024-07-24 04:30:01 +08:00
await result_mutation_service.create(
obj_in={
"result_uid": result.uid,
"before": result.result,
"after": f"{result.result} +/- {uncert.value}",
"mutation": f"Result fell inside the range [{uncert.min},{uncert.max}] with an un uncertainty of +/- {uncert.value}",
"date": datetime.now(),
}
)
result.result = f"{result.result} +/- {uncert.value}"
elif isinstance(result.result, str):
for spec in specifications:
if result.result in spec.warn_values.split(","):
2024-07-24 04:30:01 +08:00
await result_mutation_service.create(
obj_in={
"result_uid": result.uid,
"before": result.result,
"after": spec.warn_report,
"mutation": f"Result with specification (result ::equals:: {result.result}) must be reported as {spec.warn_report}",
"date": datetime.now(),
}
)
result.result = spec.warn_report
if result_in != result.result:
2024-07-28 03:52:31 +08:00
result = await analysis_result_service.save(result)
2023-12-01 04:51:40 +08:00
2024-07-24 04:30:01 +08:00
async def billing_setup_profiles(profile_uids: list[str] = None) -> None:
profile_service = ProfileService()
profile_price_service = ProfilePriceService()
profile_discount_service = ProfileDiscountService()
2024-07-28 03:52:31 +08:00
2023-12-01 04:51:40 +08:00
if profile_uids:
2024-07-24 04:30:01 +08:00
profiles = await profile_service.get_by_uids(profile_uids)
2023-12-01 04:51:40 +08:00
else:
2024-07-26 15:29:24 +08:00
profiles = await profile_service.all()
2023-12-20 02:01:46 +08:00
2023-12-01 04:51:40 +08:00
for profile in profiles:
2024-07-24 17:04:53 +08:00
exists = await profile_price_service.get(profile_uid=profile.uid)
2023-12-01 04:51:40 +08:00
if not exists:
2024-07-24 04:30:01 +08:00
await profile_price_service.create(
ProfilePriceCreate(
**{"profile_uid": profile.uid, "amount": 0.0, "is_active": True}
)
)
2023-12-20 02:01:46 +08:00
2024-07-24 17:04:53 +08:00
exists = await profile_discount_service.get(profile_uid=profile.uid)
2023-12-04 18:04:30 +08:00
if not exists:
2024-07-24 04:30:01 +08:00
await profile_discount_service.create(
ProfileDiscountCreate(
**{
"name": profile.name + "-Discount",
"profile_uid": profile.uid,
"discount_type": DiscountType.SALE,
"value_type": DiscountValueType.PERCENTATE,
"value_percent": 0.0,
"value_amount": 0.0,
"is_active": False,
}
)
)
2023-12-04 18:04:30 +08:00
2023-12-01 04:51:40 +08:00
2024-07-24 04:30:01 +08:00
async def billing_setup_analysis(analysis_uids: list[str] = None) -> None:
analysis_service = AnalysisService()
analysis_price_service = AnalysisPriceService()
analysis_discount_service = AnalysisDiscountService()
2023-12-01 04:51:40 +08:00
if analysis_uids:
2024-07-24 04:30:01 +08:00
analyses = await analysis_service.get_by_uids(analysis_uids)
2023-12-01 04:51:40 +08:00
else:
2024-07-26 15:29:24 +08:00
analyses = await analysis_service.all()
2023-12-20 02:01:46 +08:00
2023-12-01 04:51:40 +08:00
for analysis in analyses:
2024-07-24 17:04:53 +08:00
exists = await analysis_price_service.get(analysis_uid=analysis.uid)
2023-12-01 04:51:40 +08:00
if not exists:
2024-07-24 04:30:01 +08:00
await analysis_price_service.create(
AnalysisPriceCreate(
**{"analysis_uid": analysis.uid, "amount": 0.0, "is_active": True}
)
)
2023-12-20 02:01:46 +08:00
2024-07-24 17:04:53 +08:00
exists = await analysis_discount_service.get(analysis_uid=analysis.uid)
2023-12-04 18:04:30 +08:00
if not exists:
2024-07-24 04:30:01 +08:00
await analysis_discount_service.create(
AnalysisDiscountCreate(
**{
"name": analysis.name + "-Discount",
"analysis_uid": analysis.uid,
"discount_type": DiscountType.SALE,
"value_type": DiscountValueType.PERCENTATE,
"value_percent": 0.0,
"value_amount": 0.0,
"is_active": False,
}
)
)