columns and roles - what a column holds, and what it is

truecopy/columns · truecopy/roles

Count what each column contains once, then deduce what it is from that. Recognising a header label only works on issuers you have already seen.

columns, counted once

import { profileColumns, dominantKind } from 'truecopy/columns';

const profiles = profileColumns(rows, { kindOf, sampleLimit: 200 });
// [{ shareOfKind: { date: 0.83, text: 0.17 }, shareFilled: 0.83 }, …]

One pass, two uses, and they were written twice before this existed: a reader infers a column’s role from its content, and elsewhere learns the type signature of the table to drop the rows that break it. Both start by counting the same thing.

The kind of a cell is a plain string, named by you: an enum here would mean this library had an opinion on what documents contain, and it has none.

sampleLimit (200 by default). A table describes itself in its first rows; reading ten thousand to learn what sixty already said is time somebody spends watching a spinner.

shareOfKind is divided by ALL rows, not by the filled ones. A column of dates with a third of its cells empty is not a column of dates: it is a mess, and judging rows against it would condemn the honest ones.

roles, deduced from that

import { assignRoles } from 'truecopy/roles';

const roles = assignRoles(profiles, [
  { role: 'date', kind: 'date', minimum: 0.5, take: 'best', among: 'filled' },
  { role: 'money', kind: 'amount', minimum: 0.5, take: 'each', among: 'filled' },
  { role: 'description', kind: 'text', minimum: 0.4, take: 'best', among: 'filled' }
]);
// ['date', 'description', undefined, 'money', undefined]

Recognising a header label only works on issuers whose labels you have already seen. Reading the content works on the next one.

Order is the only precedence there is

A column already named is out of the running for what comes after. That is how “the wordiest column among those left is the description” gets stated, a rule that reads backwards if the description is looked for first.

take

  • best: the single strongest column, and only it. A tie resolves on the leftmost: a table is read left to right, and so is the tie.
  • each: every column that qualifies. What several of them mean is yours: one money column is a signed amount, two are a debit and a credit, and that is domain knowledge no library can be told.

among, and this one costs if you get it wrong

out of
rows (default) every row. The cautious reading, and what signature needs.
filled the cells that are actually there.

A statement leaves the date cell empty on every continuation line of a description that wrapped, and that column is still the date column: the question here is not whether the column is reliable, it is what is in it.

Measured on a real statement: a date column filled on 26 rows out of 60, of which 16 were dates. That is 0.27 out of the rows and 0.62 out of the filled ones. Past a threshold of a half, the first answer loses the column outright, and with it every role that depended on it.

undefined, never a filler name

A column whose role is unknown must be able to say so, because every rule downstream is entitled to refuse to judge on it. Naming it 'unknown' invites code to treat that as a role.