added kraken data to details.
This commit is contained in:
32
alembic/versions/785bb1140878_added_user_tracking.py
Normal file
32
alembic/versions/785bb1140878_added_user_tracking.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"""added user tracking
|
||||||
|
|
||||||
|
Revision ID: 785bb1140878
|
||||||
|
Revises: 178203610c3b
|
||||||
|
Create Date: 2023-02-06 09:54:20.371117
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '785bb1140878'
|
||||||
|
down_revision = '178203610c3b'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('_submissions', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('uploaded_by', sa.String(length=32), nullable=True))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('_submissions', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('uploaded_by')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# __init__.py
|
# __init__.py
|
||||||
|
|
||||||
# Version of the realpython-reader package
|
# Version of the realpython-reader package
|
||||||
__version__ = "1.2.1"
|
__version__ = "1.2.2"
|
||||||
|
|||||||
@@ -341,6 +341,12 @@ def submissions_to_df(ctx:dict, type:str|None=None) -> pd.DataFrame:
|
|||||||
# pass to lookup function
|
# pass to lookup function
|
||||||
subs = [item.to_dict() for item in lookup_all_submissions_by_type(ctx=ctx, type=type)]
|
subs = [item.to_dict() for item in lookup_all_submissions_by_type(ctx=ctx, type=type)]
|
||||||
df = pd.DataFrame.from_records(subs)
|
df = pd.DataFrame.from_records(subs)
|
||||||
|
# logger.debug(f"Pre: {df['Technician']}")
|
||||||
|
try:
|
||||||
|
df = df.drop("controls", axis=1)
|
||||||
|
except:
|
||||||
|
logger.warning(f"Couldn't drop 'controls' column from submissionsheet df.")
|
||||||
|
# logger.debug(f"Post: {df['Technician']}")
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
from . import Base
|
from . import Base
|
||||||
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey
|
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
import logging
|
||||||
|
from operator import itemgetter
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
class ControlType(Base):
|
class ControlType(Base):
|
||||||
"""
|
"""
|
||||||
@@ -35,3 +40,24 @@ class Control(Base):
|
|||||||
submission_id = Column(INTEGER, ForeignKey("_submissions.id")) #: parent submission id
|
submission_id = Column(INTEGER, ForeignKey("_submissions.id")) #: parent submission id
|
||||||
submission = relationship("BacterialCulture", back_populates="controls", foreign_keys=[submission_id]) #: parent submission
|
submission = relationship("BacterialCulture", back_populates="controls", foreign_keys=[submission_id]) #: parent submission
|
||||||
|
|
||||||
|
|
||||||
|
def to_sub_dict(self):
|
||||||
|
kraken = json.loads(self.kraken)
|
||||||
|
kraken_cnt_total = sum([kraken[item]['kraken_count'] for item in kraken])
|
||||||
|
new_kraken = []
|
||||||
|
for item in kraken:
|
||||||
|
kraken_percent = kraken[item]['kraken_count'] / kraken_cnt_total
|
||||||
|
new_kraken.append({'name': item, 'kraken_count':kraken[item]['kraken_count'], 'kraken_percent':"{0:.0%}".format(kraken_percent)})
|
||||||
|
new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True)
|
||||||
|
if self.controltype.targets == []:
|
||||||
|
targets = ["None"]
|
||||||
|
else:
|
||||||
|
targets = self.controltype.targets
|
||||||
|
output = {
|
||||||
|
"name" : self.name,
|
||||||
|
"type" : self.controltype.name,
|
||||||
|
"targets" : " ,".join(targets),
|
||||||
|
"kraken" : new_kraken[0:5]
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ from . import Base
|
|||||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, Table, JSON, FLOAT
|
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, Table, JSON, FLOAT
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
# table containing reagents/submission relationships
|
# table containing reagents/submission relationships
|
||||||
reagents_submissions = Table("_reagents_submissions", Base.metadata, Column("reagent_id", INTEGER, ForeignKey("_reagents.id")), Column("submission_id", INTEGER, ForeignKey("_submissions.id")))
|
reagents_submissions = Table("_reagents_submissions", Base.metadata, Column("reagent_id", INTEGER, ForeignKey("_reagents.id")), Column("submission_id", INTEGER, ForeignKey("_submissions.id")))
|
||||||
@@ -28,6 +31,7 @@ class BasicSubmission(Base):
|
|||||||
reagents_id = Column(String, ForeignKey("_reagents.id", ondelete="SET NULL", name="fk_BS_reagents_id")) #: id of used reagents
|
reagents_id = Column(String, ForeignKey("_reagents.id", ondelete="SET NULL", name="fk_BS_reagents_id")) #: id of used reagents
|
||||||
extraction_info = Column(JSON) #: unstructured output from the extraction table logger.
|
extraction_info = Column(JSON) #: unstructured output from the extraction table logger.
|
||||||
run_cost = Column(FLOAT(2))
|
run_cost = Column(FLOAT(2))
|
||||||
|
uploaded_by = Column(String(32))
|
||||||
|
|
||||||
__mapper_args__ = {
|
__mapper_args__ = {
|
||||||
"polymorphic_identity": "basic_submission",
|
"polymorphic_identity": "basic_submission",
|
||||||
@@ -77,6 +81,7 @@ class BasicSubmission(Base):
|
|||||||
"Technician": self.technician,
|
"Technician": self.technician,
|
||||||
"Cost": self.run_cost,
|
"Cost": self.run_cost,
|
||||||
}
|
}
|
||||||
|
logger.debug(f"{self.rsl_plate_num} technician: {output['Technician']}")
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
@@ -107,6 +112,7 @@ class BasicSubmission(Base):
|
|||||||
# cost = self.extraction_kit.cost_per_run
|
# cost = self.extraction_kit.cost_per_run
|
||||||
# except AttributeError:
|
# except AttributeError:
|
||||||
# cost = None
|
# cost = None
|
||||||
|
|
||||||
output = {
|
output = {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"Plate Number": self.rsl_plate_num,
|
"Plate Number": self.rsl_plate_num,
|
||||||
@@ -131,6 +137,13 @@ class BacterialCulture(BasicSubmission):
|
|||||||
samples = relationship("BCSample", back_populates="rsl_plate", uselist=True)
|
samples = relationship("BCSample", back_populates="rsl_plate", uselist=True)
|
||||||
# bc_sample_id = Column(INTEGER, ForeignKey("_bc_samples.id", ondelete="SET NULL", name="fk_BC_sample_id"))
|
# bc_sample_id = Column(INTEGER, ForeignKey("_bc_samples.id", ondelete="SET NULL", name="fk_BC_sample_id"))
|
||||||
__mapper_args__ = {"polymorphic_identity": "bacterial_culture", "polymorphic_load": "inline"}
|
__mapper_args__ = {"polymorphic_identity": "bacterial_culture", "polymorphic_load": "inline"}
|
||||||
|
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
output = super().to_dict()
|
||||||
|
output['controls'] = [item.to_sub_dict() for item in self.controls]
|
||||||
|
# logger.debug(f"{self.rsl_plate_num} technician: {output}")
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
class Wastewater(BasicSubmission):
|
class Wastewater(BasicSubmission):
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ def convert_data_list_to_df(ctx:dict, input:list[dict], subtype:str|None=None) -
|
|||||||
"""
|
"""
|
||||||
df = DataFrame.from_records(input)
|
df = DataFrame.from_records(input)
|
||||||
safe = ['name', 'submitted_date', 'genus', 'target']
|
safe = ['name', 'submitted_date', 'genus', 'target']
|
||||||
logger.debug(df)
|
# logger.debug(df)
|
||||||
for column in df.columns:
|
for column in df.columns:
|
||||||
if "percent" in column:
|
if "percent" in column:
|
||||||
count_col = [item for item in df.columns if "count" in item][0]
|
count_col = [item for item in df.columns if "count" in item][0]
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import numpy
|
|||||||
from frontend.custom_widgets import AddReagentQuestion, AddReagentForm, SubmissionsSheet, ReportDatePicker, KitAdder, ControlsDatePicker, OverwriteSubQuestion
|
from frontend.custom_widgets import AddReagentQuestion, AddReagentForm, SubmissionsSheet, ReportDatePicker, KitAdder, ControlsDatePicker, OverwriteSubQuestion
|
||||||
import logging
|
import logging
|
||||||
import difflib
|
import difflib
|
||||||
|
from getpass import getuser
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from frontend.visualizations.charts import create_charts
|
from frontend.visualizations.charts import create_charts
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ class App(QMainWindow):
|
|||||||
self._createMenuBar()
|
self._createMenuBar()
|
||||||
self._createToolBar()
|
self._createToolBar()
|
||||||
self._connectActions()
|
self._connectActions()
|
||||||
self.controls_getter()
|
self._controls_getter()
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
@@ -109,10 +110,10 @@ class App(QMainWindow):
|
|||||||
self.addReagentAction.triggered.connect(self.add_reagent)
|
self.addReagentAction.triggered.connect(self.add_reagent)
|
||||||
self.generateReportAction.triggered.connect(self.generateReport)
|
self.generateReportAction.triggered.connect(self.generateReport)
|
||||||
self.addKitAction.triggered.connect(self.add_kit)
|
self.addKitAction.triggered.connect(self.add_kit)
|
||||||
self.table_widget.control_typer.currentIndexChanged.connect(self.controls_getter)
|
self.table_widget.control_typer.currentIndexChanged.connect(self._controls_getter)
|
||||||
self.table_widget.mode_typer.currentIndexChanged.connect(self.controls_getter)
|
self.table_widget.mode_typer.currentIndexChanged.connect(self._controls_getter)
|
||||||
self.table_widget.datepicker.start_date.dateChanged.connect(self.controls_getter)
|
self.table_widget.datepicker.start_date.dateChanged.connect(self._controls_getter)
|
||||||
self.table_widget.datepicker.end_date.dateChanged.connect(self.controls_getter)
|
self.table_widget.datepicker.end_date.dateChanged.connect(self._controls_getter)
|
||||||
|
|
||||||
|
|
||||||
def importSubmission(self):
|
def importSubmission(self):
|
||||||
@@ -268,6 +269,7 @@ class App(QMainWindow):
|
|||||||
# logger.debug(info)
|
# logger.debug(info)
|
||||||
# move samples into preliminary submission dict
|
# move samples into preliminary submission dict
|
||||||
info['samples'] = self.samples
|
info['samples'] = self.samples
|
||||||
|
info['uploaded_by'] = getuser()
|
||||||
# construct submission object
|
# construct submission object
|
||||||
logger.debug(f"Here is the info_dict: {pprint.pformat(info)}")
|
logger.debug(f"Here is the info_dict: {pprint.pformat(info)}")
|
||||||
base_submission, output = construct_submission_info(ctx=self.ctx, info_dict=info)
|
base_submission, output = construct_submission_info(ctx=self.ctx, info_dict=info)
|
||||||
@@ -445,7 +447,7 @@ class App(QMainWindow):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def controls_getter(self):
|
def _controls_getter(self):
|
||||||
"""
|
"""
|
||||||
Lookup controls from database and send to chartmaker
|
Lookup controls from database and send to chartmaker
|
||||||
"""
|
"""
|
||||||
@@ -476,14 +478,14 @@ class App(QMainWindow):
|
|||||||
with QSignalBlocker(self.table_widget.sub_typer) as blocker:
|
with QSignalBlocker(self.table_widget.sub_typer) as blocker:
|
||||||
self.table_widget.sub_typer.addItems(sub_types)
|
self.table_widget.sub_typer.addItems(sub_types)
|
||||||
self.table_widget.sub_typer.setEnabled(True)
|
self.table_widget.sub_typer.setEnabled(True)
|
||||||
self.table_widget.sub_typer.currentTextChanged.connect(self.chart_maker)
|
self.table_widget.sub_typer.currentTextChanged.connect(self._chart_maker)
|
||||||
else:
|
else:
|
||||||
self.table_widget.sub_typer.clear()
|
self.table_widget.sub_typer.clear()
|
||||||
self.table_widget.sub_typer.setEnabled(False)
|
self.table_widget.sub_typer.setEnabled(False)
|
||||||
self.chart_maker()
|
self._chart_maker()
|
||||||
|
|
||||||
|
|
||||||
def chart_maker(self):
|
def _chart_maker(self):
|
||||||
"""
|
"""
|
||||||
Creates plotly charts for webview
|
Creates plotly charts for webview
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ from PyQt6.QtWidgets import (
|
|||||||
QTextEdit, QSizePolicy, QWidget,
|
QTextEdit, QSizePolicy, QWidget,
|
||||||
QGridLayout, QPushButton, QSpinBox,
|
QGridLayout, QPushButton, QSpinBox,
|
||||||
QScrollBar, QScrollArea, QHBoxLayout,
|
QScrollBar, QScrollArea, QHBoxLayout,
|
||||||
QMessageBox
|
QMessageBox, QFileDialog, QToolBar
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QDate, QAbstractTableModel, QSize
|
from PyQt6.QtCore import Qt, QDate, QAbstractTableModel, QSize
|
||||||
from PyQt6.QtGui import QFontMetrics
|
from PyQt6.QtGui import QFontMetrics, QAction
|
||||||
|
|
||||||
from backend.db import get_all_reagenttype_names, submissions_to_df, lookup_submission_by_id, lookup_all_sample_types, create_kit_from_yaml
|
from backend.db import get_all_reagenttype_names, submissions_to_df, lookup_submission_by_id, lookup_all_sample_types, create_kit_from_yaml
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
from xhtml2pdf import pisa
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
@@ -212,23 +212,25 @@ class SubmissionDetails(QDialog):
|
|||||||
def __init__(self, ctx:dict, id:int) -> None:
|
def __init__(self, ctx:dict, id:int) -> None:
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.ctx = ctx
|
||||||
self.setWindowTitle("Submission Details")
|
self.setWindowTitle("Submission Details")
|
||||||
|
|
||||||
# create scrollable interior
|
# create scrollable interior
|
||||||
interior = QScrollArea()
|
interior = QScrollArea()
|
||||||
interior.setParent(self)
|
interior.setParent(self)
|
||||||
# get submision from db
|
# get submision from db
|
||||||
data = lookup_submission_by_id(ctx=ctx, id=id)
|
data = lookup_submission_by_id(ctx=ctx, id=id)
|
||||||
base_dict = data.to_dict()
|
self.base_dict = data.to_dict()
|
||||||
|
logger.debug(f"Base dict: {self.base_dict}")
|
||||||
# don't want id
|
# don't want id
|
||||||
del base_dict['id']
|
del self.base_dict['id']
|
||||||
# convert sub objects to dicts
|
# convert sub objects to dicts
|
||||||
base_dict['reagents'] = [item.to_sub_dict() for item in data.reagents]
|
self.base_dict['reagents'] = [item.to_sub_dict() for item in data.reagents]
|
||||||
base_dict['samples'] = [item.to_sub_dict() for item in data.samples]
|
self.base_dict['samples'] = [item.to_sub_dict() for item in data.samples]
|
||||||
# retrieve jinja template
|
# retrieve jinja template
|
||||||
template = env.get_template("submission_details.txt")
|
template = env.get_template("submission_details.txt")
|
||||||
# render using object dict
|
# render using object dict
|
||||||
text = template.render(sub=base_dict)
|
text = template.render(sub=self.base_dict)
|
||||||
# create text field
|
# create text field
|
||||||
txt_editor = QTextEdit(self)
|
txt_editor = QTextEdit(self)
|
||||||
txt_editor.setReadOnly(True)
|
txt_editor.setReadOnly(True)
|
||||||
@@ -247,7 +249,37 @@ class SubmissionDetails(QDialog):
|
|||||||
interior.setWidget(txt_editor)
|
interior.setWidget(txt_editor)
|
||||||
self.layout = QVBoxLayout()
|
self.layout = QVBoxLayout()
|
||||||
self.setFixedSize(w, 900)
|
self.setFixedSize(w, 900)
|
||||||
self.layout.addWidget(interior)
|
btn = QPushButton("Export PDF")
|
||||||
|
btn.setParent(self)
|
||||||
|
btn.setFixedWidth(w)
|
||||||
|
btn.clicked.connect(self.export)
|
||||||
|
|
||||||
|
|
||||||
|
# def _create_actions(self):
|
||||||
|
# self.exportAction = QAction("Export", self)
|
||||||
|
|
||||||
|
|
||||||
|
def export(self):
|
||||||
|
template = env.get_template("submission_details.html")
|
||||||
|
html = template.render(sub=self.base_dict)
|
||||||
|
# logger.debug(f"Submission details: {self.base_dict}")
|
||||||
|
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
|
||||||
|
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0])
|
||||||
|
# logger.debug(f"report output name: {fname}")
|
||||||
|
# df.to_excel(fname, engine='openpyxl')
|
||||||
|
if fname.__str__() == ".":
|
||||||
|
logger.debug("Saving pdf was cancelled.")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with open(fname, "w+b") as f:
|
||||||
|
pisa.CreatePDF(html, dest=f)
|
||||||
|
except PermissionError as e:
|
||||||
|
logger.error(f"Error saving pdf: {e}")
|
||||||
|
msg = QMessageBox()
|
||||||
|
msg.setText("Permission Error")
|
||||||
|
msg.setInformativeText(f"Looks like {fname.__str__()} is open.\nPlease close it and try again.")
|
||||||
|
msg.setWindowTitle("Permission Error")
|
||||||
|
msg.exec()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ def generic_figure_markers(fig:Figure, modes:list=[], ytitle:str|None=None) -> F
|
|||||||
])
|
])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.debug(f"Returning figure {fig}")
|
# logger.debug(f"Returning figure {fig}")
|
||||||
assert type(fig) == Figure
|
assert type(fig) == Figure
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|||||||
32
src/submissions/templates/submission_details.html
Normal file
32
src/submissions/templates/submission_details.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Submission Details for {{ sub['Plate Number'] }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2><u>Submission Details for {{ sub['Plate Number'] }}</u></h2>
|
||||||
|
{% for key, value in sub.items() if key != 'reagents' and key != 'samples' and key != 'controls' %}
|
||||||
|
{% if key=='Cost' %} <p>{{ key }}: {{ "${:,.2f}".format(value) }}</p> {% else %} <p>{{ key }}: {{ value }}</p> {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<h3><u>Reagents:</u></h3>
|
||||||
|
{% for item in sub['reagents'] %}
|
||||||
|
<p>{{ item['type'] }}: {{ item['lot'] }} (EXP: {{ item['expiry'] }})</p>
|
||||||
|
{% endfor %}
|
||||||
|
<h3><u>Samples:</u></h3>
|
||||||
|
{% for item in sub['samples'] %}
|
||||||
|
<p>{{ item['well'] }}: {{ item['name'] }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% if sub['controls'] %}
|
||||||
|
<h3><u>Attached Controls:</u></h3>
|
||||||
|
{% for item in sub['controls'] %}
|
||||||
|
<p><b>{{ item['name'] }}:</b> {{ item['type'] }} (Targets: {{ item['targets'] }})</p>
|
||||||
|
{% if item['kraken'] %}
|
||||||
|
<p>{{ item['name'] }} Top 5 Kraken Results</p>
|
||||||
|
{% for genera in item['kraken'] %}
|
||||||
|
<p>{{ genera['name'] }}: {{ genera['kraken_count'] }} ({{ genera['kraken_percent'] }})</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{# template for constructing submission details #}
|
{# template for constructing submission details #}
|
||||||
|
|
||||||
{% for key, value in sub.items() if key != 'reagents' and key != 'samples' %}
|
{% for key, value in sub.items() if key != 'reagents' and key != 'samples' and key != 'controls' %}
|
||||||
{% if key=='Cost' %} {{ key }}: {{ "${:,.2f}".format(value) }} {% else %} {{ key }}: {{ value }} {% endif %}
|
{% if key=='Cost' %} {{ key }}: {{ "${:,.2f}".format(value) }} {% else %} {{ key }}: {{ value }} {% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
Reagents:
|
Reagents:
|
||||||
@@ -10,4 +10,14 @@ Reagents:
|
|||||||
Samples:
|
Samples:
|
||||||
{% for item in sub['samples'] %}
|
{% for item in sub['samples'] %}
|
||||||
{{ item['well'] }}: {{ item['name'] }}
|
{{ item['well'] }}: {{ item['name'] }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if sub['controls'] %}
|
||||||
|
Attached Controls:
|
||||||
|
{% for item in sub['controls'] %}
|
||||||
|
{{ item['name'] }}: {{ item['type'] }} (Targets: {{ item['targets'] }})
|
||||||
|
{% if item['kraken'] %}
|
||||||
|
{{ item['name'] }} Top 5 Kraken Results
|
||||||
|
{% for genera in item['kraken'] %}
|
||||||
|
{{ genera['name'] }}: {{ genera['kraken_count'] }} ({{ genera['kraken_percent'] }}){% endfor %}{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
Reference in New Issue
Block a user