fix!: v4 correctness pass — truncated 300 rows, missing-vs-zero readings, typed errors, dead sql removal - #111
Draft
cohenrobinson wants to merge 3 commits into
Draft
fix!: v4 correctness pass — truncated 300 rows, missing-vs-zero readings, typed errors, dead sql removal#111cohenrobinson wants to merge 3 commits into
cohenrobinson wants to merge 3 commits into
Conversation
…ad sql package Correctness and cleanup pass for v4.0.0: - 300 rows carrying more interval values than the parent 200 record's IntervalLength allows now raise NEM12ParseError instead of being silently truncated; validate_file checks 300 field counts too. - Blank numeric cells parse to None, not 0.0, across NEM12 and NEM13. - Remaining raw ValueErrors at the parse boundary are wrapped as NEM12ParseError, naming the offending field and value. - 400 StartInterval / EndInterval are bound-checked against the NMI. - spec.DIRECTION_INDICATORS descriptions corrected. - The dead aemo_mdff_reader.sql subpackage and [mysql] extra are removed. BREAKING CHANGE: reading values are float | None; the [mysql] extra and aemo_mdff_reader.sql (QueryBuilder, Storer) are removed; files with over-long 300 rows, non-numeric values, impossible dates or out-of-range 400 bounds now raise NEM12ParseError where they previously parsed. Claude-Session: https://claude.ai/code/session_01SdzSFrjvRTUyjpAPCvJpGC
Contributor
Author
|
Hot-path check (template asks for it — the columnar loop changed): `python benchmarks/bench_parser.py --nmis 2 --days 90 --interval-minutes 5`, same machine, back to back.
No regression — the added try/except around |
StartInterval and EndInterval were validated independently, so a range running backwards passed: 400,10,5,S yielded start=10, end=5, a range that is valid field-by-field but meaningless to any consumer applying it to an interval array. Deriving intervals-per-day also lacked the divisibility guard used at every other site, so an IntervalLength that does not divide the day truncated the ceiling and rejected valid intervals. Treat that ceiling as unknown and keep enforcing the floor. Claude-Session: https://claude.ai/code/session_01SdzSFrjvRTUyjpAPCvJpGC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft. Breaking major (4.0.0). Each finding below has a test that fails on
mainand passes here.Baseline on
main: 163 tests,mypy --strictclean,ruff checkclean. Here: 175 tests pass (3 removed withtests/test_sql.py, 15 added/changed), mypy/ruff/format all clean.1. Silent truncation of over-long 300 rows
Before:
_emit_intervalsandparse_to_columnsonly checkedlen(row) >= 2 + n. A 15-minute file under a stale 30-minute 200 header emitted 48 readings, silently dropped the other 48, and readquality_methodout of a dropped reading value.validate_filedid not check 300 field counts at all — it returned clean for a 30-minute NMI carrying 288 values.After: a 300 row may carry at most
INTERVAL_TRAILER_FIELDS = 5trailing fields after the values (QualityMethod, ReasonCode, ReasonDescription, UpdateDateTime, MSATSLoadDateTime — MDFF v2.6 §4.4). Anything longer raisesNEM12ParseErrornaming the NMI, the interval date, the actual value count and the expected one.validate_filereports both too-short and too-long 300 rows (skipped when IntervalLength does not divide a day, which_parse_nmistill tolerates).2. Blanks must not become
0.0(breaking)Before:
_parse_float("")returned0.0— indistinguishable from a genuine zero reading in a billing input — for NEM12 300 values and NEM13previous_register_read/current_register_read/quantity.After: reading values are
float | None, propagated throughIntervalReading,AccumulationReading, the columnar/pandas/parquet paths and the CSV writer (a missing value writes an empty cell).aggregate.daily_totalsskipsNonerather than summing it as zero, excludes it frominterval_count, and reports it in the newDailyTotal.missing_count— a day withmissing_count > 0is an incomplete total, not a lower one.mypy --strictstill passes.3. Raw
ValueErrorescaping the typed-error boundaryBefore:
NEM12ParseErroris the advertised contract, but_parse_int,_parse_float, the columnarfloat(cell),_parse_interval_event'sint(row[1])/int(row[2]), and the fixed-widthdatetime(...)constructor all leaked bareValueError— e.g.N/Ain a value cell,400,x,10,S, or300,20049999→ "month must be in 1..12".After: each is
raise NEM12ParseError(...) from exc, with the offending field and value in the message. Tests assertNEM12ParseErrorspecifically.4. Swapped direction constants
spec.DIRECTION_INDICATORShad Import and Export the wrong way round. Import is grid → connection point; Export is connection point → grid.5. Deleted the dead
sql/subpackagesql/store.pycalledreader.data.VersionHeader/.subrecords/subsubrecord.__getattr__, none of which exist onNEMReader— every entry point raisedAttributeErrorimmediately. It was excluded from mypy and coverage yet still shipped, andQueryBuilder.insert_queryinterpolated unescaped file-derived values into SQL (injection surface viaReasonDescription). Removed the subpackage,tests/test_sql.py, the[mysql]extra, and the now-redundant mypy-override / coverage-omit / ruff per-file-ignore / bandit-exclude / dependabot entries, plus the README, SECURITY.md and requirements.txt references.Also
400
StartInterval/EndIntervalare bound-checked against the NMI's intervals per day —400,1,999,Spreviously parsed happily against a 48-interval day.Not done (out of scope, as agreed): applying 400-record events to interval quality — that's a design change, not a correctness fix.
Verification
Coverage 92.42% (gate: 90%).
https://claude.ai/code/session_01SdzSFrjvRTUyjpAPCvJpGC