feat(xlsform2ddi): emit the DDI response-data CSV (buildDataCsv) - #8
Merged
Conversation
`buildDdiXml` describes a dataset it could not produce: given `submissions` it used their count for `<caseQnty>` and a filename in `<fileDscr>`, but nothing here wrote the data file itself. Consumers had to keep a Python runtime around for `survey2ddi_core.data.build_data_csv` — the app boots Pyodide and the `survey2ddi` wheel for that one call. Adds `pipelines/xlsform2ddi/data.ts`: - `buildDataCsv(variables, submissions)` — RFC 4180 CSV (CRLF, minimal quoting), header row of DDI variable names, submissions in input order. - `getDdiColumnNames(variables)` / `remapSubmissionsToDdi(variables, rows)` for callers that write the file themselves. The contract is that column order equals `<var name="">` order, so schema and data align positionally. The column plan therefore walks the same buckets `dataDscr` does — grid members, `select_multiple` binaries, `_other` patterns, standalone vars — which required exporting `splitDataVars` and its types from `ddi/codebook.ts`. A `select_multiple` expands to one `0`/`1` column per choice, `_other` multi patterns drop the `other` binary in favour of the text column (matching `emitOtherPatternVars`), and `note` variables get no column. Note this diverges from the Python `get_canonical_columns`, which documents XML order but emits input order — its CSV header does not match its own XML once a grid, `select_multiple` or `_other` pattern reorders the buckets. Cell values are byte-identical to the Python emitter; only the column order is fixed here. Response rows are keyed by bare question name or by the slash-joined group path (`group/name`) that Kobo's CSV export uses. Both are accepted, bare name wins, so no `data_key` field is needed on `Variable`. Also exports `extractVariables` / `choicesByListFromRows` / `normalizeChoices` from the package root — building a `Variable[]` for `buildDataCsv` was otherwise impossible from outside the library. Closes #5 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 19, 2026
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.
Closes #5.
What
buildDdiXmldescribes a dataset it could not produce:submissionsfed<caseQnty>and the<fileDscr>filename, but nothing in the library wrote the data file. Consumers kept a Python runtime around for that one call —formtransform-appboots Pyodide and thesurvey2ddiwheel purely forsurvey2ddi_core.data.build_data_csv.New
src/pipelines/xlsform2ddi/data.ts:buildDataCsv(variables, submissions)getDdiColumnNames(variables)<var name="">orderremapSubmissionsToDdi(variables, submissions)Also exported from the package root:
extractVariables,choicesByListFromRows,normalizeChoices— building aVariable[]to hand tobuildDataCsvwas otherwise impossible from outside the library.The one hard contract: column order equals
<var name="">orderSo schema↔data alignment is a zip, not a lookup. The column plan walks the same buckets
dataDscrdoes — grid-group members,select_multiplebinaries,_otherpatterns, standalone vars — which is whysplitDataVarsand its types are now exported fromddi/codebook.ts. Consequences:select_multiple→ one0/1column per choice, named<name>_<choice>_othermulti patterns drop theotherbinary and keep the_othertext column, matchingemitOtherPatternVarsnotevariables get no column==<var name="">order on a survey exercising all four buckets, so the two walks cannot drift silentlyDeliberate divergence from the Python emitter
survey2ddi_core.data.get_canonical_columnsdocuments XML order but emits input order. Its CSV header therefore does not match its own XML once a grid,select_multiple, or_otherpattern reorders the buckets (ddi_xml.pybuckets exactly the waycodebook.tsdoes). Fixed here rather than replicated.Cell values are byte-identical to Python. Verified against the real
survey2ddi_core.datamodule on a flat survey — quoting, CRLF,None→ empty cell, space-joined multi-select expansion:cmpclean. On a survey with aselect_multiplethe two differ in column order only, per the above.Submission keys — no
Variable.data_keyIssue #5 asked whether to add the Python
data_keyfield. Not needed:readCellaccepts either the bare question name or the slash-joined group path (group/name, nested groups joined) that Kobo's CSV export uses, bare name winning when a row carries both. That covers the Kobo identity case and the LimeSurvey flattening without a field the caller has to populate correctly.Validation surface
None, per the issue's preferred option — the remap is mechanical and a malformed row is the caller's problem. No
skipValidationtoggle.Testing
tests/ts/unit/pipelines/xlsform2ddi/data.test.tsnpm run validategreen (lint, typecheck, format, knip, dup, arch, markdownlint)src/pipelines/README.mddocument the emitter and the ordering contractThe Python byte-comparison was run locally against
CorrelAid/survey2ddibut not committed — the repo has nosurvey2ddiPython dependency and adding one to pin a package this library is meant to replace seemed like the wrong trade. Happy to add it as atests/live/script if you want it in CI.Next
Unblocks
formtransform-app#10: drop Pyodide, thesurvey2ddiwheel,pyodide.config.json,scripts/setup-pyodide.mjs,src/lib/pyodide.*, and thepyodide+xlsform2lstsvdeps. The full-mode Kobo branch becomesparseXLSData+buildDdiXml+buildDataCsv.🤖 Generated with Claude Code