Skip to content

feat(cli,frontend): preserve original under data/raw/ whenever output is transformed#133

Merged
jodeleeuw merged 4 commits into
mainfrom
feat/preserve-raw-on-transform
Jul 21, 2026
Merged

feat(cli,frontend): preserve original under data/raw/ whenever output is transformed#133
jodeleeuw merged 4 commits into
mainfrom
feat/preserve-raw-on-transform

Conversation

@Mandyx22

Copy link
Copy Markdown
Contributor

Summary

Broadens 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 (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:

transformed = isJsonDataExt(ext) || !csvVerbatimEligible || mainName !== inputName

Newly covered for CSV inputs:

  • Renamed — a clean CSV whose content is unchanged but whose filename was changed to a Psych-DS-compliant name (e.g. mydata.csvsubject-x_data.csv). The original is kept under its old name. (Near-universal for real OSF data — the high-impact case.)
  • Re-serialised — a CSV that only parsed thanks to quote relaxation (#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 skips data/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 a data/raw/ dir + .psychds-ignore), which is why it's a separate PR from the tight #132 bug fix.

Implementation

  • CLI (packages/cli/src/data.ts): capture mainOutputName from whichever write path ran (planned → planned.mainName; non-planned → the built main file), move the raw-write block to after the output is written, and replace the isJsonDataExt gate with the transformed check.
  • Frontend (packages/frontend/src/pages/DataUpload.tsx): hoist csvVerbatimEligible out of the CSV branch and compare the built main filename against file.name.

BOM-only changes are deliberately not treated as transformed — the preserved content is already BOM-stripped, so preserving it would be pointless.

Testing

  • 3 CLI tests (data.test.ts): renamed CSV preserved; already-compliant verbatim CSV gets no data/raw/; re-serialised CSV preserved even with an unchanged name.
  • 3 frontend component tests (DataUpload.test.tsx): drive the real component and inspect convertedStore.paths().
  • Full suite: 664 passing.
  • Manually verified end-to-end on the real OSF phxq4 dataset (the unquoted-stimulus-HTML files that previously failed to parse): all files read, re-serialised output in data/, malformed originals preserved in data/raw/, .psychds-ignore written. Also confirmed clean DataPipe CSVs: renamed → preserved; already-compliant → not preserved.

🤖 Generated with Claude Code

Mandyx22 and others added 2 commits June 26, 2026 14:28
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-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 36e6d15

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@jspsych/metadata Patch
@jspsych/metadata-cli Patch
frontend Patch

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

jodeleeuw added a commit that referenced this pull request Jul 20, 2026
* 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>
@jodeleeuw
jodeleeuw deleted the branch main July 21, 2026 01:41
@jodeleeuw jodeleeuw closed this Jul 21, 2026
jodeleeuw and others added 2 commits July 20, 2026 21:43
# 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>
@jodeleeuw jodeleeuw reopened this Jul 21, 2026
@jodeleeuw
jodeleeuw changed the base branch from fix/csv-relax-quotes to main July 21, 2026 01:47
@jodeleeuw jodeleeuw closed this Jul 21, 2026
@jodeleeuw jodeleeuw reopened this Jul 21, 2026
@jodeleeuw

Copy link
Copy Markdown
Member

Maintainer update: @jodeleeuw approved this PR's behavior as designed — preserving the original under data/raw/ for any transform, including rename-only. Rationale: filename-keyed mappings (analysis scripts, lab software referencing original filenames) are unrecoverable after a rename, and that provenance is worth the storage for this project.

Housekeeping since your last push (all on this branch):

  • fix(metadata,cli,frontend): ingest jsPsych CSVs with unescaped quotes #132 merged to main (including the quote+comma repair fallback), and main was merged in here; the PR now targets main directly. (GitHub auto-closed this PR when the old base branch was deleted — reopened and retargeted.)
  • Docs updated across docs/, website/docs/, and the package READMEs — 12 pages still described the old JSON-only rule (some explicitly said clean CSVs are never duplicated); they now describe the transformed trigger with the verbatim same-named clean CSV as the only no-raw case.
  • Added the missing test for the interactive rename-plan (planned) path — the code path real CLI users hit when any file needs renaming — asserting a renamed CSV's original lands in data/raw/.
  • Small test-hygiene fix: the mockIsValid implementation overrides are now restored after each test (clearAllMocks doesn't reset implementations).

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

@jodeleeuw
jodeleeuw merged commit 7e8cbee into main Jul 21, 2026
3 checks passed
@jodeleeuw
jodeleeuw deleted the feat/preserve-raw-on-transform branch July 21, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants