felicity-lims/felicity/apps/analysis/utils.py
Aurthur Musendame ad14d5f12f iter 2 done
2024-07-23 22:30:01 +02:00

440 lines
18 KiB
Python

import logging
from datetime import datetime
from typing import List
from sqlalchemy import or_
from felicity.apps.analysis import schemas
from felicity.apps.analysis.enum import ResultState, SampleState
from felicity.apps.analysis.entities.analysis import SampleType
from felicity.apps.analysis.entities.results import (AnalysisResult, result_verification)
from felicity.apps.billing.enum import DiscountType, DiscountValueType
from felicity.apps.billing.schemas import (AnalysisDiscountCreate,
AnalysisPriceCreate,
ProfileDiscountCreate,
ProfilePriceCreate)
from felicity.apps.job.enum import JobPriority, JobState, JobCategory, JobAction
from felicity.apps.job.schemas import JobCreate
from felicity.apps.notification.services import ActivityStreamService
from felicity.apps.reflex.services import ReflexEngineService
from felicity.apps.user.entities import User
from felicity.utils import has_value_or_is_truthy
from felicity.apps.analysis.services.analysis import (AnalysisService, ProfileService,
SampleService, SampleTypeService)
from felicity.apps.analysis.services.result import AnalysisResultService, ResultMutationService
from felicity.apps.user.services import UserService
from felicity.apps.shipment.services import ShippedSampleService
from felicity.apps.worksheet.services import WorkSheetService
from felicity.apps.job.services import JobService
from felicity.apps.billing.services import (AnalysisDiscountService, AnalysisPriceService,
ProfileDiscountService, ProfilePriceService)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
QC_SAMPLE = {"name": "QC Sample", "description": "QC Sample", "abbr": "QCS"}
async def get_qc_sample_type() -> SampleType:
st_service = SampleTypeService()
st = await st_service.get(name=QC_SAMPLE.get("name"))
if not st:
st_in = schemas.SampleTypeCreate(**QC_SAMPLE)
st = await st_service.create(st_in)
return st
async def get_last_verificator(result_uid: str) -> User | None:
ar_service = AnalysisResultService()
user_service = UserService()
data = await ar_service.repository.query_table(
table=result_verification, result_uid=result_uid
)
if not data:
return None
return await user_service.get(uid=data[-1])
async def sample_search(model, status: str, text: str, client_uid: str) -> list[SampleType]:
"""No pagination"""
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})
return await sample_service.filter(filters=filters, sort_attrs=["uid"])
async def retest_from_result_uids(uids: list[str], user: User) -> tuple[list[AnalysisResult], list[AnalysisResult]]:
analysis_result_service = AnalysisResultService()
originals: list[AnalysisResult] = []
retests: list[AnalysisResult] = []
for _ar_uid in uids:
a_result = await analysis_result_service.get(uid=_ar_uid)
if not a_result:
raise Exception(f"AnalysisResult with uid {_ar_uid} not found")
_retest, a_result = await a_result.retest_result(
retested_by=user, next_action="verify"
)
if _retest:
retests.append(_retest)
originals.append(a_result)
return retests, originals
async def results_submitter(analysis_results: List[dict], submitter: User) -> list[AnalysisResult]:
analysis_result_service = AnalysisResultService()
activity_stream_service = ActivityStreamService()
return_results: list[AnalysisResult] = []
for _ar in analysis_results:
uid = _ar["uid"]
a_result = await analysis_result_service.get(uid=uid)
if not a_result:
raise Exception(f"AnalysisResult with uid {uid} not found")
# only submit results in pending/submitting state
if a_result.status not in [ResultState.PENDING, ResultState.SUBMITTING]:
return_results.append(a_result)
continue
analysis_result = a_result.to_dict(nested=False)
for field in analysis_result:
if field in _ar.keys():
try:
setattr(a_result, field, _ar.get(field, None))
except AttributeError as e:
logger.warning(e)
# No Empty Results
result = getattr(a_result, "result", None)
if not result or result.strip() == "" or len(result.strip()) == 0:
setattr(a_result, "result", None)
else:
setattr(a_result, "status", ResultState.RESULTED)
# set submitter ad date_submitted
setattr(a_result, "submitted_by_uid", submitter.uid)
setattr(a_result, "date_submitted", datetime.now())
# set updated_by
try:
setattr(a_result, "updated_by_uid", submitter.uid)
except AttributeError:
pass
a_result_in = schemas.AnalysisResultUpdate(**a_result.to_dict())
a_result = await analysis_result_service.update(a_result.uid, a_result_in)
# mutate result
await result_mutator(a_result)
if a_result.status == ResultState.RESULTED:
await activity_stream_service.stream(a_result, submitter, "submitted", "result")
# # Do Reflex Testing
# logger.info(f"ReflexUtil .... running")
# await ReflexUtil(analysis_result=a_result, user=submitter).do_reflex()
# logger.info(f"ReflexUtil .... done")
# try to submit sample
if a_result.sample:
await a_result.sample.submit(submitted_by=submitter)
# try to submit associated worksheet
if a_result.worksheet_uid:
await a_result.worksheet.submit(submitter=submitter)
return_results.append(a_result)
return return_results
async def verify_from_result_uids(uids: list[str], user: User) -> list[AnalysisResult]:
job_service = JobService()
analysis_result_service = AnalysisResultService()
sample_service = SampleService()
shipped_sample_service = ShippedSampleService()
worksheet_service = WorkSheetService()
to_return: list[AnalysisResult] = []
for _ar_uid in uids:
a_result = await analysis_result_service.get(uid=_ar_uid)
if not a_result:
raise Exception(f"AnalysisResult with uid {_ar_uid} not found")
# No Empty Results
status = getattr(a_result, "status", None)
if status in [ResultState.RESULTED, ResultState.APPROVING]:
_, a_result = await analysis_result_service.verify(a_result.uid, verifier_uid=user.uid)
to_return.append(a_result)
else:
continue
# Do Reflex Testing
logger.info(f"ReflexUtil .... running")
await ReflexEngineService(analysis_result=a_result, user=user).do_reflex()
logger.info(f"ReflexUtil .... done")
# try to verify associated sample
sample_verified = False
if a_result.sample:
sample_verified, _ = await sample_service.verify(a_result.sample.uid, verified_by=user)
# try to submit associated worksheet
if a_result.worksheet_uid:
await worksheet_service.verify(a_result.worksheet.uid, verified_by=user)
# If referral then send results and mark sample as published
shipped = await shipped_sample_service.get(sample_uid=a_result.sample_uid)
if shipped:
# 1. create a Job to send the result
job_schema = JobCreate(
action=JobAction.SHIPPED_REPORT,
category=JobCategory.SHIPMENT,
priority=JobPriority.MEDIUM,
job_id=shipped.uid,
status=JobState.PENDING,
creator_uid=user.uid,
data={"target": "result", "uid": a_result.uid},
)
await job_service.create(job_schema)
# 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(
action=JobAction.SHIPPED_REPORT,
category=JobCategory.SHIPMENT,
priority=JobPriority.MEDIUM,
job_id=shipped.uid,
status=JobState.PENDING,
creator_uid=user.uid,
data={"target": "sample", "uid": a_result.sample_uid},
)
await job_service.create(job_schema)
# 2. mark sample as published
await sample_service.change_status(a_result.sample.uid, SampleState.PUBLISHED)
return to_return
async def result_mutator(result: AnalysisResult) -> None:
result_mutation_service = ResultMutationService()
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 (
cf.instrument_uid == result.laboratory_instrument_uid
and cf.method_uid == result.method_uid
):
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:
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:
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:
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:
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:
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(","):
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:
result = await result.save_async()
async def billing_setup_profiles(profile_uids: list[str] = None) -> None:
profile_service = ProfileService()
profile_price_service = ProfilePriceService()
profile_discount_service = ProfileDiscountService()
if profile_uids:
profiles = await profile_service.get_by_uids(profile_uids)
else:
profiles = await profile_service.all_async()
for profile in profiles:
exists = await profile_price_service.get_one(profile_uid=profile.uid)
if not exists:
await profile_price_service.create(
ProfilePriceCreate(
**{"profile_uid": profile.uid, "amount": 0.0, "is_active": True}
)
)
exists = await profile_discount_service.get_one(profile_uid=profile.uid)
if not exists:
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,
}
)
)
async def billing_setup_analysis(analysis_uids: list[str] = None) -> None:
analysis_service = AnalysisService()
analysis_price_service = AnalysisPriceService()
analysis_discount_service = AnalysisDiscountService()
if analysis_uids:
analyses = await analysis_service.get_by_uids(analysis_uids)
else:
analyses = await analysis_service.all_async()
for analysis in analyses:
exists = await analysis_price_service.get_one(analysis_uid=analysis.uid)
if not exists:
await analysis_price_service.create(
AnalysisPriceCreate(
**{"analysis_uid": analysis.uid, "amount": 0.0, "is_active": True}
)
)
exists = await analysis_discount_service.get_one(analysis_uid=analysis.uid)
if not exists:
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,
}
)
)