diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ce4be..5975052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 202312.01 +- Control samples info now available in plate map. - Backups will now create an regenerated xlsx file. - Report generator now does sums automatically. diff --git a/TODO.md b/TODO.md index 47da8d1..98f9305 100644 --- a/TODO.md +++ b/TODO.md @@ -27,7 +27,7 @@ - Should be used when coming in to parser and when leaving form. NO OTHER PLACES. - [x] Change 'check_is_power_user' to decorator. - [x] Drag and drop files into submission form area? -- [ ] Get info for controls into their sample hitpicks. +- [x] Get info for controls into their sample hitpicks. - [x] Move submission-type specific parser functions into class methods in their respective models. - [x] Improve function results reporting. - Maybe make it a list until it gets to the reporter? diff --git a/alembic.ini b/alembic.ini index edfb698..b0c1125 100644 --- a/alembic.ini +++ b/alembic.ini @@ -57,7 +57,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne ; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db ; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-new.db -; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions-test.db +sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions-test.db [post_write_hooks] diff --git a/alembic/versions/2684f065037c_link_controls_with_bacterial_culture_.py b/alembic/versions/2684f065037c_link_controls_with_bacterial_culture_.py new file mode 100644 index 0000000..f6eb5a2 --- /dev/null +++ b/alembic/versions/2684f065037c_link_controls_with_bacterial_culture_.py @@ -0,0 +1,34 @@ +"""link controls with Bacterial Culture Samples + +Revision ID: 2684f065037c +Revises: 7e7b6eeca468 +Create Date: 2023-12-05 10:29:31.126732 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2684f065037c' +down_revision = '7e7b6eeca468' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('_control_samples', schema=None) as batch_op: + batch_op.add_column(sa.Column('sample_id', sa.INTEGER(), nullable=True)) + batch_op.create_foreign_key('cont_BCS_id', '_samples', ['sample_id'], ['id'], ondelete='SET NULL') + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('_control_samples', schema=None) as batch_op: + batch_op.drop_constraint('cont_BCS_id', type_='foreignkey') + batch_op.drop_column('sample_id') + + # ### end Alembic commands ### diff --git a/src/submissions/backend/db/models/controls.py b/src/submissions/backend/db/models/controls.py index 4754952..e4a0f83 100644 --- a/src/submissions/backend/db/models/controls.py +++ b/src/submissions/backend/db/models/controls.py @@ -71,6 +71,8 @@ class Control(BaseClass): refseq_version = Column(String(16)) #: version of refseq used in fastq parsing kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing kraken2_db_version = Column(String(32)) #: folder name of kraken2 db + sample = relationship("BacterialCultureSample", back_populates="control") + sample_id = Column(INTEGER, ForeignKey("_samples.id", ondelete="SET NULL", name="cont_BCS_id")) def __repr__(self) -> str: return f"" @@ -243,3 +245,8 @@ class Control(BaseClass): case _: pass return query_return(query=query, limit=limit) + + def save(self): + self.__database_session__.add(self) + self.__database_session__.commit() + diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index 68fd172..ed8dde3 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -58,7 +58,7 @@ class BasicSubmission(BaseClass): run_cost = Column(FLOAT(2)) #: total cost of running the plate. Set from constant and mutable kit costs at time of creation. uploaded_by = Column(String(32)) #: user name of person who submitted the submission to the database. comment = Column(JSON) #: user notes - submission_category = Column(String(64)) #: ["Research", "Diagnostic", "Surveillance"], else defaults to submission_type_name + submission_category = Column(String(64)) #: ["Research", "Diagnostic", "Surveillance", "Validation"], else defaults to submission_type_name submission_sample_associations = relationship( "SubmissionSampleAssociation", @@ -1483,6 +1483,7 @@ class BacterialCultureSample(BasicSample): # id = Column(INTEGER, ForeignKey('basicsample.id'), primary_key=True) organism = Column(String(64)) #: bacterial specimen concentration = Column(String(16)) #: sample concentration + control = relationship("Control", back_populates="sample", uselist=False) __mapper_args__ = {"polymorphic_identity": "Bacterial Culture Sample", "polymorphic_load": "inline"} def to_sub_dict(self, submission_rsl:str) -> dict: @@ -1496,6 +1497,13 @@ class BacterialCultureSample(BasicSample): sample['name'] = f"{self.submitter_id} - ({self.organism})" return sample + def to_hitpick(self, submission_rsl: str | None = None) -> dict | None: + sample = super().to_hitpick(submission_rsl) + if self.control != None: + sample['colour'] = [0,128,0] + sample['tooltip'] += f"
- Control: {self.control.controltype.name} - {self.control.controltype.targets}" + return sample + # Submission to Sample Associations class SubmissionSampleAssociation(BaseClass): diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index 7d41fe5..e429d9c 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -325,7 +325,7 @@ class PydSubmission(BaseModel, extra='allow'): @field_validator("submission_category") @classmethod def rescue_category(cls, value, values): - if value['value'] not in ["Research", "Diagnostic", "Surveillance"]: + if value['value'] not in ["Research", "Diagnostic", "Surveillance", "Validation"]: value['value'] = values.data['submission_type']['value'] return value diff --git a/src/submissions/frontend/visualizations/plate_map.py b/src/submissions/frontend/visualizations/plate_map.py index dec580f..085dfce 100644 --- a/src/submissions/frontend/visualizations/plate_map.py +++ b/src/submissions/frontend/visualizations/plate_map.py @@ -31,10 +31,14 @@ def make_plate_map(sample_list:list) -> Image: # Go through samples and change its row/column to red if positive, else blue for sample in sample_list: logger.debug(f"sample keys: {list(sample.keys())}") + # set color of square if sample['positive']: colour = [255,0,0] else: - colour = [0,0,255] + if 'colour' in sample.keys(): + colour = sample['colour'] + else: + colour = [0,0,255] grid[int(sample['row'])-1][int(sample['column'])-1] = colour # Create pixel image from the grid and enlarge img = Image.fromarray(grid).resize((1200, 800), resample=Image.NEAREST) @@ -99,7 +103,10 @@ def make_plate_map_html(sample_list:list, plate_rows:int=8, plate_columns=12) -> if sample['positive']: sample['background_color'] = "#f10f07" else: - sample['background_color'] = "#80cbc4" + if "colour" in sample.keys(): + sample['background_color'] = "#69d84f" + else: + sample['background_color'] = "#80cbc4" output_samples = [] for column in range(1, plate_columns+1): for row in range(1, plate_rows+1):