mirror of
https://github.com/beak-insights/felicity-lims.git
synced 2025-02-24 17:02:55 +08:00
291 lines
12 KiB
Python
291 lines
12 KiB
Python
import logging
|
|
from datetime import datetime
|
|
from typing import List
|
|
|
|
from felicity.apps.abstract.service import BaseService
|
|
from felicity.apps.analysis.entities.analysis import Analysis, Sample
|
|
from felicity.apps.analysis.entities.results import AnalysisResult
|
|
from felicity.apps.analysis.enum import ResultState
|
|
from felicity.apps.analysis.schemas import (AnalysisResultCreate,
|
|
AnalysisResultUpdate)
|
|
from felicity.apps.analysis.services.result import AnalysisResultService
|
|
from felicity.apps.common.utils.serializer import marshaller
|
|
from felicity.apps.reflex.repository import (ReflexActionRepository,
|
|
ReflexBrainAdditionRepository,
|
|
ReflexBrainCriteriaRepository,
|
|
ReflexBrainFinalRepository,
|
|
ReflexBrainRepository,
|
|
ReflexRuleRepository)
|
|
from felicity.apps.reflex.schemas import (ReflexAction, ReflexActionCreate,
|
|
ReflexActionUpdate, ReflexBrain,
|
|
ReflexBrainAddition,
|
|
ReflexBrainAdditionCreate,
|
|
ReflexBrainAdditionUpdate,
|
|
ReflexBrainCreate,
|
|
ReflexBrainCriteria,
|
|
ReflexBrainCriteriaCreate,
|
|
ReflexBrainCriteriaUpdate,
|
|
ReflexBrainFinal,
|
|
ReflexBrainFinalCreate,
|
|
ReflexBrainFinalUpdate,
|
|
ReflexBrainUpdate, ReflexRule,
|
|
ReflexRuleCreate, ReflexRuleUpdate)
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ReflexRuleService(BaseService[ReflexRule, ReflexRuleCreate, ReflexRuleUpdate]):
|
|
def __init__(self):
|
|
super().__init__(ReflexRuleRepository)
|
|
|
|
|
|
class ReflexBrainAdditionService(
|
|
BaseService[
|
|
ReflexBrainAddition, ReflexBrainAdditionCreate, ReflexBrainAdditionUpdate
|
|
]
|
|
):
|
|
def __init__(self):
|
|
super().__init__(ReflexBrainAdditionRepository)
|
|
|
|
|
|
class ReflexBrainFinalService(
|
|
BaseService[ReflexBrainFinal, ReflexBrainFinalCreate, ReflexBrainFinalUpdate]
|
|
):
|
|
def __init__(self):
|
|
super().__init__(ReflexBrainFinalRepository)
|
|
|
|
|
|
class ReflexBrainCriteriaService(
|
|
BaseService[
|
|
ReflexBrainCriteria, ReflexBrainCriteriaCreate, ReflexBrainCriteriaUpdate
|
|
]
|
|
):
|
|
def __init__(self):
|
|
super().__init__(ReflexBrainCriteriaRepository)
|
|
|
|
|
|
class ReflexBrainService(
|
|
BaseService[ReflexBrain, ReflexBrainCreate, ReflexBrainUpdate]
|
|
):
|
|
def __init__(self):
|
|
super().__init__(ReflexBrainRepository)
|
|
|
|
|
|
class ReflexActionService(
|
|
BaseService[ReflexAction, ReflexActionCreate, ReflexActionUpdate]
|
|
):
|
|
def __init__(self):
|
|
super().__init__(ReflexActionRepository)
|
|
|
|
|
|
class ReflexEngineService:
|
|
_siblings: List[AnalysisResult] = None
|
|
_cousins: List[AnalysisResult] = None
|
|
_results_pool: List[AnalysisResult] = None
|
|
_reflex_action: ReflexAction = None
|
|
|
|
def __init__(self, analysis_result, user):
|
|
self.analysis_result: AnalysisResult = analysis_result
|
|
self.sample: Sample = analysis_result.sample
|
|
self.analysis: Analysis = analysis_result.analysis
|
|
self.user = user
|
|
self.analysis_result_service = AnalysisResultService()
|
|
self.reflex_action_service = ReflexActionService()
|
|
|
|
# TODO: apply set_reflex_actions for already created analyses
|
|
|
|
async def set_reflex_actions(self, analysis_results: List[AnalysisResult]):
|
|
"""Prepares an analysis result for reflex testing"""
|
|
for result in analysis_results:
|
|
logger.debug(f"set_reflex_actions for : {result}")
|
|
filters = {"analyses___uid": result.analysis_uid, "level": 1}
|
|
action = await self.reflex_action_service.get(**filters)
|
|
if action:
|
|
result.reflex_level = 1
|
|
await self.analysis_result_service.update(
|
|
result.uid, marshaller(result)
|
|
)
|
|
logger.debug(f"set_reflex_actions done")
|
|
|
|
async def do_reflex(self):
|
|
if not self.analysis_result.reflex_level:
|
|
return
|
|
|
|
logger.info(
|
|
f"do_reflex level: {self.analysis_result.reflex_level} <> SampleId {self.sample.sample_id}"
|
|
)
|
|
|
|
filters = {
|
|
"analyses___uid": self.analysis.uid,
|
|
"level": self.analysis_result.reflex_level,
|
|
}
|
|
action = await self.reflex_action_service.get(**filters)
|
|
if not action:
|
|
logger.info(f"No reflex action found for analysis: {self.analysis.name}")
|
|
return
|
|
self._reflex_action = action
|
|
logger.info(f"Reflex action found for analysis: {self.analysis.name}")
|
|
logger.info(f"Reflex action description: {action.description}")
|
|
|
|
logger.info(f"Reflex Brains: {self._reflex_action.brains}")
|
|
for brain in self._reflex_action.brains:
|
|
await self.decide(brain)
|
|
|
|
@staticmethod
|
|
def can_decide(results_pool: list[AnalysisResult]):
|
|
"""If all results in consideration are approved then a decision can be made"""
|
|
if not results_pool:
|
|
return False
|
|
return all([r.status == ResultState.APPROVED for r in results_pool])
|
|
|
|
async def decide(self, brain: ReflexBrain):
|
|
logger.info(f"Reflex Decision for brain: {brain}")
|
|
|
|
if not brain.analyses_values:
|
|
return
|
|
logger.info(f"Reflex brain analyses_values: {brain.analyses_values}")
|
|
|
|
results_pool = await self.get_results_pool(brain.analyses_values)
|
|
logger.info(f"relevant results_pool: {[r.result for r in results_pool]}")
|
|
|
|
if not self.can_decide(results_pool):
|
|
logger.info(
|
|
f"A decision cannot be made -> aborting relex: {[r.status for r in results_pool]}"
|
|
)
|
|
return
|
|
|
|
# 1. criteria_values must match results_pool
|
|
criteria_values = frozenset(
|
|
[
|
|
(criteria.analysis_uid, criteria.value)
|
|
for criteria in brain.analyses_values
|
|
]
|
|
)
|
|
logger.info(f"criteria_values: {criteria_values}")
|
|
results_values = frozenset(
|
|
[(result.analysis_uid, result.result) for result in results_pool]
|
|
)
|
|
logger.info(f"results_values: {results_values}")
|
|
|
|
is_match = criteria_values == results_values
|
|
|
|
# 2. If brain criteria expectations are met then take action
|
|
if is_match:
|
|
logger.info("Perfect match yay!")
|
|
# Add new Analyses
|
|
logger.info(f"add_new... : {brain.add_new}")
|
|
for assoc in brain.add_new:
|
|
for i in list(range(assoc.count)):
|
|
await self.create_analyte_for(assoc.analysis_uid)
|
|
# Finalise Analyses
|
|
logger.info(f"finalise... : {brain.finalise}")
|
|
for final in brain.finalise:
|
|
await self.create_final_for(final.analysis.uid, final.value)
|
|
|
|
# clean up
|
|
for r in results_pool:
|
|
if r.reportable:
|
|
await r.hide_report()
|
|
else:
|
|
logger.info("No matches found :)")
|
|
|
|
async def get_results_pool(self, criteria: list[ReflexBrainCriteria]):
|
|
total_criteria = len(criteria)
|
|
criteria_anals = list(set([cr.analysis_uid for cr in criteria]))
|
|
logger.info(f"criteria_anals: {criteria_anals}")
|
|
|
|
if self._results_pool is None:
|
|
results: List[AnalysisResult] = await self.analysis_result_service.get_all(
|
|
sample_uid=self.sample.uid
|
|
)
|
|
self._results_pool = list(
|
|
filter(lambda result: result.analysis_uid in criteria_anals, results)
|
|
)
|
|
if len(self._results_pool) > 1:
|
|
self._results_pool = list(
|
|
filter(
|
|
lambda result: result.uid != self.analysis_result.uid,
|
|
self._results_pool,
|
|
)
|
|
)
|
|
|
|
logger.info(f"entire results_pool: {[r.result for r in self._results_pool]}")
|
|
self._results_pool.sort(key=lambda x: x.created_at, reverse=True)
|
|
return self._results_pool[:total_criteria]
|
|
|
|
# async def siblings(self, latest_n: int = None):
|
|
# """
|
|
# Siblings of the current analysis result
|
|
# Analysis Results from the same sample that share the same analysis
|
|
# """
|
|
# if self._siblings is None:
|
|
# analysis_uid = self.analysis.uid
|
|
# results: List[AnalysisResult] = await self.sample.get_analysis_results()
|
|
# # get siblings and exclude current analysis
|
|
# self._siblings = list(
|
|
# filter(
|
|
# lambda result: result.analysis_uid == analysis_uid
|
|
# and result.uid != self.analysis_result.uid,
|
|
# results,
|
|
# )
|
|
# )
|
|
#
|
|
# # Sort the siblings by created_at in descending order and get the first latest_n items
|
|
# self._siblings.sort(key=lambda x: x.created_at, reverse=True)
|
|
# if latest_n:
|
|
# return self._siblings[:latest_n]
|
|
# return self._siblings
|
|
#
|
|
# async def cousins(self, latest_n: int = None):
|
|
# """
|
|
# Cousins of the current analysis result
|
|
# Analysis Results from the same sample that do not share the current result's analysis
|
|
# """
|
|
# if self._cousins is None:
|
|
# analysis_uid = self.analysis.uid
|
|
# results: List[AnalysisResult] = await self.sample.get_analysis_results()
|
|
# self._cousins = list(
|
|
# filter(lambda result: result.analysis_uid != analysis_uid, results)
|
|
# )
|
|
#
|
|
# # Sort the cousins by created_at in descending order and get the first latest_n items
|
|
# self._cousins.sort(key=lambda x: x.created_at, reverse=True)
|
|
# if latest_n:
|
|
# return self._cousins[:latest_n]
|
|
# return self._cousins
|
|
|
|
async def create_analyte_for(self, analysis_uid) -> AnalysisResult:
|
|
logger.info(f"create_analyte_for: {analysis_uid}")
|
|
|
|
a_result_in = {
|
|
"sample_uid": self.sample.uid,
|
|
"analysis_uid": analysis_uid,
|
|
"status": ResultState.PENDING,
|
|
"laboratory_instrument_uid": self.analysis_result.laboratory_instrument_uid,
|
|
"method_uid": self.analysis_result.method_uid,
|
|
"parent_id": self.analysis_result.uid,
|
|
"retest": True,
|
|
"reflex_level": self.analysis_result.reflex_level + 1,
|
|
}
|
|
a_result_schema = AnalysisResultCreate(**a_result_in)
|
|
retest = await self.analysis_result_service.create(a_result_schema)
|
|
await self.analysis_result_service.hide_report(self.analysis_result.uid)
|
|
return retest
|
|
|
|
async def create_final_for(self, analysis_uid, value):
|
|
logger.info(f"create_final_for: {analysis_uid} {value}")
|
|
retest = await self.create_analyte_for(analysis_uid)
|
|
res_in = AnalysisResultUpdate(
|
|
result=value,
|
|
submitted_by_uid=self.user.uid,
|
|
date_submitted=datetime.now(),
|
|
verified_by_uid=self.user.uid,
|
|
date_verified=datetime.now(),
|
|
status=ResultState.APPROVED,
|
|
retest=False,
|
|
reportable=True,
|
|
reflex_level=None,
|
|
)
|
|
final = await self.analysis_result_service.update(retest.uid, res_in)
|
|
return final
|