Supported formats
"The same data" looks very different depending on which tool exported it. Rather than publish a list of formats we hope work, this page is generated from the test corpus: 84 files (19 centroid, 35 failure, 4 config, 20 profile, 6 bundle) that both implementations are run against on every change. One canonical board and dataset, re-expressed in each dialect, asserted to produce identical results.
Handled automatically
Structure
Comma, semicolon, tab and pipe delimiters, detected per file. UTF-8 byte order marks, CRLF endings, quoted fields containing the delimiter, and title or metadata rows above the header.
Numbers, dates and encodings
European decimal commas and thousands separators; dd-Mon-yyyy,
yyyy/mm/dd, US m/d/y and two-digit years; Excel day
serials; Windows-1252 files that would otherwise arrive as mojibake.
Excel workbooks
.xlsx is read directly. Shared strings and rich text are resolved,
date-formatted cells are converted from day serials, and the sheet that was read
— plus the ones that were not — is named, so data on another tab is visible
rather than silently absent.
Column naming
Flexible aliases for designator, coordinate, date, quantity and assembly columns,
including headings that carry their unit (Center-X(mil)) and defect
columns matched by prefix (Defect Description, Failure Mode).
Cells naming several parts (U7, U8) become separate records.
Refused, with a reason
Being permissive about the wrong things is worse than being strict. A placement export, a bill of materials, or a JSON document whose array is not defect records would each invent one "failure" per part, so each is rejected and told why. A recognized file that yields no records is an error, not a quiet success.
Placement / pick-and-place files
| Example file | What it exercises | Result |
|---|---|---|
centroid_comma.csv |
plain comma CSV | parsed |
centroid_tab.txt |
tab-delimited (Altium .txt) | parsed |
centroid_semicolon_eu.csv |
semicolon delimiter with comma decimals (European Excel) | parsed |
centroid_units_mm.csv |
units written into the value (Altium default) | parsed |
centroid_units_mil.csv |
mil units declared in the values | parsed |
centroid_bom.csv |
UTF-8 BOM (Excel save-as) | parsed |
centroid_crlf.csv |
CRLF line endings | parsed |
centroid_preamble.csv |
metadata/title lines above the header | parsed |
centroid_kicad.pos |
KiCad .pos — '#' header, whitespace separated | parsed |
centroid_quoted.csv |
quoted fields containing the delimiter | parsed |
centroid_aliases.csv |
alternative column names | parsed |
centroid_offset_origin.csv |
origin offset far from 0,0 — must not be mistaken for a huge board | parsed |
centroid_negative.csv |
negative coordinates | parsed |
centroid_duplicate.csv |
the same designator twice | parsed |
centroid_header_units.csv |
unit declared in the column heading, not the value (Altium mil) | parsed |
centroid_eagle.mnt |
headerless whitespace report (Eagle .mnt) | parsed |
centroid_blank_coords.csv |
rows with blank / non-numeric coordinates are counted, not silently dropped | parsed |
centroid_empty.csv |
empty file | rejected, with a reason |
centroid_no_columns.csv |
a BOM, not placement data | rejected, with a reason |
Failure data
| Example file | What it exercises | Result |
|---|---|---|
failures_ict_comma.csv |
ICT CSV, comma | parsed |
failures_ict_semicolon.csv |
ICT CSV, semicolon | parsed |
failures_ict_tab.txt |
ICT CSV, tab | parsed |
failures_ict_bom_crlf.csv |
ICT CSV, BOM + CRLF | parsed |
failures_ict_titlerow.csv |
title row above the header | parsed |
failures_repair.txt |
pipe-delimited repair log, US dates, free-text defects | parsed |
failures_qms.json |
QMS JSON | parsed |
failures_qms_bare_array.json |
bare top-level JSON array with alternative keys | parsed |
failures_generic_dates.csv |
generic CSV with dd-Mon-yyyy dates and free-text faults | parsed |
failures_multi_refdes.csv |
several designators in one cell | parsed |
failures_pipe_in_cell.csv |
one cell contains pipes — must not hijack the file into the repair-log parser | parsed |
failures_qty.csv |
thousands-separated quantity; an explicit quantity of 0 is not a failure | parsed |
failures_blank_refdes.csv |
rows with no designator are counted as unusable, not dropped in silence | parsed |
failures_qms_bom.json |
QMS JSON saved with a UTF-8 BOM | parsed |
failures_qms_numeric_code.json |
defect codes stored as JSON numbers | parsed |
failures_defect_description.csv |
defect column named 'Defect Description' rather than an exact alias | parsed |
failures_ampersand.csv |
'&'-joined designators, and a 'Failure Mode' defect column | parsed |
failures_padded_refdes.csv |
zero-padded designators are parsed verbatim, not silently renamed | parsed |
failures_multi_assembly.csv |
one export spanning two assemblies | parsed |
failures_ict_datetime.csv |
date-and-time cells in six shapes, one date-only, plus a station column | parsed |
failures_repair_time_station.txt |
repair log with separate TIME and STATION columns (one row has no time) | parsed |
failures_qms_datetime.json |
QMS timestamps with an offset, and a separate time field | parsed |
failures_qms_time_precedence.json |
time in the date cell wins over a time field; U+202F before PM; padded and numeric stations | parsed |
failures_impossible_dates.csv |
day-first and impossible dates (13th month, 30 February) become no date, not a rolled-over one | parsed |
failures_generic_time.csv |
generic CSV with separate date and time columns and a bench column | parsed |
failures_bom.csv |
a BOM would otherwise become one failure per part | rejected, with a reason |
failures_placement.json |
placement data under a generic 'data' key must not pass as a QMS export | rejected, with a reason |
failures_kicad_placement.csv |
KiCad placement CSV dropped into the failure intake | rejected, with a reason |
failures_empty_records.json |
recognized but contains nothing | rejected, with a reason |
failures_no_refdes.csv |
no designator column at all | rejected, with a reason |
failures_workbook.xlsx |
Excel workbook: shared strings, a second sheet, and dates stored as day serials | parsed |
profile_alt_repair.txt |
repair log in the alternate profile's vocabulary | parsed |
profile_alt_ict.csv |
quality-server codes from the alternate profile; unknown codes go to its fallback | parsed |
profile_alt_qms.json |
QMS values as key, label, code, hyphenated key and free text | parsed |
profile_alt_default_codes.csv |
the default profile's codes under the alternate profile | parsed |
Criticality configuration
| Example file | What it exercises | Result |
|---|---|---|
config_comma.csv |
config with mixed-case values | parsed |
config_semicolon.csv |
semicolon config with alias column names | parsed |
config_empty.csv |
empty config | rejected, with a reason |
config_no_refdes.csv |
config with no designator column | rejected, with a reason |
Customer profiles
What a defect means is configuration, not code: each manufacturer's failure types, entry-station hotkeys, repair vocabulary, defect codes, shift times and component classes live in one profile file that both implementations read. The corpus includes a second profile that differs from the default in every section, with failure files parsed under it, so nothing can quietly assume the default. Profiles that contradict themselves are refused with a reason.
| Example file | What it exercises | Result |
|---|---|---|
profile_alt.json |
a second profile differing from the default in every section | parsed |
profile_minimal.json |
only a taxonomy: rules and codes empty, shifts and classes from the default | parsed |
profile_bad_schema.json |
a JSON file that is not a profile | rejected, with a reason |
profile_bad_hotkey.json |
two failure types share a hotkey (case-insensitive) | rejected, with a reason |
profile_bad_rule.json |
a keyword rule naming the default's type, not this profile's | rejected, with a reason |
profile_bad_code.json |
a defect code naming a type the profile does not have | rejected, with a reason |
profile_bad_fallback.json |
fallback that is not one of the profile's types | rejected, with a reason |
profile_bad_timezone.json |
unknown IANA time zone | rejected, with a reason |
profile_bad_overlap.json |
a shift overlapping another (crossing midnight) | rejected, with a reason |
profile_bad_prefix.json |
one refdes prefix in two component classes | rejected, with a reason |
profile_bad_color.json |
a colour that is not #rrggbb | rejected, with a reason |
profile_bad_hotkey_newline.json |
a hotkey with a trailing newline is not a single key | rejected, with a reason |
profile_bad_fallback_type.json |
a fallback given as a list, not a type key | rejected, with a reason |
profile_bad_version.json |
version true is not version 1 | rejected, with a reason |
profile_bad_recent.json |
a recent period as long as the shortest classified window | rejected, with a reason |
profile_bad_share.json |
a share above 1 | rejected, with a reason |
profile_bad_threshold.json |
a day count given as true | rejected, with a reason |
profile_bad_window_days.json |
a window length past the calendar | rejected, with a reason |
profile_bad_dup_code.json |
two defect codes that are the same once normalized | rejected, with a reason |
profile_bom.json |
profile saved with a UTF-8 byte-order mark | parsed |
Data bundles
Records kept on a station — entered at the bench or brought in from another machine — export as one portable file with the kept boards and the customer profile. Importing one merges by record, so importing the same bundle twice adds nothing; rows that do not validate are counted and named, never dropped quietly or half-applied.
| Example file | What it exercises | Result |
|---|---|---|
bundle_records.json |
records for three board revisions, mixed date/datetime, nine unusable rows (one is demo data, two name days that do not exist) | parsed |
bundle_with_profile.json |
a bundle carrying its customer profile; records keep that profile's types | parsed |
bundle_loose.json |
version written as 1.0 and boards as null — read the same by both pipelines | parsed |
bundle_newer.json |
a bundle from a newer version is refused, not half-read | rejected, with a reason |
bundle_not_a_bundle.json |
a board package is not a data bundle | rejected, with a reason |
bundle_bad_profile.json |
a bundle whose profile contradicts itself | rejected, with a reason |
Something not on this list?
Reading a new export dialect is a small, contained change — the adapters are deliberately isolated from everything downstream. Send a sample (with anything sensitive removed) to info@engsecsolutions.com and it will be added to this corpus, which means it stays working.