fix(metadata,cli,frontend): ingest jsPsych CSVs with unescaped quotes#132
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>
🦋 Changeset detectedLatest commit: fd55433 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 |
jsPsych stimulus HTML often contains both a literal `"` and a `,`. relax_quotes keeps the quote literal but the comma still splits the field, so csv-parse throws "Invalid Record Length" and the file is still dropped. Documents the gap as a test.failing so CI stays green and the spec flips to a hard failure once comma-bearing stimuli ingest correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The fix handles quote-only fields but not the common quote+comma case.
The stimulus's inner comma makes the row 5 fields against 4 headers, so csv-parse throws I pushed a target spec documenting it (commit 0dc55d0, Note "make it parse" isn't free: the only csv-parse knob that lets these through is |
* 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>
…commas; strict-first parsing Closes the gap documented by the former test.failing: a stimulus like '<p>Press "F", "J" to respond</p>' over-splits under relax_quotes and threw CSV_RECORD_INCONSISTENT_COLUMNS, dropping the whole file. - parseCSV now escalates leniency per file: strict RFC-4180 -> relax_quotes -> row-repair fallback. Strict-first means well-formed files are never exposed to lenient reinterpretation (review concern about relax_quotes silently tolerating malformed quoted fields). - The repair pass re-reads over-split rows with two rules: a closing quote only counts before a delimiter/EOL (recovers half-quoted fields), and on retry a comma followed by a space is literal text (machine-written jsPsych delimiters are never followed by a space; prose/HTML commas are). A row that still doesn't fit the header is skipped with a warning - one bad row costs that row, not the file. - CLI and frontend now warn when a file parsed leniently and is being re-serialised, so rewritten bytes are never silent. - test.failing flipped to a passing regression test; new coverage: quoted field with unescaped quotes+commas (repaired, outer quotes stripped), quote-only quoted field (pinned relax_quotes behavior), irreparable row skipped while the file survives, and a CLI end-to-end test asserting the rewritten output strict-parses with the stimulus intact. - Changeset updated (adds frontend bump, documents the leniency ladder and the malformed-file multi-parse tradeoff); trailing newline fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Maintainer update (discussed with @jodeleeuw): we merged current What changed:
Note for #133: since it's based on this branch, it will need this merged in — happy to help with that rebase. 🤖 Generated with Claude Code |
… 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>
… is transformed (#133) * fix(metadata,cli,frontend): ingest jsPsych CSVs with unescaped quotes 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> * feat(cli,frontend): preserve original under data/raw/ whenever output 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> * docs,test(preserve-raw): update all data/raw docs for the transformed 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> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Josh de Leeuw <josh.deleeuw@gmail.com>
Problem
Some jsPsych experiments export the
stimuluscolumn as unquoted HTML containing literal"(e.g.<div class = "EncodingBox">), which violates strict RFC-4180 quoting. Surfaced on a real 1258-file OSF working-memory dataset (osf.io/phxq4) where the tool read 0 files —csv-parsethrewInvalid Opening Quoteand dropped every file. Both the CLI and the browser uploader were affected.There were two layers to fix:
data/payload and the validator (which also strict-parses CSV) rejected it withCSV_FORMATTING_ERROR.Changes
parseCSVnow setsrelax_quotes: true, so a quote inside an unquoted field no longer throws and drops the file; the HTML is kept intact.parseCSVForWritehelper returns the parsed rows plus averbatimSafeflag (strict-parse probe). The CLI and frontend use it so:Result (verified end to end)
CSV_FORMATTING_ERRORDataUpload)errorClean CSVs are unaffected (still verbatim) — only files that were previously 100% unreadable change.
Tests
"now parses instead of dropping the file.DataUploadmock for the new helper.🤖 Generated with Claude Code