feat(cli,frontend): preserve original under data/raw/ whenever output is transformed#133
Conversation
jsPsych can export the `stimulus` column as unquoted HTML containing literal `"` (e.g. `<div class = "EncodingBox">`), which violates strict RFC-4180 quoting. Previously csv-parse threw "Invalid Opening Quote" and the entire file was dropped, making such datasets unreadable end to end (observed on a 1258-file OSF working-memory dataset: 0 files read). - parseCSV sets `relax_quotes: true` so the row parses instead of being rejected. - New `parseCSVForWrite` reports whether the content was already strictly valid CSV. The CLI and frontend use it so a clean file keeps its exact bytes (verbatim), while a file that only parsed thanks to relaxation is re-serialised to well-formed CSV — otherwise the malformed bytes land in the Psych-DS data/ payload and the validator rejects them with CSV_FORMATTING_ERROR. Net: these datasets now ingest and pass Psych-DS validation through both the library/CLI and the browser uploader. Adds regression tests for the parse and the re-serialise-on-write behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… is transformed Broaden raw-original preservation beyond JSON. Previously both the CLI (processFile) and the web wizard (runGenerate) kept the untouched input under data/raw/ only for JSON/JSONL. Now they preserve it whenever the Psych-DS output is not a verbatim, same-named copy of the input: transformed = isJsonDataExt(ext) || !csvVerbatimEligible || mainName !== inputName This newly covers CSV inputs that were renamed to a compliant name (e.g. mydata.csv -> subject-x_data.csv) and CSV inputs that had to be re-serialised (malformed quotes repaired in #132). A clean CSV written byte-for-byte under its own compliant name is the only no-raw case, so nothing is duplicated. The existing flat-dir disambiguation and root .psychds-ignore (so the validator skips data/raw/) apply to these too. Tested end-to-end on the real OSF phxq4 dataset (unquoted stimulus HTML with literal quotes): files read, re-serialised output in data/, malformed originals preserved in data/raw/. Adds 3 CLI + 3 frontend tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 36e6d15 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
* docs: add repository review and prioritized improvement plan Full review of all three packages, the open PRs (#132, #133, #47), and repo infrastructure. Documents verified data-loss bugs, performance issues affecting #95, CI gaps, test coverage gaps, and feature opportunities, organized into six phased workstreams. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMjKvi9vHT3LfikGfW84Kg * docs: note open Dependabot alerts and behind-major dependency stack in plan Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMjKvi9vHT3LfikGfW84Kg --------- Co-authored-by: Claude <noreply@anthropic.com>
# Conflicts: # .changeset/csv-relax-quotes.md # packages/cli/src/data.ts # packages/frontend/src/pages/DataUpload.tsx # packages/frontend/tests/DataUpload.test.tsx # packages/metadata/src/utils.ts # packages/metadata/tests/csv-input.stress.test.ts
… trigger; cover the rename-plan path Post-merge fixes after rebasing onto main (which now contains the final #132): - Docs: 12 pages (docs/, website/docs/, package READMEs) said CSV inputs are never duplicated under data/raw/ — now describe the actual rule: the original is preserved whenever the output is not a byte-for-byte, same-named copy of the input (converted JSON, re-serialised CSVs, renamed CSVs), with the verbatim same-named clean CSV as the only no-raw case (maintainer-approved behavior). - Test: the interactive CLI reaches processFile through a renamePlan whenever any file needs renaming — the flagship real-world path, which had no coverage. New test drives processDirectory with a planRenames() plan and asserts the renamed CSV's original lands in data/raw/. - Test hygiene: mockIsValid implementation overrides now restored after each test (clearAllMocks does not reset implementations). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Maintainer update: @jodeleeuw approved this PR's behavior as designed — preserving the original under Housekeeping since your last push (all on this branch):
CI is green (including the new typecheck/lint gates and the Node 22/24 matrix). Thanks for the careful PR split and the transparent flagging of the storage tradeoff — it made the policy decision easy to review. 🤖 Generated with Claude Code |
Summary
Broadens raw-original preservation beyond JSON. Previously both the CLI (
processFile) and the web wizard (runGenerate) kept the untouched input underdata/raw/only for JSON/JSONL (CSV was assumed to have no separate raw form). This changes the trigger to "the input was transformed" — i.e. preserve the original whenever the Psych-DS output is not a verbatim, same-named copy of the input:Newly covered for CSV inputs:
mydata.csv→subject-x_data.csv). The original is kept under its old name. (Near-universal for real OSF data — the high-impact case.)#132) and is therefore rewritten, so the bytes change. The malformed original is preserved.A clean CSV written byte-for-byte under its own already-compliant name is the only no-raw case, so nothing is duplicated. The existing flat-dir disambiguation and root
.psychds-ignore(so the validator skipsdata/raw/) apply to these preserved CSVs too.Note
Stacked on #132 (
fix/csv-relax-quotes) — base this PR against that branch. This intentionally changes the output layout of currently-working datasets (renamed CSVs now grow adata/raw/dir +.psychds-ignore), which is why it's a separate PR from the tight #132 bug fix.Implementation
packages/cli/src/data.ts): capturemainOutputNamefrom whichever write path ran (planned →planned.mainName; non-planned → the builtmainfile), move the raw-write block to after the output is written, and replace theisJsonDataExtgate with thetransformedcheck.packages/frontend/src/pages/DataUpload.tsx): hoistcsvVerbatimEligibleout of the CSV branch and compare the built main filename againstfile.name.BOM-only changes are deliberately not treated as transformed — the preserved
contentis already BOM-stripped, so preserving it would be pointless.Testing
data.test.ts): renamed CSV preserved; already-compliant verbatim CSV gets nodata/raw/; re-serialised CSV preserved even with an unchanged name.DataUpload.test.tsx): drive the real component and inspectconvertedStore.paths().data/, malformed originals preserved indata/raw/,.psychds-ignorewritten. Also confirmed clean DataPipe CSVs: renamed → preserved; already-compliant → not preserved.🤖 Generated with Claude Code