From 4dd04c5ae593a060ab5bfa4f440a14c8643cdbb8 Mon Sep 17 00:00:00 2001 From: Midgie MacFarland <160664522+midgemacf@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:35:21 -0400 Subject: [PATCH 1/5] Update Probe Data Storage Types (#29) `probe_data` table now has `value_float` and `value_jsonb` columns instead of one `value` (jsonb type) column. The migration will take the existing `value::jsonb` column and rename it to `value_jsonb`. Then, will fill the `value_float` for any rows whose `metric_type` is of `int` or `float` type. On downgrade, undoes (casts `value_float` to jsonb in `value` col) so that no data is lost. Updated grafana dashboards accordingly, to pull from `value_float` rather than `value` cast to `float` --------- Co-authored-by: MacFarland, Midgie --- CHANGELOG.md | 4 ++ opensampl/db/orm.py | 3 +- opensampl/load_data.py | 28 +++++++--- opensampl/metrics.py | 4 ++ .../grafana/grafana-dashboards/ntp_dash.json | 2 +- .../public-timing-dashboard.json | 8 +-- .../grafana-dashboards/public_twst_dash.json | 12 ++-- .../grafana-dashboards/timing-dashboard2.json | 8 +-- .../grafana/grafana-dashboards/twst_dash.json | 12 ++-- ...2026_09_11_1445_update_probe_data_types.py | 56 +++++++++++++++++++ 10 files changed, 106 insertions(+), 31 deletions(-) create mode 100644 opensampl/server/migrations/_migrations/versions/2026_09_11_1445_update_probe_data_types.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bca537a..5e695e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). *Unreleased* versions radiate potential—-and dread. Once you merge an infernal PR, move its bullet under a new version heading with the actual release date.* +### Changed +- ⚡ `probe_data` table now has `value_float` and `value_jsonb` columns instead of one `value` (jsonb type) column. +- ⚡ … + --> ## [1.2.1] - 2026-07-08 ### Fixed diff --git a/opensampl/db/orm.py b/opensampl/db/orm.py index 2e104e6..7902559 100644 --- a/opensampl/db/orm.py +++ b/opensampl/db/orm.py @@ -280,7 +280,8 @@ class ProbeData(Base): primary_key=True, comment="Foreign key to the metric type being measured", ) - value = Column(JSONB, comment="Measurement value stored as JSON; value's expected type defined via metric") + value_float = Column(Float, nullable=True, comment="Float type value; value's expected type defined via metric") + value_jsonb = Column(JSONB, nullable=True, comment="Measurement value stored as JSON; value's expected type defined via metric") class MetricType(Base): diff --git a/opensampl/load_data.py b/opensampl/load_data.py index d21f5ce..b4717bf 100644 --- a/opensampl/load_data.py +++ b/opensampl/load_data.py @@ -138,18 +138,28 @@ def load_time_data( df["probe_uuid"] = data_definition.probe.uuid # ty: ignore[possibly-unbound-attribute] df["reference_uuid"] = data_definition.reference.uuid # ty: ignore[possibly-unbound-attribute] df["metric_type_uuid"] = data_definition.metric.uuid # ty: ignore[possibly-unbound-attribute] - logger.debug(df.head()) - # Ensure correct dtypes + + # Ensure correct time dtypes df["time"] = pd.to_datetime(df["time"], format="mixed", utc=True, errors="raise") - df["value"] = df["value"].apply(json.dumps) + + if data_definition.metric.is_numeric(): + df["value_float"] = pd.to_numeric(df["value"], errors="raise") + df["value_jsonb"] = None + else: + df["value_float"] = None + df["value_jsonb"] = df["value"].apply(json.dumps) + + df = df.drop(columns=["value"]) # Drop original value column, as we now have value_float and value_jsonb + logger.debug(df.head()) + records = df.to_dict(orient="records") insert_stmt = text(f""" - INSERT INTO {ProbeData.__table__.schema}.{ProbeData.__tablename__} - (time, probe_uuid, reference_uuid, metric_type_uuid, value) - VALUES (:time, :probe_uuid, :reference_uuid, :metric_type_uuid, :value) - ON CONFLICT (time, probe_uuid, reference_uuid, metric_type_uuid) - DO NOTHING - """) # noqa: S608 + INSERT INTO {ProbeData.__table__.schema}.{ProbeData.__tablename__} + (time, probe_uuid, reference_uuid, metric_type_uuid, value_float, value_jsonb) + VALUES (:time, :probe_uuid, :reference_uuid, :metric_type_uuid, :value_float, :value_jsonb) + ON CONFLICT (time, probe_uuid, reference_uuid, metric_type_uuid) + DO NOTHING + """) # noqa: S608 try: result = session.execute(insert_stmt, records) diff --git a/opensampl/metrics.py b/opensampl/metrics.py index 27b6b8a..5fb28da 100644 --- a/opensampl/metrics.py +++ b/opensampl/metrics.py @@ -21,6 +21,10 @@ def convert_to_type(self, value: Any) -> Any: """Convert a given value to the expected type for the Metric""" return self.value_type(value) + def is_numeric(self) -> bool: + """Return True if the metric's value_type is numeric (int or float)""" + return self.value_type in [int, float] + @field_serializer("value_type") def serialize_type(self, value: type): """Return the name of value_type for serializing""" diff --git a/opensampl/server/grafana/grafana-dashboards/ntp_dash.json b/opensampl/server/grafana/grafana-dashboards/ntp_dash.json index 345fd8c..6a8df53 100644 --- a/opensampl/server/grafana/grafana-dashboards/ntp_dash.json +++ b/opensampl/server/grafana/grafana-dashboards/ntp_dash.json @@ -1313,7 +1313,7 @@ "editorMode": "code", "format": "table", "rawQuery": true, - "rawSql": "WITH probe_ref AS (\n SELECT\n uuid,\n COALESCE(pm.name, CONCAT(pm.ip_address, ' ', pm.probe_id)) AS probe_name\n FROM castdb.probe_metadata pm\n)\nSELECT\n time_bucket('1 minute'::interval, pd.time AT TIME ZONE 'UTC') AS time,\n pd.probe_uuid,\n pr.probe_name,\n pd.reference_uuid,\n AVG(pd.value::float * 1e9) FILTER (WHERE lower(m.name) = 'phase offset') AS phase_offset,\n AVG(pd.value::float * 1e9) FILTER (WHERE lower(m.name) = 'jitter') AS jitter,\n AVG((pd.value)::float) FILTER (WHERE lower(m.name) = 'stratum') AS stratum,\n AVG((pd.value)::float) FILTER (WHERE lower(m.name) = 'sync health') AS sync_health\nFROM castdb.probe_data pd\nJOIN probe_ref pr\n ON pd.probe_uuid = pr.uuid\nJOIN castdb.metric_type m\n ON pd.metric_type_uuid = m.uuid\nWHERE pd.probe_uuid = ANY(ARRAY[${ntp_probe:sqlstring}]::text[]) AND $__timeFilter(pd.time)\nGROUP BY 1, 2, 3, 4\nORDER BY\n 1, 3;", + "rawSql": "WITH probe_ref AS (\n SELECT\n uuid,\n COALESCE(pm.name, CONCAT(pm.ip_address, ' ', pm.probe_id)) AS probe_name\n FROM castdb.probe_metadata pm\n)\nSELECT\n time_bucket('1 minute'::interval, pd.time AT TIME ZONE 'UTC') AS time,\n pd.probe_uuid,\n pr.probe_name,\n pd.reference_uuid,\n AVG(pd.value_float * 1e9) FILTER (WHERE lower(m.name) = 'phase offset') AS phase_offset,\n AVG(pd.value_float * 1e9) FILTER (WHERE lower(m.name) = 'jitter') AS jitter,\n AVG((pd.value)::float) FILTER (WHERE lower(m.name) = 'stratum') AS stratum,\n AVG((pd.value)::float) FILTER (WHERE lower(m.name) = 'sync health') AS sync_health\nFROM castdb.probe_data pd\nJOIN probe_ref pr\n ON pd.probe_uuid = pr.uuid\nJOIN castdb.metric_type m\n ON pd.metric_type_uuid = m.uuid\nWHERE pd.probe_uuid = ANY(ARRAY[${ntp_probe:sqlstring}]::text[]) AND $__timeFilter(pd.time)\nGROUP BY 1, 2, 3, 4\nORDER BY\n 1, 3;", "refId": "A", "sql": { "columns": [ diff --git a/opensampl/server/grafana/grafana-dashboards/public-timing-dashboard.json b/opensampl/server/grafana/grafana-dashboards/public-timing-dashboard.json index 24b7a6f..68bd469 100644 --- a/opensampl/server/grafana/grafana-dashboards/public-timing-dashboard.json +++ b/opensampl/server/grafana/grafana-dashboards/public-timing-dashboard.json @@ -650,7 +650,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n coalesce(pm.name, concat(pm.ip_address, ' Interface ', pm.probe_id)),\n AVG(pd.value::FLOAT) * 1e9 AS value\nFROM castdb.probe_data pd JOIN castdb.probe_metadata pm ON pd.probe_uuid = pm.uuid\nWHERE\n $__timeFilter(pd.time)\n AND pm.vendor IN ('ADVA', 'MicrochipTP4100', 'NTP')\n AND coalesce(pm.public, true)\n AND (trim('${clock_name:csv}') = '' OR pd.probe_uuid = ANY(string_to_array(trim('${clock_name:csv}'), ',')))\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid,\n pm.name,\n pm.ip_address,\n pm.probe_id\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n coalesce(pm.name, concat(pm.ip_address, ' Interface ', pm.probe_id)),\n AVG(pd.value_float) * 1e9 AS value\nFROM castdb.probe_data pd JOIN castdb.probe_metadata pm ON pd.probe_uuid = pm.uuid\nWHERE\n $__timeFilter(pd.time)\n AND pm.vendor IN ('ADVA', 'MicrochipTP4100', 'NTP')\n AND coalesce(pm.public, true)\n AND (trim('${clock_name:csv}') = '' OR pd.probe_uuid = ANY(string_to_array(trim('${clock_name:csv}'), ',')))\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid,\n pm.name,\n pm.ip_address,\n pm.probe_id\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", "refId": "A", "sql": { "columns": [ @@ -776,7 +776,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, CONCAT(pm.ip_address, ' Interface ', pm.probe_id)),\n (MAX(pd.value::FLOAT) - MIN(pd.value::FLOAT)) * 1e9 AS value\nFROM castdb.probe_data pd JOIN castdb.probe_metadata pm ON pd.probe_uuid = pm.uuid\nWHERE\n $__timeFilter(pd.time)\n AND pm.vendor IN ('ADVA', 'MicrochipTP4100', 'NTP')\n AND coalesce(pm.public, true)\n AND (trim('${clock_name:csv}') = '' OR pd.probe_uuid = ANY(string_to_array(trim('${clock_name:csv}'), ',')))\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid,\n pm.name,\n pm.ip_address,\n pm.probe_id\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, CONCAT(pm.ip_address, ' Interface ', pm.probe_id)),\n (MAX(pd.value_float) - MIN(pd.value_float)) * 1e9 AS value\nFROM castdb.probe_data pd JOIN castdb.probe_metadata pm ON pd.probe_uuid = pm.uuid\nWHERE\n $__timeFilter(pd.time)\n AND pm.vendor IN ('ADVA', 'MicrochipTP4100', 'NTP')\n AND coalesce(pm.public, true)\n AND (trim('${clock_name:csv}') = '' OR pd.probe_uuid = ANY(string_to_array(trim('${clock_name:csv}'), ',')))\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid,\n pm.name,\n pm.ip_address,\n pm.probe_id\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", "refId": "A", "sql": { "columns": [ @@ -916,7 +916,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value::FLOAT) * 1e9 AS value\nFROM castdb.probe_data pd\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid = ${clock_name:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value_float) * 1e9 AS value\nFROM castdb.probe_data pd\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid = ${clock_name:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", "refId": "A", "sql": { "columns": [ @@ -1027,7 +1027,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n (MAX(pd.value::FLOAT) - MIN(pd.value::FLOAT)) * 1e9 AS value\nFROM castdb.probe_data pd\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid = ${clock_name:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n (MAX(pd.value_float) - MIN(pd.value_float)) * 1e9 AS value\nFROM castdb.probe_data pd\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid = ${clock_name:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", "refId": "A", "sql": { "columns": [ diff --git a/opensampl/server/grafana/grafana-dashboards/public_twst_dash.json b/opensampl/server/grafana/grafana-dashboards/public_twst_dash.json index d3f4691..7858b93 100644 --- a/opensampl/server/grafana/grafana-dashboards/public_twst_dash.json +++ b/opensampl/server/grafana/grafana-dashboards/public_twst_dash.json @@ -493,7 +493,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n coalesce(pm.name, pm.probe_id) AS channel,\n AVG(pd.value::FLOAT) AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${ebno_uuid:sqlstring}) pd\nJOIN \n castdb.reference r ON pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nWHERE \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.name,\n pm.probe_id\nORDER BY \n time\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n coalesce(pm.name, pm.probe_id) AS channel,\n AVG(pd.value_float) AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${ebno_uuid:sqlstring}) pd\nJOIN \n castdb.reference r ON pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nWHERE \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.name,\n pm.probe_id\nORDER BY \n time\n", "refId": "B", "sql": { "columns": [ @@ -628,7 +628,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, pm.probe_id) as name,\n AVG(pd.value::FLOAT) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nwhere \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, pm.probe_id) as name,\n AVG(pd.value_float) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nwhere \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ @@ -763,7 +763,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, pm.probe_id) as name,\n (MAX(pd.value::FLOAT) - MIN(pd.value::FLOAT)) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nwhere \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, pm.probe_id) as name,\n (MAX(pd.value_float) - MIN(pd.value_float)) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nwhere \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ @@ -1246,7 +1246,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value::FLOAT) AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${ebno_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value_float) AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${ebno_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ @@ -1367,7 +1367,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value::FLOAT) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value_float) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ @@ -1488,7 +1488,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n (MAX(pd.value::FLOAT) - MIN(pd.value::FLOAT)) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n (MAX(pd.value_float) - MIN(pd.value_float)) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ diff --git a/opensampl/server/grafana/grafana-dashboards/timing-dashboard2.json b/opensampl/server/grafana/grafana-dashboards/timing-dashboard2.json index bd23706..a108c40 100644 --- a/opensampl/server/grafana/grafana-dashboards/timing-dashboard2.json +++ b/opensampl/server/grafana/grafana-dashboards/timing-dashboard2.json @@ -653,7 +653,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n coalesce(pm.name, concat(pm.ip_address, ' Interface ', pm.probe_id)),\n AVG(pd.value::FLOAT) * 1e9 AS value\nFROM castdb.probe_data pd join castdb.probe_metadata pm on pd.probe_uuid = pm.uuid\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid IN (${clock_name:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid,\n pm.name,\n pm.ip_address,\n pm.probe_id\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n coalesce(pm.name, concat(pm.ip_address, ' Interface ', pm.probe_id)),\n AVG(pd.value_float) * 1e9 AS value\nFROM castdb.probe_data pd join castdb.probe_metadata pm on pd.probe_uuid = pm.uuid\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid IN (${clock_name:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid,\n pm.name,\n pm.ip_address,\n pm.probe_id\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", "refId": "A", "sql": { "columns": [ @@ -778,7 +778,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(name, CONCAT(ip_address, ' Interface ', probe_id)),\n (MAX(pd.value::FLOAT) - MIN(pd.value::FLOAT)) * 1e9 AS value\nFROM castdb.probe_data pd join castdb.probe_metadata pm on pd.probe_uuid = pm.uuid\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid in (${clock_name:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid,\n pm.name,\n pm.ip_address,\n pm.probe_id\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(name, CONCAT(ip_address, ' Interface ', probe_id)),\n (MAX(pd.value_float) - MIN(pd.value_float)) * 1e9 AS value\nFROM castdb.probe_data pd join castdb.probe_metadata pm on pd.probe_uuid = pm.uuid\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid in (${clock_name:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid,\n pm.name,\n pm.ip_address,\n pm.probe_id\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", "refId": "A", "sql": { "columns": [ @@ -917,7 +917,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value::FLOAT) * 1e9 AS value\nFROM castdb.probe_data pd\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid = ${clock_name:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value_float) * 1e9 AS value\nFROM castdb.probe_data pd\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid = ${clock_name:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", "refId": "A", "sql": { "columns": [ @@ -1027,7 +1027,7 @@ "editorMode": "code", "format": "time_series", "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n (MAX(pd.value::FLOAT) - MIN(pd.value::FLOAT)) * 1e9 AS value\nFROM castdb.probe_data pd\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid = ${clock_name:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n (MAX(pd.value_float) - MIN(pd.value_float)) * 1e9 AS value\nFROM castdb.probe_data pd\nWHERE\n $__timeFilter(pd.time)\n AND pd.probe_uuid = ${clock_name:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')\n", "refId": "A", "sql": { "columns": [ diff --git a/opensampl/server/grafana/grafana-dashboards/twst_dash.json b/opensampl/server/grafana/grafana-dashboards/twst_dash.json index c90cad0..b4c59db 100644 --- a/opensampl/server/grafana/grafana-dashboards/twst_dash.json +++ b/opensampl/server/grafana/grafana-dashboards/twst_dash.json @@ -492,7 +492,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n coalesce(pm.name, pm.probe_id) AS channel,\n AVG(pd.value::FLOAT) AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${ebno_uuid:sqlstring}) pd\nJOIN \n castdb.reference r ON pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nWHERE \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.name,\n pm.probe_id\nORDER BY \n time\n", + "rawSql": "SELECT \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n coalesce(pm.name, pm.probe_id) AS channel,\n AVG(pd.value_float) AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${ebno_uuid:sqlstring}) pd\nJOIN \n castdb.reference r ON pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nWHERE \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.name,\n pm.probe_id\nORDER BY \n time\n", "refId": "B", "sql": { "columns": [ @@ -627,7 +627,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, pm.probe_id) as name,\n AVG(pd.value::FLOAT) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nwhere \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, pm.probe_id) as name,\n AVG(pd.value_float) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nwhere \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ @@ -762,7 +762,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, pm.probe_id) as name,\n (MAX(pd.value::FLOAT) - MIN(pd.value::FLOAT)) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nwhere \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n COALESCE(pm.name, pm.probe_id) as name,\n (MAX(pd.value_float) - MIN(pd.value_float)) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nJOIN \n castdb.probe_metadata pm ON r.compound_reference_uuid = pm.uuid\nwhere \n $__timeFilter(pd.time)\n and r.compound_reference_uuid in (${compound_ref:sqlstring})\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pm.uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ @@ -1247,7 +1247,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value::FLOAT) AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${ebno_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value_float) AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${ebno_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ @@ -1368,7 +1368,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value::FLOAT) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n AVG(pd.value_float) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ @@ -1489,7 +1489,7 @@ "format": "table", "hide": false, "rawQuery": true, - "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n (MAX(pd.value::FLOAT) - MIN(pd.value::FLOAT)) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", + "rawSql": "select \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC') AS time,\n (MAX(pd.value_float) - MIN(pd.value_float)) * 1e9 AS value\nFROM \n get_probe_data_by_probe_and_metric(${modem:sqlstring}, ${phase_offset_uuid:sqlstring}) pd\njoin castdb.reference r on pd.reference_uuid = r.uuid\nwhere \n $__timeFilter(pd.time)\n AND r.compound_reference_uuid = ${compound_ref:sqlstring}\nGROUP BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC'),\n pd.probe_uuid\nORDER BY \n time_bucket(${resolution:sqlstring}, pd.time AT TIME ZONE 'UTC')", "refId": "B", "sql": { "columns": [ diff --git a/opensampl/server/migrations/_migrations/versions/2026_09_11_1445_update_probe_data_types.py b/opensampl/server/migrations/_migrations/versions/2026_09_11_1445_update_probe_data_types.py new file mode 100644 index 0000000..7c2688d --- /dev/null +++ b/opensampl/server/migrations/_migrations/versions/2026_09_11_1445_update_probe_data_types.py @@ -0,0 +1,56 @@ +"""update probe_data types + +Revision ID: b88042bae240 +Revises: c95e49e551be +Create Date: 2026-09-11 14:45:53.382353 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'b88042bae240' +down_revision: Union[str, None] = 'c95e49e551be' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +SCHEMA = 'castdb' + +def upgrade() -> None: + op.alter_column("probe_data", + "value", + new_column_name="value_jsonb", + nullable=True, + schema=SCHEMA) + op.add_column("probe_data", + sa.Column("value_float", sa.Float(), nullable=True), + schema=SCHEMA) + + # Backfill value_float from the numeric jsonb values + op.execute( + """ + UPDATE castdb.probe_data pd + SET value_float = (pd.value_jsonb #>> '{}')::double precision + FROM castdb.metric_type mt + WHERE pd.metric_type_uuid = mt.uuid + AND mt.value_type IN ('float' + , 'int') + """ + ) + + +def downgrade() -> None: + op.execute( + """ + UPDATE castdb.probe_data + SET value_jsonb = to_jsonb(value_float) + WHERE value_float IS NOT NULL + """ + ) + op.drop_column("probe_data", "value_float", schema=SCHEMA) + + op.alter_column("probe_data", "value_jsonb", nullable=False, new_column_name="value", schema=SCHEMA) From bd4e24a218fbf667dfaddc2df03e7cf1dce9aad9 Mon Sep 17 00:00:00 2001 From: Joshua Grant Date: Wed, 30 Sep 2026 09:37:53 -0400 Subject: [PATCH 2/5] Update CHANGELOG for versions 1.2.1 and 1.2.2 Updated the CHANGELOG to include new version 1.2.2 and fixed CVEs in version 1.2.1. --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e695e2..227ceb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,11 +36,13 @@ This project adheres to [Semantic Versioning](https://semver.org/). *Unreleased* versions radiate potential—-and dread. Once you merge an infernal PR, move its bullet under a new version heading with the actual release date.* + + +--> +## [1.2.2] = 2026-09-30 ### Changed - ⚡ `probe_data` table now has `value_float` and `value_jsonb` columns instead of one `value` (jsonb type) column. -- ⚡ … ---> ## [1.2.1] - 2026-07-08 ### Fixed - 🩹 Addressed CVEs by updating minimum package versions for urllib3 2.7.0, pillow 12.3.0, mako 1.3.12, starlette 1.3.1, fastapi 0.139.0, idna 3.18, pytest 9.1.1, python-dotenv 1.2.2, fonttools 4.63.0, requests 2.34.2, markdown 3.10.2, pymdown-extensions 10.21.3, pygments 2.20.0, and python-multipart 0.0.32. From 07d462ebf7cb6eded6f17d24723c355d8c78f3ad Mon Sep 17 00:00:00 2001 From: Joshua Grant Date: Wed, 30 Sep 2026 09:38:24 -0400 Subject: [PATCH 3/5] Bump version from 1.2.1 to 1.2.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2b7be09..55369d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "opensampl" -version = "1.2.1" +version = "1.2.2" description = "Python tools for adding clock data to a timescale db." license = {file = "LICENSE"} authors = [ From 85cc5206ef889007cd0fd39ae7e70e078f54fb7a Mon Sep 17 00:00:00 2001 From: Joshua Grant Date: Wed, 30 Sep 2026 09:41:34 -0400 Subject: [PATCH 4/5] Reformat columns for better readability in orm.py --- opensampl/db/orm.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/opensampl/db/orm.py b/opensampl/db/orm.py index 7902559..ea62e5c 100644 --- a/opensampl/db/orm.py +++ b/opensampl/db/orm.py @@ -280,8 +280,16 @@ class ProbeData(Base): primary_key=True, comment="Foreign key to the metric type being measured", ) - value_float = Column(Float, nullable=True, comment="Float type value; value's expected type defined via metric") - value_jsonb = Column(JSONB, nullable=True, comment="Measurement value stored as JSON; value's expected type defined via metric") + value_float = Column( + Float, + nullable=True, + comment="Float type value; value's expected type defined via metric" + ) + value_jsonb = Column( + JSONB, + nullable=True, + comment="Measurement value stored as JSON; value's expected type defined via metric" + ) class MetricType(Base): @@ -299,9 +307,17 @@ class MetricType(Base): default=lambda: str(uuid.uuid4()), comment="Auto generated primary key UUID for the metric type", ) - name = Column(String, unique=True, comment="Unique name for the metric type (e.g., phase offset, delay, quality)") - description = Column(Text, nullable=True, comment="Optional human-readable description of the metric") - unit = Column(String, nullable=False, comment="Measurement unit (e.g., ns, s, ppm)") + name = Column( + String, + unique=True, + comment="Unique name for the metric type (e.g., phase offset, delay, quality)" + ) + description = Column( + Text, nullable=True, comment="Optional human-readable description of the metric" + ) + unit = Column( + String, nullable=False, comment="Measurement unit (e.g., ns, s, ppm)" + ) value_type = Column( String, nullable=False, default="string", comment="Data type of the value (e.g., float, int, string)" ) From 41a22f06a08d32a779a8532015ed8fcf81b2a8ae Mon Sep 17 00:00:00 2001 From: "Grant, Josh" Date: Wed, 30 Sep 2026 10:27:20 -0400 Subject: [PATCH 5/5] lint: format opensample/db/orm.py with ruff --- opensampl/db/orm.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/opensampl/db/orm.py b/opensampl/db/orm.py index ea62e5c..5403ff3 100644 --- a/opensampl/db/orm.py +++ b/opensampl/db/orm.py @@ -280,15 +280,9 @@ class ProbeData(Base): primary_key=True, comment="Foreign key to the metric type being measured", ) - value_float = Column( - Float, - nullable=True, - comment="Float type value; value's expected type defined via metric" - ) + value_float = Column(Float, nullable=True, comment="Float type value; value's expected type defined via metric") value_jsonb = Column( - JSONB, - nullable=True, - comment="Measurement value stored as JSON; value's expected type defined via metric" + JSONB, nullable=True, comment="Measurement value stored as JSON; value's expected type defined via metric" ) @@ -307,17 +301,9 @@ class MetricType(Base): default=lambda: str(uuid.uuid4()), comment="Auto generated primary key UUID for the metric type", ) - name = Column( - String, - unique=True, - comment="Unique name for the metric type (e.g., phase offset, delay, quality)" - ) - description = Column( - Text, nullable=True, comment="Optional human-readable description of the metric" - ) - unit = Column( - String, nullable=False, comment="Measurement unit (e.g., ns, s, ppm)" - ) + name = Column(String, unique=True, comment="Unique name for the metric type (e.g., phase offset, delay, quality)") + description = Column(Text, nullable=True, comment="Optional human-readable description of the metric") + unit = Column(String, nullable=False, comment="Measurement unit (e.g., ns, s, ppm)") value_type = Column( String, nullable=False, default="string", comment="Data type of the value (e.g., float, int, string)" )