Skip to content
Beta · version 0.7.1 · commands and the JSON format may still change.

Findings catalog

A finding is what a rule found in a column: a count in one unit, out of a population, with its accuracy. Tabalyst has three classes of finding. An Error is a value that a deterministic rule proves invalid. An Issue is an ambiguity or a deviation worth a look, never proof of an error. An Information is a fact about a column. There is no Warning class.

Each finding has a stable code. This page lists them all, as the JSON profile carries them. The HTML report is one reading of the same data and its wording can change; the codes and the members below do not depend on it.

report.datasets[].columns[].summary.findings
missing Missing positions of the column
errors Errors: findings, state and checks behind them
issues Issues: findings, state and checks behind them

Twelve codes produce a finding in errors or issues. Two more state a fact.

CodeClassCountsUnitOut ofDetail member
temporal.invalid_calendarErrorDates that do not existoccurrencesdate valuesnone
semantic.invalidErrorValues failing the shape or the syntax of their typeoccurrencesvalues the detector testednone
type.outside_familyErrorValues that are not numbers in a numeric columnoccurrencespresent valuesnone
temporal.ambiguous_dateIssueDates that fit two day-month ordersoccurrencesdate valuesdates
number.ambiguousIssueNumbers with more than one readingoccurrencesvalues the detector testednone
categorical.case_variantsIssueGroups of spellings of one textgroupsno populationvariants
pattern.minorityIssueDistinct values in a rare patterndistinct valuesdistinct valuespatterns
length.minorityIssueValues of a rare lengthoccurrencestext valueslengths
type.mixedIssueValues outside the closest technical typeoccurrencespresent valuestypes
identifier.repetitionIssueRepetitions beyond the first occurrenceoccurrencespresent valuesnone
text.surrounding_whitespaceIssueValues with whitespace around the textoccurrencestext valueswhitespace
text.repeated_whitespaceIssueValues with whitespace repeated insideoccurrencestext valueswhitespace
presence.empty_columnInformationPositions of an empty columnpositionspositionsnone
values.constant_columnInformationPositions of a constant columnpositionspositionsnone

A finding in the JSON has: code, cls (error, issue), field, count, unit, population (its kind and size), share (numerator, denominator, percent), accuracy, status, basis (the ids of the observations behind it), examples with examples_state, and overlaps. A count is exact unless its accuracy says lower_bound (a limit stopped it), sample or estimated. A share has its own accuracy and is null when the population is unknown.

{
"code": "temporal.ambiguous_date",
"cls": "issue",
"field": "column_5",
"count": 10857,
"unit": "occurrences",
"population": {"kind": "occurrences", "size": 99994},
"accuracy": "exact",
"status": "detected",
"basis": ["o4"],
"examples_state": "shown"
}

Two findings are never added together unless one proves they cover the same values; overlaps lists the codes proven to overlap and is empty otherwise. The dataset totals only sum findings of one code counted in one unit.

An Error needs a column whose classification is established. On a column whose type is only a candidate or is undetermined, the same fact is stated as an Issue with the same code, and the preview of the finding says so.

  • Category: Temporal columns (Date, Datetime, Time).
  • Counts: the values the date detector reads as a date and rejects: an impossible calendar date (2025-02-30), an impossible time (25:00) or offset, mixed separators (2025/01-15) and a component of the wrong width.
  • Source: the date detector, coverage.invalid, out of its eligible values. A value that is not date-shaped is not counted.
  • Detail: none; the examples come with the finding.
  • Limits: the reasons are not split. A date that passes can still be wrong. Narrowing scan.detectors.date.orders makes a date of an order left out count as invalid.
  • Category: columns classified as Email, Phone, Postal code, URL, IP address or UUID. One code covers all six; the detector is named in basis.
  • Counts: values that do not have the shape of the type, or have it and fail its syntax. For example K0J S9 in a postal code column, a space in the local part of an email, an area code no phone number has, a Canadian postal code with a letter Canada Post does not use, a port above 65,535, an octet above 255, or a non-hexadecimal digit in a UUID.
  • Source: the detector of the type of the column, coverage.invalid plus coverage.not_matched. A detector of another type that finds values in the column is not a constraint on it.
  • Detail: none.
  • Limits: syntax only, no code is looked up. The type is established by the detector and the name of the column, so a value outside it counts.
  • Category: columns classified and typed Integer or Number.
  • Counts: present values that are not numbers: a word, a symbol or a currency code. The decimals of an Integer column are numbers and are not counted.
  • Source: technical_type.outside_count, exact over the values read.
  • Detail: none.
  • Limits: says which values are not numbers, not what they should have been. Date, Boolean and Text columns have no check: an impossible date has its own code, and a Text column has no family to fall outside of.

An Issue needs no established classification: it states what the Scan found, on whatever column it found it. An ambiguous date in a Text column is an Issue.

  • Counts: dates that two configured day and month orders both read as valid, such as 03/04/2024.
  • Source: the observation ambiguous_dates, out of the date values.
  • Detail (finding_details.dates): ambiguous_count; candidates, the pairs of formats that fit the same values with their counts; readings, the unambiguous values per day-month order; formats, the formats read without doubt, each with its order and count; more_candidates and more_formats when the lists are bounded (six each). No example: a format is a shape.
  • Settings: scan.detectors.date.ambiguous_order (DMY or MDY) reads the values one way, and the Scan then raises no observation. deep.datetime.infer_order lets the second pass read them by the evidence of the column. See Configuration.
  • Limits: an ambiguous date is not wrong. Tabalyst never guesses the order.
  • Counts: numbers whose separators allow more than one reading, such as 1,234.
  • Source: the number detector, coverage.ambiguous, out of its eligible values. A count behind a partly tested detector is a lower bound.
  • Detail: none.
  • Settings: scan.detectors.number.ambiguous_convention, infer_convention and min_evidence.
  • Limits: an ambiguous number is not wrong. Tabalyst does not guess.
  • Counts: groups of values that differ only by case, accents or spacing, such as Montréal and MONTREAL. The unit is groups, never the occurrences inside them, and there is no population, so no share.
  • Source: the observation variant_groups, built by the normalization stages.
  • Detail (finding_details.variants): the largest groups, each with its comparison key, its occurrence count, its distinct spellings and their counts, the most frequent first; truncated flags a bounded list. Masked for a masked column, empty for a hidden one.
  • Settings: scan.normalization.casefold, strip_accents, trim, collapse_whitespace; scan.limits.max_variant_groups and max_variants_per_group.
  • Limits: variants are spellings of one text; they do not prove that the spellings mean the same thing. The most frequent spelling is the likely target of a correction, the others the candidates to review.
  • Counts: distinct values written in a pattern that few values share.
  • Rule: one pattern covers at least 80 % of the distinct values and the minority patterns at most 5 % each. These thresholds are constants of the version of the rules, not settings.
  • Source: the observation minority_pattern and the inferred patterns. The accuracy is sample when the discovery read a sample of the distinct values (the check is then partial, reason patterns_sample).
  • Detail (finding_details.patterns): the dominant pattern with its distinct values; the minority patterns, largest first (five at most), each with its distinct values and up to three examples taken from a sample; more_patterns. A pattern can show no example, which says nothing about the others.
  • Limits: a rare pattern is a deviation to inspect, not a constraint. A masked or hidden column has no pattern and no detail.
  • Counts: text values whose length differs from the one that nearly all the values share.
  • Rule: at least 20 text values, one length holds at least 95 % of them and the other lengths at most 5 % together. Lengths count the characters of the value after normalization. Two legitimate lengths leave the rule without meaning (state not_applicable, reason no_dominant_length).
  • Source: the observation minority_length, from the exact length histogram.
  • Detail (finding_details.lengths): the dominant length, and each other length with its count and up to three example texts (five lengths at most; more_lengths). The texts follow the exposure of the column; the lengths and counts do not.
  • Limits: a minority length may be legitimate. It often betrays a malformed value that a pattern absorbed.
  • Counts: values outside the closest technical type of a column whose technical type is mixed.
  • Source: the observation mixed_types, out of the present values.
  • Detail (finding_details.types): the composition of the column by technical type (type, count) in descending count, and the type it comes closest to. Codes and counts, no example.
  • Limits: a value of another type may be legitimate. The composition lists every type counted, which is not only the values outside the closest type.
  • Category: columns classified in the Identifier family.
  • Counts: the values present minus the distinct values (N - D): the occurrences beyond the first one of each identifier. For A, A, A, B this is 2.
  • Source: the exact distinct count of the analytical values. When it is not exact, the check is not_assessed (reason cardinality_not_exact). A Unique ID has proved its uniqueness and has none.
  • Detail: none.
  • Limits: it is neither the number of duplicated occurrences nor the number of identifiers that repeat.

text.surrounding_whitespace and text.repeated_whitespace

Section titled “text.surrounding_whitespace and text.repeated_whitespace”
  • Counts: text values the trim stage changed (whitespace before or after the text), and values the collapse_whitespace stage changed (whitespace repeated inside).
  • Source: the observations trimmed_cells and collapsed_whitespace. Both are remarks in the Scan; the report states them as Issues. A stage turned off gives not_assessed (reason stage_disabled).
  • Detail (finding_details.whitespace): stages, the cells each stage changed; examples from the sample, by kind (leading, trailing, repeated), up to three per kind, each value keeping its whitespace. A value with two kinds is listed under the first. The counts are per stage, never per kind, and the sample says nothing about the examples left out.
  • Settings: scan.normalization.trim and collapse_whitespace.
  • Limits: whitespace is often invisible and says nothing about what the value means.

Two facts about a column are never an Error or an Issue and have no entry in errors or issues. They are Scan observations (empty_column and constant_column, status informational), carried in observations.items.

CodeMeaning
presence.empty_columnEvery position of the column is missing
values.constant_columnEvery present value is the same

Missing positions have their own member, findings.missing, separate from Errors and Issues: a missing value is never an Error or an Issue by itself. It holds count, unit (positions, or occurrences under a JSON array), population, share, always exact, and components: absent, null, empty, blank and marker. The definition of missing is scan.values.missing.

Each list (errors, issues) has a state and assessments, one per check with its code, state, accuracy, eligible, tested and reason. They tell a clean column from a column a check did not cover.

StateMeaning
detectedThe check ran and found something
none_detectedThe check ran completely and found nothing
partialThe check covered only part of the values, so a count is a lower bound and nothing is proven absent
not_assessedThe check was turned off or never ran
not_applicableThe check has no meaning for this column
failedThe check raised a diagnostic; it is never an invalid value

The reason of an assessment, when there is one:

ReasonStateMeaning
not_temporal, not_semantic_type, not_numeric_type, not_identifiernot_applicableThe classification of the column is not the one the code concerns
no_values, no_text, too_few_values, no_dominant_lengthnot_applicableNothing to test, or the rule has no meaning on these values
disabled, stage_disabled, detector_missing, no_checknot_assessedA detector or a stage is off, missing, or no check exists
patterns_unavailablenot_assessedThe Scan inferred no pattern for the column
cardinality_not_exactnot_assessedThe distinct count is not exact
observations_truncatedpartialscan.observations.max_items cut the list of observations
observations_unavailablenot_assessedThe Scan holds no observations
patterns_samplepartialThe patterns were inferred on a sample

The state of a whole list is detected when it holds a finding. Otherwise the checks that apply decide: all in one state give that state, a mix that includes a completed or partial check is partial, and a mix of failed and not_assessed is failed.

examples and the finding_details members go through the exposure of the column before they are written: shown, masked (merged masks) or hidden (none), in examples_state. Counts, lengths, formats and type names describe shapes and quantities and stay whatever the exposure. An example is cut at 64 characters, with truncated set. Examples are taken from the samples the Scan already holds, never from the source file, so a rare case can have none.

The findings are built from the Scan’s observations, carried unchanged in the profile. The rules of version 2 are below, with the category and status fixed by the version. See also observations and limits.

RuleCategoryStatusScopeFinding code
excluded_recordsrecordsconfirmed issuedatasetnone
ambiguous_headernamesconfirmed issuedatasetnone
duplicate_rowsrecordscandidate anomalydatasetnone
mixed_typestypescandidate anomalyfieldtype.mixed
type_outlierstypescandidate anomalyfieldnone
ambiguous_datesdatescandidate anomalyfieldtemporal.ambiguous_date
variant_groupsnormalizationcandidate anomalyfieldcategorical.case_variants
minority_patternpatternscandidate anomalyfieldpattern.minority
minority_lengthpatternscandidate anomalyfieldlength.minority
missing_valuespresenceinformationalfieldfindings.missing
empty_columnpresenceinformationalfieldpresence.empty_column
constant_columnvaluesinformationalfieldvalues.constant_column
trimmed_cellsnormalizationinformationalfieldtext.surrounding_whitespace
collapsed_whitespacenormalizationinformationalfieldtext.repeated_whitespace

Observations without a finding code stay in observations.items. The type_outliers observation counts values outside a technical type; the Error type.outside_family reads the technical type itself, and a Date or Boolean column has no finding for it.

The other codes of a finding (type.outside_family, semantic.invalid, temporal.invalid_calendar, number.ambiguous, identifier.repetition) read detectors, the technical type or the distinct count rather than an observation.

report.datasets[].table sums the findings of the columns. quality counts the columns with Errors, with Issues only and with no finding. signals has one entry per code and unit, with total, lower_bound, and the columns behind it, each with its count. See JSON profile.