diff --git a/README.md b/README.md
index 9e787e7..35f3398 100644
--- a/README.md
+++ b/README.md
@@ -28,8 +28,8 @@ Options:
--help Show this message and exit.
Commands:
- audit
- audit-bulk
+ audit Audit a consolidated Dropmate log.
+ audit-bulk Audit a directory of consolidated Dropmate logs.
```
@@ -44,21 +44,23 @@ The following environment variables are provided to help customize pipeline beha
### `dropmate audit`
Process a consolidated Dropmate log CSV.
#### Input Parameters
-| Parameter | Description | Type | Default |
-|------------------|------------------------------------|--------------|------------|
-| `--log-filepath` | Path to Dropmate log CSV to parse. | `Path\|None` | GUI Prompt |
-| `--min-alt-loss` | Threshold altitude delta. | `int` | `200` |
-| `--min-firmware` | Threshold firmware version. | `int\|float` | `5` |
+| Parameter | Description | Type | Default |
+|------------------------|-----------------------------------------------|--------------|------------|
+| `--log-filepath` | Path to Dropmate log CSV to parse. | `Path\|None` | GUI Prompt |
+| `--min-alt-loss` | Threshold altitude delta. | `int` | `200` |
+| `--min-firmware` | Threshold firmware version. | `int\|float` | `5` |
+| `--time-delta-minutes` | Dropmate internal clock delta from real-time. | `int` | `60` |
### `dropmate audit-bulk`
Batch process a directory of consolidated Dropmate log CSVs.
#### Input Parameters
-| Parameter | Description | Type | Default |
-|------------------|-----------------------------------------------|--------------|------------|
-| `--log-dir` | Path to Dropmate log directory to parse. | `Path\|None` | GUI Prompt |
-| `--log-pattern` | Dropmate log file glob pattern.1,2 | `str` | `"*.csv"` |
-| `--min-alt-loss` | Threshold altitude delta. | `int` | `200` |
-| `--min-firmware` | Threshold firmware version. | `int\|float` | `5` |
+| Parameter | Description | Type | Default |
+|------------------------|-----------------------------------------------|--------------|------------|
+| `--log-dir` | Path to Dropmate log directory to parse. | `Path\|None` | GUI Prompt |
+| `--log-pattern` | Dropmate log file glob pattern.1,2 | `str` | `"*.csv"` |
+| `--min-alt-loss` | Threshold altitude delta. | `int` | `200` |
+| `--min-firmware` | Threshold firmware version. | `int\|float` | `5` |
+| `--time-delta-minutes` | Dropmate internal clock delta from real-time. | `int` | `60` |
1. Case sensitivity is deferred to the host OS
2. Recursive globbing requires manual specification (e.g. `**/*.csv`)
diff --git a/dropmate_py/cli.py b/dropmate_py/cli.py
index 1ba9411..9690766 100644
--- a/dropmate_py/cli.py
+++ b/dropmate_py/cli.py
@@ -7,6 +7,7 @@
MIN_ALT_LOSS = 200
MIN_FIRMWARE = 5
+MIN_TIME_DELTA_MINUTES = 60
load_dotenv()
start_dir = os.environ.get("PROMPT_START_DIR", ".")
@@ -20,6 +21,7 @@ def audit(
log_filepath: Path = typer.Option(exists=True, file_okay=True, dir_okay=False),
min_alt_loss: int = typer.Option(default=MIN_ALT_LOSS),
min_firmware: float = typer.Option(default=MIN_FIRMWARE),
+ time_delta_minutes: int = typer.Option(default=MIN_TIME_DELTA_MINUTES),
) -> None:
"""Audit a consolidated Dropmate log."""
if log_filepath is None:
@@ -41,6 +43,7 @@ def audit_bulk(
log_pattern: str = typer.Option("*.csv"),
min_alt_loss: int = typer.Option(default=MIN_ALT_LOSS),
min_firmware: float = typer.Option(default=MIN_FIRMWARE),
+ time_delta_minutes: int = typer.Option(default=MIN_TIME_DELTA_MINUTES),
) -> None:
"""Audit a directory of consolidated Dropmate logs."""
if log_dir is None:
diff --git a/dropmate_py/parser.py b/dropmate_py/parser.py
new file mode 100644
index 0000000..ed8131a
--- /dev/null
+++ b/dropmate_py/parser.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+import typing as t
+from dataclasses import dataclass, fields
+
+
+@dataclass
+class ColumnIndices:
+ """
+ Column index in the parsed Dropmate compiled log CSV.
+
+ Attribute names are assumed to correspond to the Dropmate log header name. Columns contained may
+ vary by Dropmate app version, so if they are not present in the compiled log they will remain at
+ a value of `-1`, indicating they were not found.
+ """
+
+ serial_number: int = -1
+ uid: int = -1
+ battery: int = -1
+ device_health: int = -1
+ firmware_version: int = -1
+ start_time_utc: int = -1
+ end_time_utc: int = -1
+ start_barometric_altitude_msl_ft: int = -1
+ end_barometric_altitude_msl_ft: int = -1
+ dropmate_internal_time: int = -1
+ last_scanned_time: int = -1
+
+ def __iter__(self) -> t.Generator[str, None, None]:
+ for f in fields(self):
+ yield f.name
+
+ @classmethod
+ def from_header(cls, header: str) -> ColumnIndices:
+ """Attempt to match attribute names to their corresponding data columns."""
+ indices = ColumnIndices()
+
+ # Some column names may have stray spaces in them, listify so we can iterate over repeatedly
+ col_names = [c.strip().lower() for c in header.split(",")]
+ for query_col in indices:
+ for idx, col in enumerate(col_names):
+ if col == query_col:
+ setattr(indices, query_col, idx)
+
+ return indices
+
+ def __str__(self) -> str: # pragma: no cover
+ return ", ".join(f"({f}, {getattr(self, f)})" for f in self)
diff --git a/poetry.lock b/poetry.lock
index 3955133..c2ab368 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -621,20 +621,6 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
-[[package]]
-name = "pytest-check"
-version = "2.2.0"
-description = "A pytest plugin that allows multiple failures per test."
-optional = false
-python-versions = ">3.7"
-files = [
- {file = "pytest_check-2.2.0-py3-none-any.whl", hash = "sha256:9769c9a13889d1f3298ee6b0ff0b84b59c8af4d927f7d52d070513601b9979c2"},
- {file = "pytest_check-2.2.0.tar.gz", hash = "sha256:ec93701d930de5a628957cd54fe918e324da1b811040e64cb70cae2857cf26ff"},
-]
-
-[package.dependencies]
-pytest = "*"
-
[[package]]
name = "pytest-cov"
version = "4.1.0"
@@ -843,13 +829,13 @@ files = [
[[package]]
name = "virtualenv"
-version = "20.24.0"
+version = "20.24.1"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
- {file = "virtualenv-20.24.0-py3-none-any.whl", hash = "sha256:18d1b37fc75cc2670625702d76849a91ebd383768b4e91382a8d51be3246049e"},
- {file = "virtualenv-20.24.0.tar.gz", hash = "sha256:e2a7cef9da880d693b933db7654367754f14e20650dc60e8ee7385571f8593a3"},
+ {file = "virtualenv-20.24.1-py3-none-any.whl", hash = "sha256:01aacf8decd346cf9a865ae85c0cdc7f64c8caa07ff0d8b1dfc1733d10677442"},
+ {file = "virtualenv-20.24.1.tar.gz", hash = "sha256:2ef6a237c31629da6442b0bcaa3999748108c7166318d1f55cc9f8d7294e97bd"},
]
[package.dependencies]
@@ -864,4 +850,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "4b2ac8471c534dc6148f9ea0696b5013c8f373a46053ef1acdcd747f99e73ea3"
+content-hash = "659ae1fe23148cf6256d101e720508c3b572c5771abdd05534825c92b72c6cfc"
diff --git a/pyproject.toml b/pyproject.toml
index 05ca0f4..47228fc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -49,7 +49,6 @@ mypy = "^1.0"
pep8-naming = "^0.13"
pre-commit = "^3.0"
pytest = "^7.2"
-pytest-check = "^2.1"
pytest-cov = "^4.0"
pytest-randomly = "^3.12"
tox = "^4.4"
diff --git a/tests/test_heading_parse.py b/tests/test_heading_parse.py
new file mode 100644
index 0000000..da8dcbd
--- /dev/null
+++ b/tests/test_heading_parse.py
@@ -0,0 +1,44 @@
+from dropmate_py.parser import ColumnIndices
+
+SAMPLE_FULL_HEADER = "serial_number,uid,battery,device_health, firmware_version,log_timestamp,log_altitude,total_flights,flights_over_18kft,recorded_flights,flight_index,start_time_utc,end_time_utc,start_barometric_altitude_msl_ft,end_barometric_altitude_msl_ft,dropmate_internal_time,last_scanned_time"
+
+
+def test_parse_indices_full_header() -> None:
+ TRUTH_INDICES = ColumnIndices(
+ serial_number=0,
+ uid=1,
+ battery=2,
+ device_health=3,
+ firmware_version=4,
+ start_time_utc=11,
+ end_time_utc=12,
+ start_barometric_altitude_msl_ft=13,
+ end_barometric_altitude_msl_ft=14,
+ dropmate_internal_time=15,
+ last_scanned_time=16,
+ )
+
+ indices = ColumnIndices.from_header(SAMPLE_FULL_HEADER)
+ assert indices == TRUTH_INDICES
+
+
+SAMPLE_OLD_HEADER = "serial_number,uid,battery,log_timestamp,log_altitude,total_flights,prior_flights,flights_over_18kft,recorded_flights,flight_index,start_time_utc,end_time_utc,start_barometric_altitude_msl_ft,end_barometric_altitude_msl_ft"
+
+
+def test_parse_indices_old_header() -> None:
+ TRUTH_INDICES = ColumnIndices(
+ serial_number=0,
+ uid=1,
+ battery=2,
+ device_health=-1,
+ firmware_version=-1,
+ start_time_utc=10,
+ end_time_utc=11,
+ start_barometric_altitude_msl_ft=12,
+ end_barometric_altitude_msl_ft=13,
+ dropmate_internal_time=-1,
+ last_scanned_time=-1,
+ )
+
+ indices = ColumnIndices.from_header(SAMPLE_OLD_HEADER)
+ assert indices == TRUTH_INDICES
diff --git a/tests/test_hello.py b/tests/test_hello.py
deleted file mode 100644
index 36a5248..0000000
--- a/tests/test_hello.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def test_placeholder() -> None:
- ...