You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(corrections): add source and accept to the log, extract a shared correction form - #307
Adds the groundwork that the check-page correction work in #295 builds on. Closes#296 (stack position 1 of 11; #297 branches from here).
source column in corr_log_{alias}, backfilled to corrections_page when an existing log is read. The empty-log schemas now include every column, status and status_reason among them.
accept action. It records check_type, KEY, column (null for GPS), the value at the time of acceptance and a required reason. Replay and the generated replication script skip accept rows. correction_log.csv keeps them.
Active-acceptance helper. An acceptance stays active only while the current value still matches the stored one. For GPS, latitude and longitude must both still match.
Shared correction form, utils/correction_form.py. It takes a prefilled KEY, column and value, the allowed actions, a source and a widget-key namespace, and it can apply several entries as one all-or-nothing step. The Corrections page now uses it.
Fix:CorrectionProcessor cache keys now include project_id.
Fix: a new value of "0" now enables Apply.
Correction Log shows the Source and check type. "Remove correction step" can remove accept rows.
src/datasure/
├── processing/
+│ ├── correction_log.py # log schema, source/accept constants, backfill
│ └── corrections.py # + accept_value, apply_corrections, active acceptances
├── replication/
│ ├── package_builder.py # skip accept rows in README counts
│ └── script_generators.py # skip accept rows in generated script
├── utils/
+│ └── correction_form.py # shared action / new value / reason form
└── views/
└── correction_view.py # now renders the shared form
Why is this change needed? 🤔
Every check page in #295 (outliers, constraints, backchecks, duplicates, GPS) needs to write corrections and acceptances into the same log from its own tab. That needs a reusable form, a way to record where each entry came from, and an accept action that doesn't change the data. It also fixes two existing bugs:
Two projects that share an alias were sharing cached corrected data, because _self is not hashed by st.cache_data.
The Apply button used a truthiness check, so "0" counted as a missing value.
How was this implemented? 🛠️
Log schema. The schema, the action names and the backfill now live in processing/correction_log.py. ensure_log_columns extends the old _ensure_status_columns backfill.
Accept rows are written to the log like any other entry. _reapply_all_corrections skips them, and so do the replication script and the README summary.
Cache keys. The four cached CorrectionProcessor methods use hash_funcs so that project_id is part of the cache key.
Apply button.should_enable_apply_button now checks new_value is not None and new_value != "". An empty string is deliberately not a valid modify value; USER_GUIDE.md points users to "remove value" for that case.
Atomic apply.apply_corrections saves the corrected data and then the log. If saving the log fails, it restores the corrected data.
Corrections page. It passes its existing _handle_apply_correction as on_apply, so the page behaves as before.
Known follow-ups from the pre-PR review (not blocking this PR):
accept_value does not check that the KEY or column exists, but apply_corrections does.
validate_correction_input compares KEY values without converting them to text, while the acceptance check converts first. That could matter for KEY columns that aren't text.
The backfill happens when a log is read, not as a migration on disk.
test_failed_log_save_leaves_corrected_data_unchanged fails if run together with only tests/views/test_correction_view.py, because the views conftest mocks Streamlit. It passes on its own and in the full suite.
How to test or reproduce ? 🧪
just test passes: 3333 passed, 6 skipped. just pre-commit-run passes. The new tests cover:
Schema backfill: a legacy log without source loads with source = corrections_page and no data loss.
Accept:corrected data is unchanged; replay skips the row and the generated script leaves it out; after the value changes, the acceptance shows as inactive (for GPS, when either latitude or longitude changes).
Cache: two processors with the same alias and different project_ids return different corrected data. The tests use the real st.cache_data.
Apply button:"0" enables Apply; None and "" do not.
Shared form: the tests are in tests/views/test_correction_form.py; the existing tests/views/test_correction_view.py suite still passes.
Manual check:
Open a project with an existing correction log. The Correction Log shows a Source column set to corrections_page for old rows.
On the Corrections page, modify a value to 0. Apply is enabled and the change is saved.
Open two projects that share a form alias. Each one shows its own corrected data.
Screenshots (if applicable) 📷
Orange Box - Shows the source column with backfilled values for previous entries
Yellow Box - Shows successful modification of value to 0
I have updated/added tests to cover my changes (if applicable)
I have updated/added requirements to cover my changes (if applicable): N/A, no new dependencies
I have run linting and formatting on any code changes (if applicable)
I have updated the documentation (README, etc.) accordingly: CHANGELOG.md, docs/ARCHITECTURE.md, docs/USER_GUIDE.md
I have reviewed and resolved any merge conflict
Reviewer Emoji Legend
:code:
Meaning
😃👍💯
:smiley::+1::100:
I like this...
...and I want the author to know it! This is a way to highlight positive parts of a code review.
⭐⭐⭐
:star: :star: :star:
Important to fix before PR can be approved...
And I am providing reasons why it needs to be addressed as well as suggested improvements.
⭐⭐
:star: :star:
Important to fix but non-blocking for PR approval...
And I am providing suggestions where it could be improved either in this PR or later.
⭐
:star:
Give this some thought but non-blocking for PR approval...
...and consider this a suggestion, not a requirement.
❓
:question:
I have a question.
This should be a fully formed question with sufficient information and context that requires a response.
📝
:memo:
This is an explanatory note, fun fact, or relevant commentary that does not require any action.
⛏
:pick:
This is a nitpick.
This does not require any changes and is often better left unsaid. This may include stylistic, formatting, or organization suggestions and should likely be prevented/enforced by linting if they really matter
♻️
:recycle:
Suggestion for refactoring.
Should include enough context to be actionable and not be considered a nitpick.
… correction form
- Add a `source` column to corr_log_{alias}, backfilled to
`corrections_page` for existing logs; give empty-log schemas the full
column set (including status and status_reason)
- Add an `accept` action that records check_type, KEY, column and the
current value with a required reason; replay and the replication
script skip accept rows, correction_log.csv keeps them
- Add a helper returning active acceptances per alias and check type
(GPS requires both latitude and longitude to still match)
- Extract the action/new-value/reason inputs into
utils/correction_form.py with prefilled values, allowed actions,
source, widget-key namespace and multi-entry atomic apply; the
Corrections page now uses it
- Fix: include project_id in CorrectionProcessor cache keys so projects
sharing an alias no longer share cached data
- Fix: a new value of "0" now enables Apply; only None/empty is missing
- Show Source and check type in the Correction Log; accept rows can be
removed
Refs #296
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validate accepted value matches current row before logging
src/datasure/processing/corrections.py:658
The acceptance path only verifies that the KEY exists; it never compares entry.current_value with the current row. A stale form can therefore log an acceptance that is immediately inactive (and the log does not contain the value at the time of acceptance). Before returning, derive the snapshot from data or reject a mismatch for the selected column—and for both GPS coordinates.
…e data
accept_value and the accept branch of apply_corrections now check the
value being accepted against the corrected data before logging it. A
stale value (changed since it was flagged), an unknown KEY or an unknown
column raises ValueError and nothing is logged. Both paths share the
check used by get_active_acceptances, so an acceptance is active as soon
as it is saved. accept_value takes a key_col argument for the lookup.
Addresses Copilot review on #307.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
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.
Pull Request Summary 🚀
What does this PR do? 📝
Adds the groundwork that the check-page correction work in #295 builds on. Closes #296 (stack position 1 of 11; #297 branches from here).
sourcecolumn incorr_log_{alias}, backfilled tocorrections_pagewhen an existing log is read. The empty-log schemas now include every column,statusandstatus_reasonamong them.acceptaction. It recordscheck_type, KEY, column (null for GPS), the value at the time of acceptance and a required reason. Replay and the generated replication script skip accept rows.correction_log.csvkeeps them.utils/correction_form.py. It takes a prefilled KEY, column and value, the allowed actions, asourceand a widget-key namespace, and it can apply several entries as one all-or-nothing step. The Corrections page now uses it.CorrectionProcessorcache keys now includeproject_id."0"now enables Apply.Why is this change needed? 🤔
Every check page in #295 (outliers, constraints, backchecks, duplicates, GPS) needs to write corrections and acceptances into the same log from its own tab. That needs a reusable form, a way to record where each entry came from, and an accept action that doesn't change the data. It also fixes two existing bugs:
_selfis not hashed byst.cache_data."0"counted as a missing value.How was this implemented? 🛠️
processing/correction_log.py.ensure_log_columnsextends the old_ensure_status_columnsbackfill._reapply_all_correctionsskips them, and so do the replication script and the README summary.CorrectionProcessormethods usehash_funcsso thatproject_idis part of the cache key.should_enable_apply_buttonnow checksnew_value is not None and new_value != "". An empty string is deliberately not a valid modify value; USER_GUIDE.md points users to "remove value" for that case.apply_correctionssaves the corrected data and then the log. If saving the log fails, it restores the corrected data._handle_apply_correctionason_apply, so the page behaves as before.Known follow-ups from the pre-PR review (not blocking this PR):
apply_correctionsyet; its first real users are Corrections: resolve survey and backcheck ID problems from the duplicate cards #303 and Corrections: correct or accept GPS outliers from the GPS page #304.accept_valuedoes not check that the KEY or column exists, butapply_correctionsdoes.validate_correction_inputcompares KEY values without converting them to text, while the acceptance check converts first. That could matter for KEY columns that aren't text.test_failed_log_save_leaves_corrected_data_unchangedfails if run together with onlytests/views/test_correction_view.py, because the views conftest mocks Streamlit. It passes on its own and in the full suite.How to test or reproduce ? 🧪
just testpasses: 3333 passed, 6 skipped.just pre-commit-runpasses. The new tests cover:sourceloads withsource = corrections_pageand no data loss.correcteddata is unchanged; replay skips the row and the generated script leaves it out; after the value changes, the acceptance shows as inactive (for GPS, when either latitude or longitude changes).project_ids return different corrected data. The tests use the realst.cache_data."0"enables Apply;Noneand""do not.tests/views/test_correction_form.py; the existingtests/views/test_correction_view.pysuite still passes.Manual check:
corrections_pagefor old rows.0. Apply is enabled and the change is saved.Screenshots (if applicable) 📷
Orange Box - Shows the source column with backfilled values for previous entries
Yellow Box - Shows successful modification of value to 0
Checklist ✅
src/and about 1,170 lines of new tests. Corrections: add source and accept to the log, extract a shared correction form #296 is scoped as one foundation step: the schema, accept, form extraction and both fixes are all needed before Corrected data goes stale when prep steps change #297 can branch from it. More than half of thesrc/diff comes from moving the form out ofcorrection_view.pyintocorrection_form.py.Reviewer Emoji Legend
:code::smiley::+1::100:...and I want the author to know it! This is a way to highlight positive parts of a code review.
:star: :star: :star:And I am providing reasons why it needs to be addressed as well as suggested improvements.
:star: :star:And I am providing suggestions where it could be improved either in this PR or later.
:star:...and consider this a suggestion, not a requirement.
:question:This should be a fully formed question with sufficient information and context that requires a response.
:memo::pick:This does not require any changes and is often better left unsaid. This may include stylistic, formatting, or organization suggestions and should likely be prevented/enforced by linting if they really matter
:recycle:Should include enough context to be actionable and not be considered a nitpick.
🤖 Generated with Claude Code