v0.20.0 -- Full s3 event parse and PDF creation. #28

Merged
serversdown merged 46 commits from dev into main 2026-05-28 17:54:34 -04:00
2 changed files with 46 additions and 14 deletions
Showing only changes of commit 6381dcb312 - Show all commits
+12 -1
View File
@@ -2,10 +2,21 @@ FROM python:3.11-slim
WORKDIR /app
# tzdata is required for the TZ env var to take effect (python:slim
# omits the timezone database). Without it, datetime.now() / logging
# / matplotlib all stay in UTC regardless of TZ. Default zone gets
# set further down via ENV; users override per-deployment via the
# `TZ` env var in docker-compose.
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
apt-get install -y --no-install-recommends curl tzdata && \
rm -rf /var/lib/apt/lists/*
# Default display timezone — applied to server logs, datetime.now(),
# matplotlib rendered timestamps, and any naïve-vs-aware datetime
# conversions in the PDF renderer. Override via TZ env var in
# docker-compose; storage in the DB is always UTC regardless.
ENV TZ=America/New_York
COPY pyproject.toml requirements.txt ./
COPY minimateplus ./minimateplus
COPY micromate ./micromate
+34 -13
View File
@@ -348,9 +348,11 @@ def render_event_report_pdf(rd: ReportData) -> bytes:
# Page footer (common to both layouts) — Created date + event id.
# Pushed to the very page bottom so it doesn't collide with the
# waveform footer scale / trigger legend lines just above.
# Convert UTC server_received_at to local for display.
created_local = _fmt_iso_to_bw(rd.server_received_at) if rd.server_received_at else ""
fig.text(
0.07, 0.005,
f"Created: {rd.server_received_at or ''} • seismo-relay",
f"Created: {created_local} • seismo-relay",
fontsize=6, color="#888", ha="left",
)
fig.text(
@@ -419,31 +421,50 @@ def _render_histogram_layout(fig, rd: ReportData) -> None:
_draw_histogram_subplot(fig, gs[3], rd)
def _to_display_local(iso: str):
"""Parse an ISO timestamp and return a datetime in the system's local
timezone (set by the TZ env var, default America/New_York via the
Dockerfile).
Behaviour:
- "...Z" or "...+HH:MM" suffix → tz-aware UTC → converted to local
- Naïve "YYYY-MM-DDTHH:MM:SS" (no tz) → returned as-is. This
matches the convention used elsewhere in seismo-relay: BW's
recorded-at timestamps are naïve and ALREADY in the unit's
local clock; we don't second-guess them.
"""
import datetime as _dt
dt = _dt.datetime.fromisoformat(iso.replace("Z", "+00:00"))
if dt.tzinfo is not None:
# Convert from UTC (or other tz) → local per the TZ env var.
# astimezone() without arg uses the system timezone.
dt = dt.astimezone()
return dt
def _fmt_iso_to_bw(iso: Optional[str]) -> Optional[str]:
"""Convert a ISO-8601 timestamp like '2026-05-16T22:30:37' to BW's
display format '22:30:37 May 16, 2026'. Returns input unchanged if
it doesn't look like ISO."""
"""Convert an ISO-8601 timestamp to BW's display format
'22:30:37 May 16, 2026'. UTC inputs (with Z suffix) are
converted to the system's local timezone first; naïve inputs
are formatted as-is. Returns input unchanged on parse failure."""
if not iso or "T" not in iso:
return iso
try:
import datetime as _dt
dt = _dt.datetime.fromisoformat(iso.replace("Z", "+00:00"))
return dt.strftime("%H:%M:%S %B %d, %Y").replace(" 0", " ")
return _to_display_local(iso).strftime("%H:%M:%S %B %d, %Y").replace(" 0", " ")
except Exception:
return iso
def _split_iso_to_date_time(iso: Optional[str]) -> tuple[Optional[str], Optional[str]]:
"""Split an ISO timestamp into BW-formatted ("May 27 /26", "06:06:14")
"""Split an ISO timestamp into BW-formatted ('May 27 /26', '06:06:14')
date+time strings. Used for the histogram stats table where the
Date and Time rows are presented separately. Returns (None, None)
if the input isn't a valid ISO datetime."""
Date and Time rows are presented separately. UTC inputs are
converted to local time first. Returns (None, None) on parse failure."""
if not iso:
return (None, None)
try:
import datetime as _dt
dt = _dt.datetime.fromisoformat(iso.replace("Z", "+00:00"))
# BW format: "May 27 /26" (3-letter month + 2-digit year)
dt = _to_display_local(iso)
# BW format: 'May 27 /26' (3-letter month + 2-digit year)
date_str = dt.strftime("%b %d /%y").replace(" 0", " ")
time_str = dt.strftime("%H:%M:%S")
return (date_str, time_str)