diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8cfc6b6..7cf9560 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,6 +1,8 @@
name: build
on:
push:
+ branches:
+ - main
tags:
- 'v*.*.*'
jobs:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9de222f..c8cf7ef 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,6 +9,21 @@ Changelog `_, and this project
adheres to `Semantic
Versioning `_.
+[v0.5.9 unreleased]
+-------------------
+
+In Progress
+======
+
+- a database interface
+
+[v0.5.8]
+--------
+
+Hotfix
+======
+
+- plot figure saving fixed
[v0.5.7]
--------
diff --git a/db/create.py b/db/create.py
new file mode 100644
index 0000000..8b1e0f0
--- /dev/null
+++ b/db/create.py
@@ -0,0 +1,92 @@
+import sqlite3
+from sqlite3 import Error
+
+PATH = r".\db\example.db"
+
+# flake8: noqa
+
+
+def create_connection():
+ """create a database connection to a database that resides
+ in the memory
+ """
+ conn = None
+ try:
+ conn = sqlite3.connect(PATH)
+ except Error as e:
+ print(e)
+ finally:
+ return conn
+
+
+def create_table(conn, create_table_sql):
+ """create a table from the create_table_sql statement
+ :param conn: Connection object
+ :param create_table_sql: a CREATE TABLE statement
+ :return:
+ """
+ try:
+ c = conn.cursor()
+ c.execute(create_table_sql)
+ except Error as e:
+ print(e)
+
+
+def main():
+ # trailing commas will throw a syntax error !?
+
+ sql_create_projects_table = """
+ CREATE TABLE IF NOT EXISTS projects (
+ id integer PRIMARY KEY,
+ name text NOT NULL
+ );
+ """
+
+ sql_create_tests_table = """
+ CREATE TABLE IF NOT EXISTS tests (
+ id integer PRIMARY KEY,
+ name text NOT NULL,
+ project_id integer NOT NULL,
+ FOREIGN KEY (project_id) REFERENCES projects (id)
+ );
+ """
+
+ sql_create_readings_table = """
+ CREATE TABLE IF NOT EXISTS readings (
+ id integer PRIMARY KEY,
+ name text NOT NULL,
+ pump_1 integer NOT NULL,
+ pump_2 integer NOT NULL,
+ test_id integer NOT NULL,
+ FOREIGN KEY (test_id) REFERENCES tests (id)
+ );
+ """
+
+ sql_create_groups_table = """
+ CREATE TABLE IF NOT EXISTS groups (
+ id integer NOT NULL,
+ listID NOT NULL,
+ name text NOT NULL,
+ )
+ """
+
+ # create a database connection
+ conn = create_connection()
+ # create tables
+ if conn is not None:
+ # create projects table
+ print("projects")
+ create_table(conn, sql_create_projects_table)
+
+ print("tasks")
+ # create tasks table
+ create_table(conn, sql_create_tests_table)
+ else:
+ print("Error! cannot create the database connection.")
+
+ conn.commit()
+ conn.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/db/sampledb.json b/db/sampledb.json
new file mode 100644
index 0000000..1a43b92
--- /dev/null
+++ b/db/sampledb.json
@@ -0,0 +1,42 @@
+{
+ "_default": {
+ "1": {
+ "uuid":"uuid",
+ "name": "project",
+ "customer": "customer",
+ "productionCo": "productionCo",
+ "submittedBy": "submittedBy",
+ "field": "field",
+ "sample": "sample",
+ "sampleDate": "sampleDate",
+ "recDate": "recDate",
+ "compDate": "compDate",
+ "analyst": "analyst",
+ "numbers": "numbers",
+ "notes": "notes",
+ "tests": [
+ {
+ "name": "testname",
+ "reportAs":"label",
+ "isBlank": true,
+ "chemical": "chemical",
+ "rate": 100,
+ "clarity": "clarity",
+ "toConsider": "toConsider",
+ "includeOnRep": "includeOnRep",
+ "obsBaseline": 75,
+ "notes": "notes",
+ "result": 100,
+ "readings":[
+ {
+ "average": 2,
+ "pump 1": 1,
+ "pump 2": 3,
+ "elapsedMin": "stamp"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/db/sampledb.yaml b/db/sampledb.yaml
new file mode 100644
index 0000000..dbea809
--- /dev/null
+++ b/db/sampledb.yaml
@@ -0,0 +1,33 @@
+---
+_default:
+ '1':
+ uuid: uuid
+ name: project
+ customer: customer
+ productionCo: productionCo
+ submittedBy: submittedBy
+ field: field
+ sample: sample
+ sampleDate: sampleDate
+ recDate: recDate
+ compDate: compDate
+ analyst: analyst
+ numbers: numbers
+ notes: notes
+ tests:
+ - name: testname
+ reportAs: label
+ isBlank: true
+ chemical: chemical
+ rate: 100
+ clarity: clarity
+ toConsider: toConsider
+ includeOnRep: includeOnRep
+ obsBaseline: 75
+ notes: notes
+ result: 100
+ readings:
+ - average: 2
+ pump 1: 1
+ pump 2: 3
+ elapsedMin: stamp
diff --git a/db/schema.sql b/db/schema.sql
new file mode 100644
index 0000000..a4ae05e
--- /dev/null
+++ b/db/schema.sql
@@ -0,0 +1,44 @@
+--
+-- File generated with SQLiteStudio v3.3.3 on Wed Jun 30 07:22:45 2021
+--
+-- Text encoding used: System
+--
+PRAGMA foreign_keys = off;
+BEGIN TRANSACTION;
+
+-- Table: projects
+CREATE TABLE projects (
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL
+);
+
+
+-- Table: readings
+CREATE TABLE readings (
+ id INTEGER PRIMARY KEY,
+ test_id INTEGER REFERENCES tests (id) ON DELETE CASCADE
+ ON UPDATE CASCADE
+ NOT NULL
+);
+
+
+-- Table: reports
+CREATE TABLE reports (
+ id INTEGER PRIMARY KEY,
+ test_id REFERENCES tests (id) ON DELETE CASCADE
+ ON UPDATE CASCADE
+);
+
+
+-- Table: tests
+CREATE TABLE tests (
+ id INTEGER PRIMARY KEY,
+ project_id REFERENCES projects (id) ON DELETE CASCADE
+ ON UPDATE CASCADE
+ NOT NULL,
+ name TEXT NOT NULL
+);
+
+
+COMMIT TRANSACTION;
+PRAGMA foreign_keys = on;
diff --git a/poetry.lock b/poetry.lock
index 3ebdd03..1284de4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -276,6 +276,14 @@ category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+[[package]]
+name = "tinydb"
+version = "4.4.0"
+description = "TinyDB is a tiny, document oriented database optimized for your happiness :)"
+category = "main"
+optional = false
+python-versions = ">=3.5,<4.0"
+
[[package]]
name = "tkcalendar"
version = "1.6.1"
@@ -332,7 +340,7 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "c8aea8aaf25674e2103327faab3031b697a230a0b36c8015cd08cbbb87a79d4e"
+content-hash = "3108fb9291bfae1f579f9170ddec544c2c3e314247ace01876975a82c8941338"
[metadata.files]
appdirs = [
@@ -583,6 +591,10 @@ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
+tinydb = [
+ {file = "tinydb-4.4.0-py3-none-any.whl", hash = "sha256:30b0f718ebb288e42d2f69f3e1b18928739f25153e6b5308a234e95c1673de71"},
+ {file = "tinydb-4.4.0.tar.gz", hash = "sha256:d57c29524ecacc081ebc24f96e0d787bba11dc20d52634a32a709b878be3545a"},
+]
tkcalendar = [
{file = "tkcalendar-1.6.1-py3-none-any.whl", hash = "sha256:9d3a80816a7b32d64fab696fa3d2a007fb23c87953267d5e343a38ff4cd7c15c"},
{file = "tkcalendar-1.6.1-py3.8.egg", hash = "sha256:c3ac34ab268734377ce73407893e8a5765e288aecbbb55136fb3ccea98006a96"},
diff --git a/pyproject.toml b/pyproject.toml
index 99f6089..6632ced 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,10 +1,11 @@
[tool.poetry]
name = "scalewiz"
-version = "0.5.7"
+version = "0.5.8"
description = "A graphical user interface for chemical performance testing designed to work with Teledyne SSI MX-class HPLC pumps."
readme = "README.rst"
-license = "GPL-3.0-or-later"
+license = "GPL-3.0"
authors = ["Alex Whittington "]
+repository = "https://github.com/teauxfu/scalewiz"
packages = [
{include = "scalewiz"}
]
@@ -29,6 +30,7 @@ pandas = "^1.2.2"
py-hplc = "^1.0.1"
tomlkit = "^0.7.0"
appdirs = "^1.4.4"
+tinydb = "^4.4.0"
[tool.poetry.scripts]
scalewiz = "scalewiz.__main__:main"
diff --git a/requirements.txt b/requirements.txt
index 13c0ae5..c839dca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -154,6 +154,9 @@ pytz==2021.1; python_full_version >= "3.7.1" \
six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7" \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926
+tinydb==4.4.0; python_version >= "3.5" and python_version < "4.0" \
+ --hash=sha256:30b0f718ebb288e42d2f69f3e1b18928739f25153e6b5308a234e95c1673de71 \
+ --hash=sha256:d57c29524ecacc081ebc24f96e0d787bba11dc20d52634a32a709b878be3545a
tkcalendar==1.6.1 \
--hash=sha256:9d3a80816a7b32d64fab696fa3d2a007fb23c87953267d5e343a38ff4cd7c15c \
--hash=sha256:c3ac34ab268734377ce73407893e8a5765e288aecbbb55136fb3ccea98006a96 \
diff --git a/scalewiz/__init__.py b/scalewiz/__init__.py
index e8cdd3e..87d3aca 100644
--- a/scalewiz/__init__.py
+++ b/scalewiz/__init__.py
@@ -4,6 +4,8 @@
from typing import TYPE_CHECKING
+from tinydb import TinyDB
+
if TYPE_CHECKING:
from tkinter import Tk
@@ -11,3 +13,4 @@
ROOT: Tk = None
CONFIG: dict = get_config()
+DATABASE: TinyDB = TinyDB(CONFIG["recents"]["database"])
diff --git a/scalewiz/components/evaluation_window.py b/scalewiz/components/evaluation_window.py
index 02ad3a0..17142ae 100644
--- a/scalewiz/components/evaluation_window.py
+++ b/scalewiz/components/evaluation_window.py
@@ -119,7 +119,7 @@ def save(self) -> None:
)
parent_dir = Path(self.editor_project.path.get()).parent
plot_output = Path(parent_dir, plot_output).resolve()
- self.plot_view.fig.savefig(plot_output)
+ self.plot_view.fig.savefig(str(plot_output))
self.editor_project.plot.set(str(plot_output))
# update log
log_output = (
diff --git a/scalewiz/helpers/configuration.py b/scalewiz/helpers/configuration.py
index 1e2d2cf..9694dcf 100644
--- a/scalewiz/helpers/configuration.py
+++ b/scalewiz/helpers/configuration.py
@@ -16,6 +16,18 @@
CONFIG_DIR = Path(user_config_dir("ScaleWiz", "teauxfu"))
CONFIG_FILE = Path(CONFIG_DIR, "config.toml")
+DATABASE_FILE = Path(CONFIG_DIR, "db.json")
+
+
+def ensure_database() -> None:
+ """Ensures a database file exists."""
+ # make sure we have a place to store data
+ if DATABASE_FILE.is_file():
+ LOGGER.info("Found an existing database file %s", DATABASE_FILE)
+ else:
+ LOGGER.info("No database file found. Making one now at %s", DATABASE_FILE)
+ with DATABASE_FILE.open("w") as dbfile:
+ dbfile.write("")
def ensure_config() -> None:
@@ -34,6 +46,8 @@ def ensure_config() -> None:
"No config file found in %s. Making one now at %s", CONFIG_DIR, CONFIG_FILE
)
init_config()
+ ensure_database()
+ update_config("recents", "database", str(DATABASE_FILE))
def init_config() -> None:
@@ -84,6 +98,7 @@ def generate_default() -> document:
recents = table()
recents["analyst"] = "teauxfu"
recents["project"] = ""
+ recents["database"] = str(DATABASE_FILE)
doc["recents"] = recents
doc["recents"].comment("these will get updated between user sessions")
@@ -124,7 +139,6 @@ def generate_default() -> document:
def open_config() -> None:
"""Opens the config file."""
- ensure_config()
if CONFIG_FILE.is_file():
os.startfile(CONFIG_FILE)
@@ -134,6 +148,8 @@ def get_config() -> dict[str, Union[float, int, str]]:
ensure_config()
with CONFIG_FILE.open("r") as file:
config = loads(file.read())
+ if "database" not in config["recents"]:
+ config["recents"]["database"] = str(DATABASE_FILE)
return config
@@ -145,7 +161,6 @@ def update_config(table: str, key: str, value: Union[float, int, str]) -> None:
key (str): the key to update
value (Union[float, int, str]): the new value of `key`
"""
- ensure_config()
doc = loads(CONFIG_FILE.open("r").read())
if table in doc.keys() and key in doc[table].keys():
doc[table][key] = value
diff --git a/scalewiz/helpers/score.py b/scalewiz/helpers/score.py
index 0f958f0..fb89363 100644
--- a/scalewiz/helpers/score.py
+++ b/scalewiz/helpers/score.py
@@ -100,7 +100,7 @@ def score(project: Project, log_widget: ScrolledText = None, *args) -> None:
f"Result: 1 - ({int_psi} - {baseline_area}) / {avg_protectable_area}"
)
log.append(f"Result: {result} \n")
- trial.result.set(f"{result:.2f}")
+ trial.result.set(result)
if isinstance(log_widget, tk.Text):
to_log(log, log_widget)
diff --git a/scalewiz/models/project.py b/scalewiz/models/project.py
index a9fdafa..054aadc 100644
--- a/scalewiz/models/project.py
+++ b/scalewiz/models/project.py
@@ -7,6 +7,7 @@
import tkinter as tk
from pathlib import Path
from typing import TYPE_CHECKING
+from uuid import uuid4
from scalewiz import CONFIG
from scalewiz.helpers.configuration import update_config
@@ -15,6 +16,7 @@
if TYPE_CHECKING:
from typing import List
+ from uuid import UUID
LOGGER = logging.getLogger("scalewiz")
@@ -26,6 +28,7 @@ class Project:
def __init__(self) -> None:
self.tests: List[Test] = []
+ self.uuid: str = None # hex string id
# experiment parameters that affect score
self.baseline = tk.IntVar()
self.limit_minutes = tk.DoubleVar()
@@ -79,12 +82,47 @@ def set_defaults(self) -> None:
self.interval_seconds.set(1)
self.analyst.set(CONFIG["recents"]["analyst"])
- def add_traces(self) -> None:
- """Adds tkVar traces where needed. Must be cleaned up with remove_traces."""
- self.customer.trace_add("write", self.update_proj_name)
- self.client.trace_add("write", self.update_proj_name)
- self.field.trace_add("write", self.update_proj_name)
- self.sample.trace_add("write", self.update_proj_name)
+ def get_metadata(self) -> dict:
+ """Returns a dict representing the Project's metadata.
+
+ Returns:
+ dict: represents the project's metadata
+ """
+ return {
+ "customer": self.customer.get(),
+ "submittedBy": self.submitted_by.get(),
+ "productionCo": self.client.get(),
+ "field": self.field.get(),
+ "sample": self.sample.get(),
+ "sampleDate": self.sample_date.get(),
+ "recDate": self.received_date.get(),
+ "compDate": self.completed_date.get(),
+ "name": self.name.get(),
+ "analyst": self.analyst.get(),
+ "numbers": self.numbers.get(),
+ "path": str(Path(self.path.get()).resolve()),
+ "notes": self.notes.get(),
+ }
+
+ def get_params(self) -> dict:
+ """Returns a dict representing the Project's experiment parameters.
+
+ Returns:
+ dict: a dict representing experiment parameters
+ """
+ return {
+ "bicarbonates": self.bicarbs.get(),
+ "bicarbsIncreased": self.bicarbs_increased.get(),
+ "calcium": self.calcium.get(),
+ "chlorides": self.chlorides.get(),
+ "baseline": self.baseline.get(),
+ "temperature": self.temperature.get(),
+ "limitPSI": self.limit_psi.get(),
+ "limitMin": self.limit_minutes.get(),
+ "interval": self.interval_seconds.get(),
+ "flowrate": self.flowrate.get(),
+ "uptake": self.uptake_seconds.get(),
+ }
def dump_json(self, path: str = None) -> None:
"""Dump a JSON representation of the Project at the passed path."""
@@ -97,6 +135,7 @@ def dump_json(self, path: str = None) -> None:
label = test.label.get().lower()
while label in blanks or label in trials: # make sure we don't overwrite
label = "".join((label, " - copy"))
+ test.label.set(label)
if test.is_blank.get():
blanks[label] = test
else:
@@ -104,7 +143,6 @@ def dump_json(self, path: str = None) -> None:
blank_labels = sort_nicely(list(blanks.keys()))
trial_labels = sort_nicely(list(trials.keys()))
-
tests = []
for label in blank_labels:
tests.append(blanks.pop(label))
@@ -115,34 +153,8 @@ def dump_json(self, path: str = None) -> None:
self.tests = [test for test in tests]
this = {
- "info": {
- "customer": self.customer.get(),
- "submittedBy": self.submitted_by.get(),
- "productionCo": self.client.get(),
- "field": self.field.get(),
- "sample": self.sample.get(),
- "sampleDate": self.sample_date.get(),
- "recDate": self.received_date.get(),
- "compDate": self.completed_date.get(),
- "name": self.name.get(),
- "analyst": self.analyst.get(),
- "numbers": self.numbers.get(),
- "path": str(Path(self.path.get()).resolve()),
- "notes": self.notes.get(),
- },
- "params": {
- "bicarbonates": self.bicarbs.get(),
- "bicarbsIncreased": self.bicarbs_increased.get(),
- "calcium": self.calcium.get(),
- "chlorides": self.chlorides.get(),
- "baseline": self.baseline.get(),
- "temperature": self.temperature.get(),
- "limitPSI": self.limit_psi.get(),
- "limitMin": self.limit_minutes.get(),
- "interval": self.interval_seconds.get(),
- "flowrate": self.flowrate.get(),
- "uptake": self.uptake_seconds.get(),
- },
+ "info": self.get_metadata(),
+ "params": self.get_params(),
"tests": [test.to_dict() for test in self.tests],
"outputFormat": self.output_format.get(),
"plot": str(Path(self.plot.get()).resolve()),
@@ -170,22 +182,24 @@ def load_json(self, path: str) -> None:
"Opened a Project whose actual path didn't match its path property"
)
obj["info"]["path"] = str(path)
+ self.uuid = UUID(obj.get("uuid", uuid4().hex))
+ # clerical metadata
info: dict = obj["info"]
- self.customer.set(info["customer"])
- self.submitted_by.set(info["submittedBy"])
- self.client.set(info["productionCo"])
- self.field.set(info["field"])
- self.sample.set(info["sample"])
- self.sample_date.set(info["sampleDate"])
- self.received_date.set(info["recDate"])
- self.completed_date.set(info["compDate"])
- self.name.set(info["name"])
- self.numbers.set(info["numbers"])
- self.analyst.set(info["analyst"])
+ self.customer.set(info.get("customer"))
+ self.submitted_by.set(info.get("submittedBy"))
+ self.client.set(info.get("productionCo"))
+ self.field.set(info.get("field"))
+ self.sample.set(info.get("sample"))
+ self.sample_date.set(info.get("sampleDate"))
+ self.received_date.set(info.get("recDate"))
+ self.completed_date.set(info.get("compDate"))
+ self.name.set(info.get("name"))
+ self.numbers.set(info.get("numbers"))
+ self.analyst.set(info.get("analyst"))
self.path.set(str(Path(info["path"]).resolve()))
- self.notes.set(info["notes"])
-
+ self.notes.set(info.get("notes"))
+ # experimental metadata
defaults = CONFIG["defaults"]
params: dict = obj["params"]
self.bicarbs.set(params.get("bicarbonates", 0))
@@ -200,14 +214,22 @@ def load_json(self, path: str) -> None:
self.flowrate.set(params.get("flowrate", defaults["flowrate"]))
self.uptake_seconds.set(params.get("uptake", defaults["uptake_time"]))
self.output_format.set(obj.get("outputFormat", defaults["output_format"]))
-
- self.plot.set(obj["plot"])
+ self.plot.set(obj.get("plot"))
self.tests.clear()
- for entry in obj["tests"]:
+ for entry in obj.get("tests"):
test = Test(data=entry)
self.tests.append(test)
+ # traces / validation stuff
+
+ def add_traces(self) -> None:
+ """Adds tkVar traces where needed. Must be cleaned up with remove_traces."""
+ self.customer.trace_add("write", self.update_proj_name)
+ self.client.trace_add("write", self.update_proj_name)
+ self.field.trace_add("write", self.update_proj_name)
+ self.sample.trace_add("write", self.update_proj_name)
+
def remove_traces(self) -> None:
"""Remove tkVar traces to allow the GC to do its thing."""
variables = (self.customer, self.client, self.field, self.sample)
diff --git a/scalewiz/models/test.py b/scalewiz/models/test.py
index 9fd45f3..bb8194f 100644
--- a/scalewiz/models/test.py
+++ b/scalewiz/models/test.py
@@ -1,4 +1,4 @@
-"""Model object for a Test."""
+"""Tkinter-powered model object for a Test, with some dict-related capabilities."""
from __future__ import annotations
@@ -10,16 +10,36 @@
if TYPE_CHECKING:
from typing import List, Tuple, Union
+ from uuid import UUID
LOGGER = logging.getLogger("scalewiz")
@dataclass
class Reading:
- elapsedMin: float
+ """Holds the data for a particular reading."""
+
+ elapsed_min: float
pump1: int
pump2: int
- average: int
+ average: int = round((pump1 + pump2) / 2)
+
+
+# this is currently unused
+@dataclass
+class TestData:
+ name: str
+ label: str
+ is_blank: bool
+ on_report: bool
+ clarity: str
+ readings: List[Reading]
+ max_pressure: int
+ observed_baseline: int
+ pump_to_score: str
+ result: float
+ chemical: str
+ rate: Union[float, int]
class Test:
@@ -28,6 +48,7 @@ class Test:
# pylint: disable=too-many-instance-attributes
def __init__(self, data: dict = None) -> None:
+ # mutable metadata
self.is_blank = tk.BooleanVar() # boolean for blank vs chemical trial
self.name = tk.StringVar() # identifier for the test
self.chemical = tk.StringVar() # chemical, if any, to be tested
@@ -35,14 +56,17 @@ def __init__(self, data: dict = None) -> None:
self.label = tk.StringVar() # how the test will be labeled on the report/plot
self.clarity = tk.StringVar() # the clarity of the treated water
self.notes = tk.StringVar() # misc notes on the experiment
+ # immutable data
+ self.readings: Tuple[Reading] = () # list of flat reading dicts
+ self.uuid: UUID = None
+ # mutable data
self.pump_to_score = tk.StringVar() # which series of PSIs to use
self.result = tk.DoubleVar() # represents the test's performance vs the blank
self.include_on_report = tk.BooleanVar() # condition for scoring
- self.readings: List[Reading] = [] # list of flat reading dicts
self.max_psi = tk.IntVar() # the highest psi of the test
self.observed_baseline = tk.IntVar() # a guess at the baseline for the test
# set defaults
- self.pump_to_score.set("pump 1")
+ self.pump_to_score.set("pump1")
self.is_blank.set(True)
self.add_traces() # will need to clean these up later for the GC
@@ -58,54 +82,63 @@ def add_traces(self) -> None:
def to_dict(self) -> dict[str, Union[bool, float, int, str]]:
"""Returns a dict representation of a Test."""
- self.clean_test() # strip whitespaces from relevant fields
+ self.strip_test() # strip whitespaces from relevant fields
# cast all readings from dataclasses to dicts
readings = []
for reading in self.readings:
readings.append(
{
- "pump 1": reading.pump1,
- "pump 2": reading.pump2,
"average": reading.average,
+ "pump1": reading.pump1,
+ "pump2": reading.pump2,
"elapsedMin": reading.elapsedMin,
}
)
return {
"name": self.name.get(),
- "isBlank": self.is_blank.get(),
+ "is_blank": self.is_blank.get(),
"chemical": self.chemical.get(),
"rate": self.rate.get(),
- "reportAs": self.label.get(),
+ "report_as": self.label.get(),
"clarity": self.clarity.get(),
"notes": self.notes.get(),
- "toConsider": self.pump_to_score.get(),
- "includeOnRep": self.include_on_report.get(),
+ "to_consider": self.pump_to_score.get(),
+ "include_on_report": self.include_on_report.get(),
"result": self.result.get(),
- "obsBaseline": self.observed_baseline.get(),
+ "observed_baseline": self.observed_baseline.get(),
"readings": readings,
}
- def load_json(self, obj: dict[str, Union[bool, float, int, str]]) -> None:
- """Load a Test with values from a JSON object."""
- self.name.set(obj["name"])
- self.is_blank.set(obj["isBlank"])
- self.chemical.set(obj["chemical"])
- self.rate.set(obj["rate"])
- self.label.set(obj["reportAs"])
- self.clarity.set(obj["clarity"])
- self.notes.set(obj["notes"])
- self.pump_to_score.set(obj["toConsider"])
- self.include_on_report.set(obj["includeOnRep"])
- self.result.set(obj["result"])
- readings = obj["readings"]
+ def load_json(self, obj: dict) -> None:
+ """Load a Test with values from a dict."""
+ # look for fallbacks for backwards compatability
+ self.name.set(obj.get("name"))
+ self.is_blank.set(obj.get("is_blank", obj.get("isBlank")))
+ self.calcium.set(obj.get("calcium"))
+ self.chemical.set(obj.get("chemical"))
+ self.rate.set(obj.get("rate"))
+ self.label.set(obj.get("report_as", obj.get("reportAs")))
+ self.clarity.set(obj.get("clarity"))
+ self.notes.set(obj.get("notes"))
+ self.pump_to_score.set(obj.get("to_consider", obj.get("toConsider")))
+ self.include_on_report.set(
+ obj.get("include_on_report", obj.get("includeOnRep"))
+ )
+ self.result.set(obj.get("result"))
+ readings: List[dict] = obj.get("readings")
for reading in readings:
+ # do some cleaning for backwards compatibility
+ pump1 = reading.get("pump1", reading.get("pump 1"))
+ pump2 = reading.get("pump2", reading.get("pump 2"))
+ if "average" not in reading.keys():
+ average = round((pump1 + pump2) / 2)
self.readings.append(
Reading(
- pump1=reading["pump 1"],
- pump2=reading["pump 2"],
- average=reading["average"],
- elapsedMin=reading["elapsedMin"],
+ average=average,
+ pump1=pump1,
+ pump2=pump2,
+ elapsed_min=reading.get("elapsed_min", reading.get("elapsedMin")),
)
)
self.update_obs_baseline()
@@ -113,23 +146,26 @@ def load_json(self, obj: dict[str, Union[bool, float, int, str]]) -> None:
def get_readings(self) -> Tuple[int]:
"""Returns a list of the pump_to_score's pressure readings."""
pump = self.pump_to_score.get()
- pump = pump.replace(" ", "") # legacy accomodation for spaces in keys
- return [getattr(reading, pump) for reading in self.readings]
+ if " " in pump: # legacy accomodation for spaces in keys
+ pump = pump.replace(" ", "")
+ return tuple([getattr(reading, pump) for reading in self.readings])
def update_test_name(self, *args) -> None:
"""Makes a name by concatenating the chemical name and rate."""
- if not (self.chemical.get() == "" or self.rate.get() == 0):
+ if self.chemical.get() != "" and self.rate.get() != 0:
if float(self.rate.get()) == int(self.rate.get()):
self.name.set(f"{self.chemical.get()} {self.rate.get():.0f} ppm")
else:
self.name.set(f"{self.chemical.get()} {self.rate.get():.2f} ppm")
- def clean_test(self) -> None:
+ def strip_test(self) -> None:
"""Do some formatting on the test to clean it up for storing."""
strippables = (self.chemical, self.name, self.label, self.clarity, self.notes)
for attr in strippables:
- if attr.get().strip() != attr.get():
- attr.set(attr.get().strip())
+ val = attr.get()
+ stripped = val.strip()
+ if val != stripped:
+ attr.set(stripped)
def update_label(self, *args) -> None:
"""Sets the label to the current name as a default value."""
diff --git a/scalewiz/models/test_handler.py b/scalewiz/models/test_handler.py
index 8a60f0b..e469ea8 100644
--- a/scalewiz/models/test_handler.py
+++ b/scalewiz/models/test_handler.py
@@ -157,8 +157,9 @@ def get_pressure(pump: NextGenPump) -> Union[float, int]:
psi1 = self.pool.submit(get_pressure, self.pump1)
psi2 = self.pool.submit(get_pressure, self.pump2)
psi1, psi2 = psi1.result(), psi2.result()
- t1 = time()
- self.logger.warn("got both in %s s", t1 - t0)
+ self.logger.debug(
+ "got both pressures from %s in %s s", self.name, time() - t0
+ )
average = round(((psi1 + psi2) / 2))
reading = Reading(
elapsedMin=self.elapsed_min, pump1=psi1, pump2=psi2, average=average
@@ -206,7 +207,7 @@ def save_test(self) -> None:
self.logger.info(
"Saving %s to %s", self.test.name.get(), self.project.name.get()
)
- self.test.readings.extend(self.readings)
+ self.test.readings = tuple(self.readings)
self.project.tests.append(self.test)
self.project.dump_json()
# refresh data / UI
diff --git a/todo b/todo
deleted file mode 100644
index 8468c8b..0000000
--- a/todo
+++ /dev/null
@@ -1,30 +0,0 @@
-todo
-----
-
-- try to clean up export code / add export confirmation dialog
-- handle a queue of changes to a project more gracefully
-
-bugs
-----
-
-- none! I'm pretty sure ...
-
-refactoring
------------
-
-- we have a dep. on Pandas for one little call in export helper - could be worked around
-
-updates / new features
-----------------------
-
-- none!
-
-low prio
---------
-
-- port over the old chlorides / ppm calculators
-- check for config missing keys ?
-- menubar:
- - 'add system' -> 'system' > 'add new', 'remove current'
- - this will be a little awkward since we'd have to update/rebuild the menubar
- each time a system is added / removed