Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,9 @@
// Test --update_ssds_provenance with a short log_file
//"args": ["-v", "1", "--log_file", "ahi/missionlogs/2025/20250128_20250131/20250131T051404/202501310514_202501310535.nc4", "--update_ssds_provenance"]
// Test ahi mission that has Backseat Planktivore data with --update_ssds_provenance
//"args": ["-v", "1", "--log_file", "ahi/missionlogs/2025/20250414_20250418/20250415T040019/202504150400_202504152346.nc4", "--update_ssds_provenance"]
"args": ["-v", "1", "--log_file", "ahi/missionlogs/2025/20250414_20250418/20250415T040019/202504150400_202504152346.nc4", "--update_ssds_provenance"]
// Make per log file .html files to test with lrauv_deployment_plots and --update_ssds_provenance
"args": ["-v", "1", "--auv_name", "ahi", "--start", "20251022T000000", "--end", "20251024T000000", "--update_ssds_provenance", "--clobber"]
//"args": ["-v", "1", "--auv_name", "ahi", "--start", "20251022T000000", "--end", "20251024T000000", "--update_ssds_provenance", "--clobber"]
},
{
"name": "lrauv_deployment_plots",
Expand All @@ -484,13 +484,13 @@
// ahi planktivore deployment April 2025 add --update_ssds_provenance
//"args": ["-v", "1", "--dlist", "ahi/missionlogs/2025/20250414_20250418.dlist", "--update_ssds_provenance"]
// Test time range of DeploymentPlots with ahi planktivore deployment April 2025
//"args": ["-v", "1", "--auv_name", "ahi", "--start", "20251001", "--end", "20251231", "--update_ssds_provenance", "--force"]
"args": ["-v", "1", "--auv_name", "ahi", "--start", "20250401", "--end", "20250501", "--update_ssds_provenance", "--force"]
// Test web page building with a short deployment
//"args": ["-v", "1", "--dlist", "ahi/missionlogs/2025/20251022_20251024.dlist", "--update_ssds_provenance"]
// Test --force option for rebuilding web pages with a short deployment
//"args": ["-v", "1", "--last_n_days", "10", "--update_ssds_provenance", "--force"]
// Test --notify option
"args": ["-v", "1", "--dlist", "ahi/missionlogs/2025/20251022_20251024.dlist", "--update_ssds_provenance", "--force", "--notify", "mccann@mbari.org"]
//"args": ["-v", "1", "--dlist", "ahi/missionlogs/2025/20251022_20251024.dlist", "--update_ssds_provenance", "--force", "--notify", "mccann@mbari.org"]

},

Expand Down
3 changes: 3 additions & 0 deletions src/data/create_products.py
Original file line number Diff line number Diff line change
Expand Up @@ -2030,6 +2030,7 @@ def plot_2column(self) -> str: # noqa: C901, PLR0912, PLR0915
if self.output_dir is not None
else Path(BASE_LRAUV_PATH, f"{Path(self.log_file).parent}")
)
out_dir.mkdir(parents=True, exist_ok=True)
stem = (
self.plot_name_stem if self.plot_name_stem is not None else Path(self.log_file).stem
)
Expand Down Expand Up @@ -2170,6 +2171,7 @@ def plot_biolume_2column(self) -> str: # noqa: C901, PLR0912, PLR0915
if self.output_dir is not None
else Path(BASE_LRAUV_PATH, f"{Path(self.log_file).parent}")
)
out_dir.mkdir(parents=True, exist_ok=True)
stem = (
self.plot_name_stem if self.plot_name_stem is not None else Path(self.log_file).stem
)
Expand Down Expand Up @@ -2311,6 +2313,7 @@ def plot_planktivore_2column(self) -> str: # noqa: C901, PLR0912, PLR0915
if self.output_dir is not None
else Path(BASE_LRAUV_PATH, f"{Path(self.log_file).parent}")
)
out_dir.mkdir(parents=True, exist_ok=True)
stem = (
self.plot_name_stem if self.plot_name_stem is not None else Path(self.log_file).stem
)
Expand Down
100 changes: 58 additions & 42 deletions src/data/lrauv_deployment_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def plot_deployment( # noqa: C901, PLR0912, PLR0913, PLR0915
# Deployment name (spaces → underscores for filenames)
raw_name = self._parse_deployment_name(dlist_content) if dlist_content else None
if raw_name:
plot_name_stem = raw_name.replace(" ", "_")
plot_name_stem = raw_name.replace(" ", "_").replace("/", "_")
self.logger.info("Deployment name: %s", raw_name)
else:
plot_name_stem = dlist_rel.stem
Expand Down Expand Up @@ -367,6 +367,7 @@ def _build_and_write_html( # noqa: PLR0913
nc_files,
auv_name=_auv_name,
png_file_path=Path(png_path),
other_png_paths=[p for p in png_paths if p != png_path],
)
self.logger.info("Per-PNG HTML written to %s", per_png_html)
archiver = Archiver(add_handlers=True, clobber=True)
Expand Down Expand Up @@ -527,16 +528,6 @@ def _stoqs_url_for_nc_url(self, nc_url: str, auv_name: str) -> str | None:
self.logger.debug("Could not generate per-log STOQS URL for %s: %s", nc_url, exc)
return None

def _per_log_html_url(self, nc_urls: list[str]) -> str:
"""Return the first reachable per-log HTML URL for a list of nc URLs, or empty string."""
for nc_url in nc_urls:
candidate = nc_url.replace(
LRAUV_OPENDAP_BASE.rstrip("/"), BASE_LRAUV_WEB.rstrip("/")
).replace(f"_{FREQ}.nc", f"_{FREQ}.html")
if self._url_exists(candidate):
return candidate
return ""

def _per_log_stoqs_url(
self, nc_urls: list[str], auv_name: str, fallback: str | None
) -> str | None:
Expand All @@ -548,15 +539,15 @@ def _per_log_stoqs_url(
return url
return fallback

def _per_log_png_links(self, nc_urls: list[str]) -> str:
"""Return HTML anchor tags for each existing per-log PNG, pipe-separated."""
parts: list[str] = []
def _per_log_png_links(self, nc_urls: list[str]) -> list[tuple[str, str]]:
"""Return (url, label) pairs for each existing per-log PNG."""
parts: list[tuple[str, str]] = []
for nc_url in nc_urls:
for png_url in self._png_urls_for_nc(nc_url):
if self._url_exists(png_url):
pname = png_url.rsplit("/", 1)[1]
parts.append(f'<a href="{png_url}">{pname}</a>')
return " | ".join(parts)
parts.append((png_url, pname))
return parts

def _write_per_png_html( # noqa: C901, PLR0913
self,
Expand All @@ -568,6 +559,7 @@ def _write_per_png_html( # noqa: C901, PLR0913
nc_files: list[str],
auv_name: str = "",
png_file_path: Path | None = None,
other_png_paths: list[str] | None = None,
) -> None:
"""Write a plain HTML page for one deployment PNG.

Expand All @@ -581,37 +573,59 @@ def _write_per_png_html( # noqa: C901, PLR0913
log_dir = url.rsplit("/", 2)[1]
grouped.setdefault(log_dir, []).append(url)

log_items = ""
# Collect per-row data first so we can suppress empty columns
rows: list[dict] = []
for log_dir in sorted(grouped):
nc_urls = grouped[log_dir]
per_log_html_url = self._per_log_html_url(nc_urls)
log_stoqs_url = self._per_log_stoqs_url(nc_urls, auv_name, stoqs_url)

links = ""
if per_log_html_url:
links += f' <a href="{per_log_html_url}">image</a>'
png_links = self._per_log_png_links(nc_urls)
if png_links:
if links:
links += " | "
links += png_links
for nc_url in nc_urls:
dap_form_url = nc_url + ".html"
if links:
links += " | "
links += f'<a href="{dap_form_url}">OPeNDAP Data Access Form</a>'
if log_stoqs_url:
if links:
links += " | "
links += f'<a href="{log_stoqs_url}">STOQS</a>'
rows.append(
{
"dir": log_dir,
"plots": "<br>".join(f'<a href="{u}">{lbl}</a>' for u, lbl in png_links),
"dap": "".join(f'<a href="{nc_url}.html">OPeNDAP</a>' for nc_url in nc_urls),
"stoqs": f'<a href="{log_stoqs_url}">STOQS</a>' if log_stoqs_url else "",
}
)

log_items += f" <li>{log_dir} — {links}</li>\n"
# Only show columns that have at least one non-empty cell
show = {col: any(r[col] for r in rows) for col in ("plots", "dap", "stoqs")}
col_labels = {"plots": "Quick Look Plots", "dap": "OPeNDAP", "stoqs": "STOQS"}

header_cells = "<th>Log directory</th>" + "".join(
f"<th>{col_labels[c]}</th>" for c in col_labels if show[c]
)
log_rows = ""
for r in rows:
data_cells = "".join(f"<td>{r[c]}</td>" for c in col_labels if show[c])
log_rows += f" <tr><td>{r['dir']}</td>{data_cells}</tr>\n"

log_table = (
" <table>\n"
f" <thead><tr>{header_cells}</tr></thead>\n"
" <tbody>\n"
f"{log_rows}"
" </tbody>\n"
" </table>\n"
)

other_plots_line = ""
if other_png_paths:
sibling_links = [
f'<a href="{Path(p).with_suffix(".html").name}">{Path(p).name}</a>'
for p in other_png_paths
if Path(p).exists()
]
if sibling_links:
other_plots_line = (
"<p>Other plots for this deployment: " + " | ".join(sibling_links) + "</p>\n"
)

stoqs_line = ""
if stoqs_url:
after_scheme = stoqs_url.split("//", 1)[-1] if "//" in stoqs_url else stoqs_url
db_label = after_scheme.split("/")[1] if "/" in after_scheme else after_scheme
stoqs_line = f'<p><a href="{stoqs_url}">View these data in {db_label}</a></p>\n'
stoqs_line = f'<p>View these data in <a href="{stoqs_url}">{db_label}</a></p>\n'

if png_file_path is not None and png_file_path.exists():
b64 = base64.b64encode(png_file_path.read_bytes()).decode("ascii")
Expand All @@ -638,11 +652,10 @@ def _write_per_png_html( # noqa: C901, PLR0913
"<body>\n"
f" <h1>{html_title_single}</h1>\n"
f' <img src="{img_src}" alt="{png_name}">\n'
f" {other_plots_line}"
f" {stoqs_line}"
" <h2>Log files</h2>\n"
" <ul>\n"
f"{log_items}"
" </ul>\n"
f"{log_table}"
f"{footer}"
"</body>\n"
"</html>\n"
Expand Down Expand Up @@ -680,8 +693,11 @@ def _dlist_list( # noqa: C901, PLR0912
window. The sibling ``.dlist`` file (``YYYYMMDD_YYYYMMDD.dlist``)
is returned for each matching directory.
"""
_vol = Path(LRAUV_VOL)
base = _vol if _vol.is_dir() else Path(BASE_LRAUV_PATH)
base = Path(LRAUV_VOL)
if not base.is_dir():
self.logger.error("%s does not exist.", LRAUV_VOL)
self.logger.info("Is %s mounted?", LRAUV_VOL)
sys.exit(1)
dlists: list[str] = []
auv_dirs = (
sorted(base.glob("*/missionlogs/"))
Expand Down
32 changes: 6 additions & 26 deletions src/data/test_lrauv_deployment_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def test_per_log_html_link_when_url_exists(self, dp, tmp_path):
None,
[_NC_URL],
)
assert "image" in html_path.read_text() # noqa: S101
assert "2column_cmocean" in html_path.read_text() # noqa: S101

def test_per_log_png_links_included(self, dp, tmp_path):
html_path = tmp_path / "depl.html"
Expand All @@ -220,7 +220,7 @@ def test_per_log_png_links_included(self, dp, tmp_path):
patch.object(
dp,
"_per_log_png_links",
return_value=f'<a href="{fake_png_url}">ahi_1S_2column_cmocean.png</a>',
return_value=[(fake_png_url, "ahi_1S_2column_cmocean.png")],
),
patch.object(dp, "_stoqs_url_for_nc_url", return_value=None),
):
Expand Down Expand Up @@ -279,25 +279,6 @@ def test_footer_contains_script_link_and_timestamp(self, dp, tmp_path):
# ---------------------------------------------------------------------------


class TestPerLogHtmlUrl:
def test_returns_first_reachable(self, dp):
# Compute the expected candidate the same way _per_log_html_url does
from nc42netcdfs import BASE_LRAUV_WEB # noqa: PLC0415
from resample import FREQ, LRAUV_OPENDAP_BASE # noqa: PLC0415

nc_url_a = _NC_URL
expected = nc_url_a.replace(
LRAUV_OPENDAP_BASE.rstrip("/"), BASE_LRAUV_WEB.rstrip("/")
).replace(f"_{FREQ}.nc", f"_{FREQ}.html")
with patch.object(dp, "_url_exists", side_effect=lambda u: u == expected):
result = dp._per_log_html_url([nc_url_a])
assert result == expected # noqa: S101

def test_returns_empty_when_none_reachable(self, dp):
with patch.object(dp, "_url_exists", return_value=False):
assert dp._per_log_html_url([_NC_URL]) == "" # noqa: S101


class TestPerLogStoqsUrl:
def test_returns_nc_scoped_url_when_available(self, dp):
scoped = _STOQS_URL.replace("abc123", "scoped")
Expand All @@ -322,17 +303,16 @@ class TestPerLogPngLinks:
def test_returns_links_for_existing_pngs(self, dp):
with patch.object(dp, "_url_exists", return_value=True):
result = dp._per_log_png_links([_NC_URL])
assert "2column_cmocean" in result # noqa: S101
assert "<a href=" in result # noqa: S101
assert any("2column_cmocean" in lbl for _, lbl in result) # noqa: S101

def test_returns_empty_when_no_pngs_exist(self, dp):
with patch.object(dp, "_url_exists", return_value=False):
assert dp._per_log_png_links([_NC_URL]) == "" # noqa: S101
assert dp._per_log_png_links([_NC_URL]) == [] # noqa: S101

def test_pipe_separated(self, dp):
def test_returns_multiple_tuples_for_multiple_kinds(self, dp):
with patch.object(dp, "_url_exists", return_value=True):
result = dp._per_log_png_links([_NC_URL])
assert " | " in result # noqa: S101
assert len(result) > 1 # noqa: S101


# ---------------------------------------------------------------------------
Expand Down
Loading