Where: faircode/loaders.py's read_table (.csv/.tsv branches - delimiter hardcoded by extension) vs assets/profiler-engine.js's parseCSV (always calls sniffDelimiter, regardless of the dropped file's extension).
The gap: Python's read_table only sniffs the delimiter for an unrecognized extension - a .csv file is always read via pd.read_csv(path) (comma, hardcoded), a .tsv always via sep="\t". The JS engine ignores the extension entirely and always sniffs (assets/profiler-ui.js routes any non-.xlsx/.json drop straight to E.parseCSV, never checking the extension either). A semicolon-delimited file saved with a .csv extension - a common Excel-locale export - parses fine in the browser tool but silently breaks the CLI.
Repro: a file semicolon.csv containing:
sex;race;age;outcome
M;White;25;1
F;Black;30;0
M;White;45;1
F;Asian;22;0
>>> from faircode.loaders import read_table
>>> read_table('semicolon.csv').shape, list(read_table('semicolon.csv').columns)
((4, 1), ['sex;race;age;outcome'])
vs the identical file through the JS engine:
> E.parseCSV(fs.readFileSync('semicolon.csv','utf8')).columns
[ 'sex', 'race', 'age', 'outcome' ]
Verified directly - the CLI collapses the file into one bogus column; the web profiler parses it correctly.
Why it matters: CONTRIBUTING.md requires the two engines to "keep producing identical results" for this shared logic - here the CLI silently produces a garbage 1-column profile (every row becomes its own "group", all flagged as a tiny sample) for a file the browser tool handles correctly, with no error on either side.
Suggested fix: make loaders.py's .csv/.tsv branches sniff too (or validate the parsed column count against a quick sniff before committing to the extension-implied delimiter), so both engines make the same choice for the same file.
Where:
faircode/loaders.py'sread_table(.csv/.tsvbranches - delimiter hardcoded by extension) vsassets/profiler-engine.js'sparseCSV(always callssniffDelimiter, regardless of the dropped file's extension).The gap: Python's
read_tableonly sniffs the delimiter for an unrecognized extension - a.csvfile is always read viapd.read_csv(path)(comma, hardcoded), a.tsvalways viasep="\t". The JS engine ignores the extension entirely and always sniffs (assets/profiler-ui.jsroutes any non-.xlsx/.jsondrop straight toE.parseCSV, never checking the extension either). A semicolon-delimited file saved with a.csvextension - a common Excel-locale export - parses fine in the browser tool but silently breaks the CLI.Repro: a file
semicolon.csvcontaining:vs the identical file through the JS engine:
Verified directly - the CLI collapses the file into one bogus column; the web profiler parses it correctly.
Why it matters: CONTRIBUTING.md requires the two engines to "keep producing identical results" for this shared logic - here the CLI silently produces a garbage 1-column profile (every row becomes its own "group", all flagged as a tiny sample) for a file the browser tool handles correctly, with no error on either side.
Suggested fix: make
loaders.py's.csv/.tsvbranches sniff too (or validate the parsed column count against a quick sniff before committing to the extension-implied delimiter), so both engines make the same choice for the same file.