Linkr
Home Resources Tools Documentation Blog Demo
FR
  • What is Linkr?
  • Deployment modes
  • Quick start
  • Local install
  • With Docker
  • Manual install
  • Client-only
  • Install LibreChat
  • Your first project
  • Linkr in a clinical data warehouse
  • Workspaces and projects
  • The data pipeline
  • Entities and sharing
  • Versioning and collaboration
  • Overview
  • Projects
  • Wiki
  • Plugins
  • Members and roles
  • Settings
  • Schemas
  • Getting and exploring
  • Mapping
  • Databases
  • Derived sub-databases
  • Data quality
  • Data catalog
  • Build and publish
  • Anonymize
  • SQL script collections
  • ETL pipelines
  • Building and running
  • Generating the scripts
  • Overview
  • Mapping projects
  • Global view
  • Target concepts
  • Mapping editor
  • Suggestions
  • AI agent
  • Evaluation
  • Export
  • Overview
  • Databases
  • Concepts
  • Cohorts
  • Building
  • Results, SQL and report
  • Patient data
  • Pipeline
  • Datasets
  • IDE
  • Web apps
  • Versioning
  • Overview
  • Tabs and widgets
  • Built-in widgets
  • Analysis widgets
  • Control charts (SPC)
  • Surveys and eCRF
  • R and Python code
  • Filters, settings and export
  • Overview
  • Presentation mode
  • Exporting a report
  • Agents
  • MCP server
  • Skills
  • Import and export
  • Git versioning
  • Community catalog
  • Publishing content
  • Production install
  • Configuration
  • Database connectors
  • Authentication and permissions
  • Files on the server
  • Backup and restore
  • Contributing code
  • Glossary
  • Keyboard shortcuts
  • Release notes
Documentation Data warehouse Data quality

Data quality

Checking what a database contains: a rule set created from a schema, the checks you write, the score that comes out, and the failing rows to investigate.

In short

A rule set gathers checks to run against a database. Created from a schema, it starts with hundreds of checks drawn from that schema’s structure and mapping — ordinary checks, which you edit or delete like your own. A scan runs them all and produces a score; when a check fails, Investigate shows the rows at fault.

Client Available in client-only mode — runs entirely in the browser, no backend. Backend Available with the FastAPI backend.

What it is for

A warehouse degrades without warning. A feed changes, a column empties, an identifier stops matching. Nothing crashes: the dashboards keep showing numbers, only wrong ones.

A rule set turns that watch into something you rerun. Rather than discovering the problem in an analysis six months later, you catch it by running a scan again.

This is a workspace feature

Rule sets live in the warehouse, not in a project. A database’s quality matters to everyone who uses it: checking it once per database makes more sense than re-checking it in every project.

Creating a rule set from a schema

New rule set opens a form: a Name, an Identifier, the Database to check and the Schema to start from. Picking the database proposes its schema automatically; the form then tells you how many checks will be added, and where they come from.

These checks are written once, at creation. From then on they are checks like any other: you rename them, edit their SQL, disable or delete them. Choosing None (empty rule set) starts with no checks.

From the DDL — structure, on the raw tables

The DDL is the description of the schema’s tables (columns, constraints). From it Linkr checks, for each table: do the columns exist, is every NOT NULL column actually filled, is the primary key unique, does every foreign key point to a row that exists.

From the mapping — what the data means

The schema’s mapping says which table holds patients, stays, events. From it Linkr checks: are the essential columns filled, are there stays or events with no patient, an end before the start, an event before birth or more than 60 days after death, an age outside 0–130, unmapped codes, values with no unit, negative doses.

On OMOP CDM 5.4, that comes to about 470 checks. A mapping check that a DDL constraint already covers is not created twice.

Why check a NOT NULL the DDL already declares?

Because declaring it guarantees nothing. A database made of Parquet or CSV files enforces no constraint, and OMOP loads often skip the constraint scripts to go faster. The check costs little; an empty mandatory column skews every analysis that relies on it.

Mapping checks follow each database

They are written against Linkr’s standard relations (linkr_patient, linkr_visit, linkr_event_…), not against the source tables. The same rule set therefore applies to two databases sharing a schema, even if one of them adapted its mapping.

Conformance, completeness, plausibility

Every check has a category and, for two of them, a subcategory. They follow the framework of Kahn et al. (2016), the one OHDSI’s Data Quality Dashboard uses — the shared vocabulary of the OMOP ecosystem.

CategoryThe questionSubcategories
ConformanceDoes the data follow the expected structure and formats?Value, Relational, Computational
CompletenessIs the data there where it is expected?—
PlausibilityAre the values believable?Uniqueness, Atemporal, Temporal

The information icon next to Category, in the editor, recalls these definitions. For what data quality means in healthcare, see the article Data quality.

Organising checks

In the Checks tab, the list is arranged in groups. Generated checks land in their table’s group; the ones you write, in Other checks. A dot gives each check’s category — blue for conformance, teal for completeness, fuchsia for plausibility — and a group header counts its enabled checks out of the total.

demo.linkr.interhop.org
ICU hospital — OMOP
Checks8
CategoryPlausibilitySubcategoryTemporalSeverityErrorThreshold (%)0From mapping
In linkr_visit, no end is before its start
1SELECT
2 COUNT(*) FILTER (WHERE end_datetime < start_datetime)::BIGINT AS violated_rows,
3 COUNT(*)::BIGINT AS total_rows
4FROM linkr_visit

Failed

12 rows break the rule out of 48,210 (0.02 %)

Threshold 0 %: no violation allowed

The Checks tab: on the left the list grouped by table, with a disabled check struck through and an edited, unsaved one (orange dot); on the right the editor of the selected check, its settings, its two queries and Test's verdict. Click a check, switch the query tab, then press Test.
  • Rename or Delete group — right-click the group header. Deleting asks for confirmation: delete its checks with it, or Keep the checks, which move to “Other checks”.
  • New group — in the list’s + menu.
  • Move to… — right-click a check, then pick the destination group, “Other checks” or a new group to name. With Select multiple, you move, enable, disable or delete several checks at once.
  • Filter and sort — the filter button narrows the list by Origin (from the DDL, from the mapping, or manual) and by Category, and sorts the groups: List order, A → Z or Z → A.

A disabled check stays in the list, struck through, but is left out of the scan and the score.

An empty group is kept

A group created with New group stays in the list, even empty, until a check is moved into it: it shows “Empty — right-click a check › Move to…”. It is saved with the rule set and travels with it.

Adding checks

The list’s + menu offers three entries:

  • Manual check — an empty check, with an example query to adapt.
  • From the DDL… and From the mapping… — the schema’s checks that this rule set does not hold yet, grouped by table, to tick. This is how you get back a check deleted by mistake, or bring a rule set up to date after the schema changed.

Writing a check

A check is a SQL query under a single constraint: return one row with two columns named violated_rows (the rows breaking the rule) and total_rows (the rows the rule applies to).

SELECT
  COUNT(*) FILTER (WHERE end_datetime < start_datetime)::BIGINT AS violated_rows,
  COUNT(*)::BIGINT AS total_rows
FROM linkr_visit

Linkr does not read your SQL: it reads those two numbers, turns them into a percentage, and compares it with the threshold. All the business logic is yours.

The editor — on the right in the figure above — has two tabs. Count holds that query. Failing rows, optional, holds the one listing the rows the count treats as violations — the one Investigate opens. Generated checks have both. An orange dot marks an unsaved change, and Save or Cancel apply to both queries at once.

Four settings come with the queries:

  • Category and Subcategory — used for grouping and charts.
  • Severity — error, warning or notice.
  • Threshold (%) — the percentage of failing rows that is tolerated, between 0 and 100. 0 means zero tolerance. An empty value, or one outside that range, is flagged beside the field and not saved.

The Test button, or Ctrl/⌘ + Enter in the Count tab, runs the check without launching a full scan and explains the result: how many rows break the rule, out of how many, and the threshold applied. The same shortcut in the Failing rows tab runs Investigate. With no database picked, a red message says so beside the database selector.

Investigating a failure

A failing check says how many rows are wrong, not which ones. Investigate — in the Checks tab toolbar, or in a result’s detail panel — opens the Investigation tab and runs the failing-rows query there: you see the offending records straight away. Without that query, the count query is opened instead.

The Investigation tab is a SQL console on the rule set’s database: you can rewrite the query, run it again with Run, Ctrl/⌘ + Enter (selection or current line) or Ctrl/⌘ + Shift + Enter (the whole editor). Every run shows the number of rows, the duration, and the date and time it ran.

Running a scan and reading the result

Run Scan, in the Results tab, runs every enabled check, one after the other. Above the table sit two charts — Checks by category and Failure rate by category — which the Charts button hides or shows.

demo.linkr.interhop.org
94%438 / 466 checks passed
Checks by category
214
98
154
ConformanceCompletenessPlausibility
Failure rate by category
3%
0%
13%
ConformanceCompletenessPlausibility
StatusCheckCategorySubcategoryTable% ViolatedSeverity
linkr_event_measurement: values with a unitPlausibilityAtemporallinkr_event_measurement7.4%
linkr_visit: end after startPlausibilityTemporallinkr_visit0.0%
note: columnsConformanceValuenote0.0%
gender_concept_id not nullCompleteness—person0.0%
person: unique keyPlausibilityUniquenessperson0.0%
visit_occurrence_id → visit_occurrenceConformanceRelationalmeasurement0.0%
linkr_event_drug: no negative dosePlausibilityAtemporallinkr_event_drug—
Fail

linkr_event_measurement: values with a unit

Numeric values of linkr_event_measurement come with a unit

PlausibilityAtemporal·Warning·From mapping
% Violated7.40%
Threshold: 5%
Violated rows38,412
Total rows519,080
Execution time412 ms
Tablelinkr_event_measurement
SQL QueryInvestigate
The Results tab after a scan: the score (amber, between 80 and 95 %), the category charts, the results table and, on the right, the detail of the selected check. Click a row — the one in Error status shows the database's message.

For each check, the table gives: status (Pass, Fail, Error or N/A), name, category, subcategory, table, percentage failing and severity; hovering an icon gives its label. Clicking a row opens the detail panel: the violation bar against the threshold, the counters, the execution time, SQL Query — which shows both queries — and Investigate.

The score is the proportion of checks that passed among those that apply. Checks with nothing to test — zero rows concerned, N/A — are excluded. Its badge is green from 95 %, amber from 80 %, red below.

A check in error lowers the score

A check whose SQL fails — a missing table, a renamed column, a typo — counts in the total but never among the passes. The score therefore drops as if the data were at fault. When a score falls for no visible reason, look first for rows with status Error: the detail panel gives the message.

Every scan is recorded in the Run History, which you can browse, replay on screen, and clear. A single Test is not: only full scans are kept.

What to know before launching

  • A scan can be long. Checks run in sequence, and a rule set created from OMOP holds several hundred. Disable the ones that do not concern you rather than letting them slow every scan.
  • A database is essential. It is optional when creating the rule set, but without one there is nothing to run.
  • Where the queries run. In browser mode, in the browser. In server mode, on the server, against the database: the browser only sequences the checks and never loads the raw data. See Deployment modes.
  • The history keeps the full report of every scan. With frequent scans it grows: that is what the Clear button is for.

Sharing a rule set

A rule set is an entity like any other: it exports, is versioned in a git repository, and is published to the catalog. Its export holds all its checks — generated and manual, with both queries, their group and whether they are enabled — and a pointer to the schema it came from. Never the results, nor the data.

On import, the checked database is found again by its identity, if it is installed. This is what lets a proven set of OMOP quality checks travel between institutions and apply as-is to another database carrying the same schema. See Entities and sharing.

Going further

  • Schemas — the DDL and the mapping the checks are drawn from.
  • Databases — attaching a schema to a database, and its SQL console.
  • Data quality — why raw data is not ready, and what it takes.
  • ETL pipelines — which have their own source/target comparison.
PreviousDerived sub-databasesNextData catalog

Product

  • Home
  • Demo

Resources

  • Documentation
  • Resources
  • Tools
  • Blog

Community

  • Framagit source code
  • Github source code

About

  • InterHop.org
  • Contact

2021–2026 InterHop — CC BY-NC-SA 4.0 (site) · AGPLv3 (software)