Claude commited on
Commit
2420f34
Β·
1 Parent(s): b7c0a6b

Fix docstring and remove wasteful D+1 fetch

Browse files

Now that fetch_csv_for_date downloads ALL files matching the settlement
date (both the _YYYYMMDD_ first-half and _2_YYYYMMDDHHMMSS_ second-half
ZIPs), the D+1 fetch in _fetch_day_pair was downloading two extra ZIP
files that the boundary filter immediately threw away, making every
request unnecessarily slow.

- Remove _fetch_day_pair; replace with _fetch_day_csv (single call)
- Update all three call sites in api.py accordingly
- Remove now-unused timedelta import from api.py
- Correct module docstring to document the actual two-file-per-day
naming convention: FPPMW_YYYYMMDD_<seq> (first half) and
FPPMW_2_YYYYMMDDHHMMSS_<seq+1> (second half), both sharing the
same settlement date string

https://claude.ai/code/session_01Vm5dTYdRf99LZ9MfB86vjx

Files changed (2) hide show
  1. app/routers/api.py +15 -27
  2. app/services/aemo_fetcher.py +23 -10
app/routers/api.py CHANGED
@@ -3,7 +3,7 @@ REST API endpoints for BESS SCADA data.
3
  """
4
  import json
5
  import logging
6
- from datetime import date, datetime, timedelta
7
  from pathlib import Path
8
 
9
  logger = logging.getLogger(__name__)
@@ -57,28 +57,16 @@ def get_quality_flags():
57
  return json.loads(flags_file.read_text())
58
 
59
 
60
- async def _fetch_day_pair(target_date: date, duid: str) -> tuple[bytes, bytes | None]:
61
  """
62
- Fetch CSV bytes for target_date and, if available, for target_date + 1 day.
63
-
64
- Each FPPMW daily ZIP may contain two CSV halves covering the full NEM
65
- market day (04:00–16:00 UTC and 16:00–04:00 UTC next day); both are
66
- concatenated inside fetch_csv_for_date. The next-day file is also
67
- fetched as a safety net for older single-CSV files and boundary coverage.
68
- It is fetched with skip_future_check=True (it may be today's date) and
69
- any error is silently swallowed β€” partial data is acceptable.
70
- """
71
- import asyncio as _asyncio
72
- csv_bytes = await fetch_csv_for_date(target_date, duid)
73
-
74
- next_date = target_date + timedelta(days=1)
75
- csv_next: bytes | None = None
76
- try:
77
- csv_next = await fetch_csv_for_date(next_date, duid, skip_future_check=True)
78
- except Exception as exc:
79
- logger.debug("Next-day CSV not available for %s (%s): %s", duid, next_date, exc)
80
 
81
- return csv_bytes, csv_next
 
 
 
 
 
82
 
83
 
84
  @router.get("/data")
@@ -95,8 +83,8 @@ async def get_data(
95
  ip = _get_ip(request)
96
 
97
  try:
98
- csv_bytes, csv_next = await _fetch_day_pair(target_date, duid)
99
- df = filter_and_process(csv_bytes, duid, target_date, csv_next)
100
  summary = compute_summary(df)
101
  records = to_json_records(df)
102
  log_request(ip, duid, date, "view")
@@ -125,8 +113,8 @@ async def download_csv(
125
  ip = _get_ip(request)
126
 
127
  try:
128
- csv_bytes, csv_next = await _fetch_day_pair(target_date, duid)
129
- df = filter_and_process(csv_bytes, duid, target_date, csv_next)
130
  output = to_csv_bytes(df)
131
  log_request(ip, duid, date, "download_csv")
132
  filename = f"BESS_SCADA_{duid}_{date}.csv"
@@ -152,8 +140,8 @@ async def download_parquet(
152
  ip = _get_ip(request)
153
 
154
  try:
155
- csv_bytes, csv_next = await _fetch_day_pair(target_date, duid)
156
- df = filter_and_process(csv_bytes, duid, target_date, csv_next)
157
  output = to_parquet_bytes(df)
158
  log_request(ip, duid, date, "download_parquet")
159
  filename = f"BESS_SCADA_{duid}_{date}.parquet"
 
3
  """
4
  import json
5
  import logging
6
+ from datetime import date, datetime
7
  from pathlib import Path
8
 
9
  logger = logging.getLogger(__name__)
 
57
  return json.loads(flags_file.read_text())
58
 
59
 
60
+ async def _fetch_day_csv(target_date: date, duid: str) -> bytes:
61
  """
62
+ Fetch and concatenate all FPPMW ZIP files for target_date.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ fetch_csv_for_date already finds ALL files whose name contains the
65
+ settlement date (both the first and second 12-hour halves) and
66
+ concatenates their CSV bytes. The data_processor then applies the
67
+ [04:00 AEST D, 04:00 AEST D+1) boundary filter.
68
+ """
69
+ return await fetch_csv_for_date(target_date, duid)
70
 
71
 
72
  @router.get("/data")
 
83
  ip = _get_ip(request)
84
 
85
  try:
86
+ csv_bytes = await _fetch_day_csv(target_date, duid)
87
+ df = filter_and_process(csv_bytes, duid, target_date)
88
  summary = compute_summary(df)
89
  records = to_json_records(df)
90
  log_request(ip, duid, date, "view")
 
113
  ip = _get_ip(request)
114
 
115
  try:
116
+ csv_bytes = await _fetch_day_csv(target_date, duid)
117
+ df = filter_and_process(csv_bytes, duid, target_date)
118
  output = to_csv_bytes(df)
119
  log_request(ip, duid, date, "download_csv")
120
  filename = f"BESS_SCADA_{duid}_{date}.csv"
 
140
  ip = _get_ip(request)
141
 
142
  try:
143
+ csv_bytes = await _fetch_day_csv(target_date, duid)
144
+ df = filter_and_process(csv_bytes, duid, target_date)
145
  output = to_parquet_bytes(df)
146
  log_request(ip, duid, date, "download_parquet")
147
  filename = f"BESS_SCADA_{duid}_{date}.parquet"
app/services/aemo_fetcher.py CHANGED
@@ -7,18 +7,31 @@ File naming conventions on NEMWEB:
7
  PUBLIC_NEXT_DAY_FPP_YYYYMMDD[_<suffix>].zip
8
  ZIP contains the daily CSV directly.
9
 
10
- From 11 Jan 2026 β€” FPPMW format (nested ZIP):
11
- PUBLIC_NEXT_DAY_FPPMW_YYYYMMDD[_<suffix>].zip
12
- Outer ZIP β†’ one or more inner ZIPs β†’ CSV(s).
13
- The NEM market day (04:00–04:00 AEST) is split into two 12-hour halves.
14
- The second half (16:00 AEST β†’ 04:00 AEST next calendar day) is typically
15
- published as a separate inner ZIP whose filename carries the next calendar
16
- date. All inner ZIPs must be opened and concatenated; the data_processor
17
- applies the authoritative [04:00 AEST D, 04:00 AEST D+1) boundary filter.
18
- All AEMO NEMWEB timestamps are in AEST (UTC+10), no daylight saving.
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  Current directory (https://www.nemweb.com.au/REPORTS/Current/FPPDAILY/):
21
- Individual daily files (FPP or FPPMW naming), one per market day.
22
  Keeps a long rolling history (180+ files observed).
23
 
24
  Archive directory (https://nemweb.com.au/Reports/Archive/FPPDAILY/):
 
7
  PUBLIC_NEXT_DAY_FPP_YYYYMMDD[_<suffix>].zip
8
  ZIP contains the daily CSV directly.
9
 
10
+ From 11 Jan 2026 β€” FPPMW format (two ZIPs per market day):
11
+ Each NEM market day (04:00–04:00 AEST) is split into two 12-hour
12
+ halves, each published as a separate outer ZIP. Both files carry
13
+ the same settlement date (YYYYMMDD) in their name:
14
+
15
+ First half (04:00–16:00 AEST):
16
+ PUBLIC_NEXT_DAY_FPPMW_YYYYMMDD_<seq>.zip
17
+
18
+ Second half (16:00–04:00 AEST next calendar day):
19
+ PUBLIC_NEXT_DAY_FPPMW_2_YYYYMMDDHHMMSS_<seq+1>.zip
20
+
21
+ Both are identified by searching for "FPPMW" AND "YYYYMMDD" in the
22
+ directory listing. _find_files_in returns ALL matches; fetch_csv_for_date
23
+ downloads them all and concatenates their CSV bytes.
24
+ The data_processor applies the authoritative [04:00 AEST D, 04:00 AEST D+1)
25
+ boundary filter. All AEMO NEMWEB timestamps are in AEST (UTC+10),
26
+ no daylight saving.
27
+
28
+ Inner ZIP structure (each outer ZIP):
29
+ PUBLIC_NEXT_DAY_FPPMW_*.zip ← outer ZIP (~MB)
30
+ PUBLIC_NEXT_DAY_FPPMW_*.ZIP ← inner ZIP
31
+ PUBLIC_NEXT_DAY_FPPMW_*.CSV
32
 
33
  Current directory (https://www.nemweb.com.au/REPORTS/Current/FPPDAILY/):
34
+ Two files per market day (FPPMW format), or one per day (FPP format).
35
  Keeps a long rolling history (180+ files observed).
36
 
37
  Archive directory (https://nemweb.com.au/Reports/Archive/FPPDAILY/):