Finetype
Finetype reads a CSV and names what each column holds, with the DuckDB expression that types it. What it does, how far to trust it, and how to run it.
A CSV file holds no types, only text. Every column arrives as a string, and whatever reads the file has to guess what each one holds. Finetype reads the values and the column names and tells you: this column is a month-first date, that one is email addresses, that one is two-letter country codes, each with the DuckDB expression that turns the text into a typed column.
Early Release
Finetype is under active development. Expect breaking changes to taxonomy labels, CLI arguments, library APIs, and model formats between releases. Pin to a specific version if stability matters for your use case.
Every column is a string
Here is a small orders file. Save it as orders.csv to follow along; every example on this page reads it.
order_id,order_date,customer,email,country,amount
1001,03/04/2024,Alice Chen,alice@example.com,US,149.99
1002,03/15/2024,Bob Smith,bob@company.org,GB,2500.00
1003,04/02/2024,Carol Wu,carol@startup.io,AU,89.50
1004,04/21/2024,Dan Reeves,dan@university.edu,US,1200.00
1005,05/06/2024,Eve Nakamura,eve@research.net,JP,340.75
1006,05/30/2024,Frank Osei,frank@example.com,GH,75.00
1007,06/11/2024,Grace Patel,grace@design.studio,IN,1850.25
1008,07/04/2024,Hiro Tanaka,hiro@tech.co,JP,425.00
1009,08/19/2024,Isla Morgan,isla@gov.uk,GB,620.00
1010,09/09/2024,Jin Li,jin@startup.io,CN,3200.00
1011,10/12/2024,Kara Novak,kara@example.com,CZ,410.00
1012,11/28/2024,Leo Santos,leo@agency.com,BR,95.20DuckDB's CSV reader guesses a storage type for each column. Run this in duckdb, opened in the same folder:
DESCRIBE FROM 'orders.csv';┌────────────────────┐
│ orders.csv │
│ │
│ order_id bigint │
│ order_date date │
│ customer varchar │
│ email varchar │
│ country varchar │
│ amount double │
└────────────────────┘Those are storage types: they say how DuckDB can hold each column, not what it holds. Nothing here tells you that email is email addresses, which are personal data, or that country is ISO 3166-1 codes rather than country names. And to make order_date a DATE, DuckDB's CSV reader had to decide whether 03/04/2024 is 4 March or 3 April.
What Finetype does
finetype profile reads every column of the file and names its type:
finetype profile -f orders.csvFineType Column Profile — "orders.csv" (12 rows, 6 columns)
════════════════════════════════════════════════════════════════════════════════
COLUMN TYPE BROAD CONF
──────────────────────────────────────────────────────────────────────────────
order_id representation.identifier.increment BIGINT 70.7% [numeric_sequential_detection] ~ medium
order_date datetime.date.mdy_slash DATE 63.7% [date_slash_disambiguation] ⚑ low (maybe datetime.date.dmy_slash)
customer identity.person.full_name VARCHAR 96.6%
email identity.person.email VARCHAR 100.0%
country geography.location.country_code VARCHAR 100.0% [country_code_post_hint_guard:alpha2_rate=1.00]
amount representation.numeric.decimal_number DOUBLE 60.0% [amount_bare_number_veto:amount] ⚑ low
6/6 columns typed, 12 rows analyzedEach type is a three-part label, domain.category.type. datetime.date.mdy_slash is a date written month, day, year with slashes; geography.location.country_code is an ISO 3166-1 two-letter code. BROAD is the DuckDB storage type for the column, CONF is Finetype's confidence, and a name in brackets is the rule, guard or header hint Finetype applied to the column.
Every type comes from Finetype's taxonomy of 251 types: dates, emails, IP addresses, coordinates, financial identifiers and more. 251 is the taxonomy's size; the shipped model predicts 244 of them directly, and the type registry explains the rest.
Each type also carries the DuckDB expression that converts it, with {col} standing for your column. The JSON output holds it as transform, which jq can list:
finetype profile -f orders.csv -o json | jq -r '.columns[] | "\(.column): \(.transform)"'order_id: CAST({col} AS BIGINT)
order_date: strptime({col}, '%m/%d/%Y')::DATE
customer: CAST({col} AS VARCHAR)
email: LOWER(CAST({col} AS VARCHAR))
country: UPPER(CAST({col} AS VARCHAR))
amount: CAST({col} AS DOUBLE)Why it reads the whole column
One value on its own can be ambiguous. 03/04/2024 is a month-first date or a day-first one, and nothing in the value says which. The column settles it: 03/15/2024, in the next row, has no fifteenth month, so the column is month-first. The date_slash_disambiguation in brackets is that rule: when some value's second field is over 12 and no value's first field is, the column is month-first. profile runs every column through the model, with the column name as a hint, so the rule sets the type but not the confidence. (finetype infer on a date column with no header settles it on a structural fast path instead, at a fixed 0.99.) CONF and the band still report the model's confidence, and the runner-up comes from the model's votes, which is why order_date reads low with day-first as its runner-up. Day-first and month-first dates explains how Finetype tells the two apart, and what happens when no value settles it.
The column name counts too: Finetype reads it as a hint alongside the values.
Accuracy
The measured figure
Finetype v0.6.63 names the right type for 881 of 1,037 columns (0.850, 95% confidence interval 0.827–0.870) in Finetype's gold corpus, measured on 2026-10-24. The figure is for that release; a later release that changes how values are read can score differently.
Read it for what it covers. The gold corpus is 1,037 columns from tables published on GitHub and from public datasets, labelled by hand, by audited rules, by agreement between independent labelling methods, or by panels of AI reviewers whose verdicts were spot-checked by hand. It covers 55 of Finetype's 251 types, chosen where one type is easily mistaken for another, so it says nothing about the other 196. The corpus as scored and the script that scores it are in Finetype's repository at that release. The corpus names each column and its label; the sampled values the tables published on GitHub were scored on are not in the repository, so only the public-dataset columns can be re-run from it.
Reading the confidence
A column's confidence ranks how sure Finetype is, but the number itself isn't calibrated: 0.9 doesn't mean right nine times in ten. So each column also carries a quality band, and the band is the part to act on:
| Band | Confidence | What to do |
|---|---|---|
high | 0.85 or more | Trust the type |
medium | 0.70 to below 0.85 | Treat it as a hint |
low | below 0.70 | Check it; the output adds the runner-up type where there is one |
Two columns of orders.csv read low. The JSON output shows them with the runner-up:
finetype profile -f orders.csv -o json | jq '.columns[] | select(.quality_band == "low") | {column, type, confidence, quality_band, runner_up}'{
"column": "order_date",
"type": "datetime.date.mdy_slash",
"confidence": 0.6368157267570496,
"quality_band": "low",
"runner_up": "datetime.date.dmy_slash"
}
{
"column": "amount",
"type": "representation.numeric.decimal_number",
"confidence": 0.6000000238418579,
"quality_band": "low",
"runner_up": null
}A low band says the type may be wrong: check the column. order_date reads "probably month-first, maybe day-first", and the query under Use it checks it: the month-first reading parses all twelve dates and the day-first one only seven.
amount reads low for another reason. Its name suggested a currency amount, but its values carry no currency symbol, so a veto turned it back into a plain decimal number and capped its confidence at 0.6. Check whether the column is money; if it is, the file doesn't say in which currency.
How profile decides
finetype profile works through each column in four steps:
- Sample. It reads up to 100 values from the column (
--sample-sizechanges the number). - Classify. It classifies the sample as one column, using the column name as a hint (
--no-header-hintturns the hint off). This runs in two stages: a model predicts a broad type, then value-based rules and vetoes narrow it to one type in the taxonomy. - Veto. It checks the sampled values against the predicted type's own validator. For the types this check has been audited on, a prediction that fewer than half the values pass becomes
unknown, so the profile doesn't name a type most of the column's own values reject; other types get an advisory flag instead (--no-validation-vetoturns the check off). - Report. It prints each column's type, its DuckDB type and its confidence.
The profile reference lists every option and output format.
Use it
Install Finetype and DuckDB. finetype profile needs the duckdb CLI on your PATH, and the query below runs in it. The quick start has every platform; these are the install scripts:
curl -fsSL https://install.meridian.online/finetype | bash
curl https://install.duckdb.org | shProfile the file. This prints the table under What Finetype does; on your own file, swap orders.csv for its path:
finetype profile -f orders.csvThen check the date column's reading in DuckDB. The query reads every column as text and counts the dates each reading parses, using the strptime format from Finetype's transform for datetime.date.mdy_slash and the one for its runner-up:
SELECT
count(*) AS total_rows,
count(try_strptime(order_date, '%m/%d/%Y')) AS month_first,
count(try_strptime(order_date, '%d/%m/%Y')) AS day_first
FROM read_csv('orders.csv', all_varchar = true);┌────────────┬─────────────┬───────────┐
│ total_rows │ month_first │ day_first │
│ int64 │ int64 │ int64 │
├────────────┼─────────────┼───────────┤
│ 12 │ 12 │ 7 │
└────────────┴─────────────┴───────────┘Every date parses month-first. Five fail day-first, the five whose second field is over 12.
Where to go next
- Quick start: install Finetype and DuckDB, then take one contacts file from raw CSV to a schema, a validated file and a typed DuckDB table.
- CLI reference: every command and option, including JSON Schema and Data Package export with
profileand typed tables withvalidate. - Guides: build a DuckDB pipeline, profile Parquet, validate a delivery.
- DuckDB extension: classification from inside a query. The extension is a different, older Finetype release from the CLI, so its confidences and DuckDB types can differ.
- Type registry: every type, with its examples, schema and DuckDB expression.
Finetype's source is on GitHub, with the taxonomy definitions and the gold corpus. The CLI is on crates.io and the models are on Hugging Face.
Acknowledgements
QSV, the high-performance CSV toolkit, inspired Finetype's approach to data profiling.
FAQ
Answers about Meridian's open datasets: cost, freshness, why the first query is slower, querying without the browser, and how to request a dataset.
Quick Start
Go from raw CSV to a typed, validated DuckDB table in a handful of commands: classify values, profile the file, generate a schema, then validate and query it.