-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor QaCase into CrfCase and RequisitionCase
- Loading branch information
Showing
15 changed files
with
473 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from .qa_case import QaCase, QaCaseError | ||
from .crf_case import CrfCase, CrfCaseError | ||
from .crf_subquery import CrfSubquery | ||
from .requisition_case import RequisitionCase | ||
from .requisition_subquery import RequisitionSubquery | ||
from .sql_view_generator import SqlViewGenerator | ||
from .subquery import Subquery | ||
from .subquery_from_dict import subquery_from_dict |
23 changes: 10 additions & 13 deletions
23
edc_qareports/sql_generator/qa_case.py → edc_qareports/sql_generator/crf_case.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,40 @@ | ||
from dataclasses import dataclass, field | ||
|
||
import sqlglot | ||
from django.apps import apps as django_apps | ||
from django.db import OperationalError, connection | ||
|
||
from .subquery_from_dict import subquery_from_dict | ||
from .crf_subquery import CrfSubquery | ||
|
||
|
||
class QaCaseError(Exception): | ||
class CrfCaseError(Exception): | ||
pass | ||
|
||
|
||
@dataclass(kw_only=True) | ||
class QaCase: | ||
class CrfCase: | ||
label: str = None | ||
dbtable: str = None | ||
label_lower: str = None | ||
fld_name: str | None = None | ||
where: str | None = None | ||
list_tables: list[tuple[str, str, str]] | None = field(default_factory=list) | ||
|
||
def __post_init__(self): | ||
if self.fld_name is None and self.where is None: | ||
raise QaCaseError("Expected either 'fld_name' or 'where'. Got None for both.") | ||
elif self.fld_name is not None and self.where is not None: | ||
raise QaCaseError("Expected either 'fld_name' or 'where', not both.") | ||
subjectvisit_dbtable: str | None = None | ||
|
||
@property | ||
def sql(self): | ||
return subquery_from_dict([self.__dict__]) | ||
sql = CrfSubquery(**self.__dict__).sql | ||
vendor = "postgres" if connection.vendor.startswith("postgres") else connection.vendor | ||
return sqlglot.transpile(sql, read="mysql", write=vendor)[0] | ||
|
||
@property | ||
def model_cls(self): | ||
return django_apps.get_model(self.label_lower) | ||
|
||
def fetchall(self): | ||
sql = subquery_from_dict([self.__dict__]) | ||
with connection.cursor() as cursor: | ||
try: | ||
cursor.execute(sql) | ||
cursor.execute(self.sql) | ||
except OperationalError as e: | ||
raise QaCaseError(f"{e}. See {self}.") | ||
raise CrfCaseError(f"{e}. See {self}.") | ||
return cursor.fetchall() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
from dataclasses import dataclass, field | ||
from string import Template | ||
|
||
|
||
class CrfSubqueryError(Exception): | ||
pass | ||
|
||
|
||
@dataclass(kw_only=True) | ||
class CrfSubquery: | ||
label: str = None | ||
label_lower: str = None | ||
dbtable: str = None | ||
fld_name: str | None = None | ||
subjectvisit_dbtable: str | None = None | ||
where: str | None = None | ||
list_tables: list[tuple[str, str, str]] | None = field(default_factory=list) | ||
template: Template = field( | ||
init=False, | ||
default=Template( | ||
"select v.subject_identifier, crf.id as original_id, crf.subject_visit_id, " | ||
"crf.report_datetime, crf.site_id, v.visit_code, " | ||
"v.visit_code_sequence, v.schedule_name, crf.modified, " | ||
"'${label_lower}' as label_lower, " | ||
"'${label}' as label, count(*) as records " | ||
"from ${dbtable} as crf " | ||
"left join ${subjectvisit_dbtable} as v on v.id=crf.subject_visit_id " | ||
"${left_joins} " | ||
"where ${where} " | ||
"group by v.subject_identifier, crf.subject_visit_id, crf.report_datetime, " | ||
"crf.site_id, v.visit_code, v.visit_code_sequence, v.schedule_name, crf.modified" | ||
), | ||
) | ||
|
||
def __post_init__(self): | ||
# default where statement if not provided and have fld_name. | ||
if self.where is None and self.fld_name: | ||
self.where = f"crf.{self.fld_name} is null" | ||
if not self.label_lower: | ||
raise CrfSubqueryError("label_lower is required") | ||
if not self.subjectvisit_dbtable: | ||
self.subjectvisit_dbtable = f"{self.label_lower.split('.')[0]}_subjectvisit" | ||
|
||
@property | ||
def left_joins(self) -> str: | ||
"""Add list tbls to access list cols by 'name' instead of 'id'""" | ||
left_join = [] | ||
for opts in self.list_tables or []: | ||
list_field, list_dbtable, alias = opts | ||
left_join.append( | ||
f"left join {list_dbtable} as {alias} on crf.{list_field}={alias}.id" | ||
) | ||
return " ".join(left_join) | ||
|
||
@property | ||
def sql(self): | ||
opts = {k: v for k, v in self.__dict__.items() if v is not None} | ||
opts.update(left_joins=self.left_joins) | ||
try: | ||
sql = self.template.substitute(**opts).replace(";", "") | ||
except KeyError as e: | ||
raise CrfSubqueryError(e) | ||
return sql |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from dataclasses import dataclass | ||
|
||
from .crf_case import CrfCase | ||
from .requisition_subquery import RequisitionSubquery | ||
|
||
|
||
@dataclass(kw_only=True) | ||
class RequisitionCase(CrfCase): | ||
panel: str = None | ||
subjectrequisition_dbtable: str | None = None | ||
panel_dbtable: str | None = None | ||
|
||
@property | ||
def sql(self): | ||
sql = RequisitionSubquery(**self.__dict__).sql | ||
return sql.format(panel=self.panel) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
from dataclasses import dataclass, field | ||
from string import Template | ||
|
||
from edc_constants.constants import YES | ||
|
||
from .crf_subquery import CrfSubquery | ||
|
||
|
||
class RequisitionSubqueryError(Exception): | ||
pass | ||
|
||
|
||
@dataclass(kw_only=True) | ||
class RequisitionSubquery(CrfSubquery): | ||
"""Generate a SELECT query returning requisitions where | ||
is_drawn=Yes for a given panel but results have not been captured | ||
in the result CRF. | ||
For example requisition exists for panel FBC but results_fbc | ||
CRF does not exist. | ||
""" | ||
|
||
panel: str = None | ||
subjectrequisition_dbtable: str | None = None | ||
panel_dbtable: str | None = None | ||
template: str = field( | ||
init=False, | ||
default=Template( | ||
"select req.subject_identifier, req.id as original_id, req.subject_visit_id, " | ||
"req.report_datetime, req.site_id, v.visit_code, v.visit_code_sequence, " | ||
"v.schedule_name, req.modified, '${label_lower}' as label_lower, " | ||
"'${label}' as label, count(*) as records " | ||
"from ${subjectrequisition_dbtable} as req " | ||
"left join ${dbtable} as crf on req.id=crf.requisition_id " | ||
"left join ${subjectvisit_dbtable} as v on v.id=req.subject_visit_id " | ||
"${left_joins} " | ||
"left join ${panel_dbtable} as panel on req.panel_id=panel.id " | ||
f"where panel.name='${{panel}}' and req.is_drawn='{YES}' and crf.id is null " | ||
"group by req.id, req.subject_identifier, req.subject_visit_id, " | ||
"req.report_datetime, req.site_id, v.visit_code, v.visit_code_sequence, " | ||
"v.schedule_name, req.modified" | ||
), | ||
) | ||
|
||
def __post_init__(self): | ||
# default where statement if not provided and have fld_name. | ||
if self.where is None and self.fld_name: | ||
self.where = f"crf.{self.fld_name} is null" | ||
if not self.label_lower: | ||
raise RequisitionSubqueryError("label_lower is required") | ||
if not self.subjectvisit_dbtable: | ||
self.subjectvisit_dbtable = f"{self.label_lower.split('.')[0]}_subjectvisit" | ||
if not self.subjectrequisition_dbtable: | ||
self.subjectrequisition_dbtable = ( | ||
f"{self.label_lower.split('.')[0]}_subjectrequisition" | ||
) | ||
if not self.panel_dbtable: | ||
self.panel_dbtable = "edc_lab_panel" |
Oops, something went wrong.