| Site | Name | Country | Critical findings | Endpoint records | Not evaluable | % incomplete | Drift (days/month) |
|---|---|---|---|---|---|---|---|
| SE-02 | Västerlid Sjukhus | SE | 21 | 86 | 3 | 23.3 | 1.69 |
| FI-02 | Lahdenpää Yliopistollinen Sairaala | FI | 12 | 75 | 2 | 29.3 | 0.18 |
| SE-03 | Almstrand Lasarett | SE | 4 | 46 | 1 | 23.9 | 0.15 |
| NL-01 | Sint-Aldegonde Ziekenhuis | NL | 14 | 133 | 1 | 24.8 | 0.10 |
| SE-01 | Björkhagen Universitetssjukhus | SE | 16 | 123 | 3 | 20.3 | 0.08 |
| NL-02 | Maasstad Ziekenhuis Noord | NL | 11 | 93 | 6 | 34.4 | 0.08 |
| NL-03 | Duinrand Medisch Centrum | NL | 6 | 69 | 1 | 29.0 | 0.07 |
| DK-02 | Vestbro Sygehus | DK | 13 | 154 | 4 | 26.0 | 0.06 |
| FI-01 | Koivuranta Keskussairaala | FI | 12 | 104 | 4 | 30.8 | 0.03 |
| DK-05 | Havnestad Sygehus | DK | 8 | 131 | 2 | 22.9 | 0.03 |
Central data monitoring report
Coordinating centre view - Synthetic Adaptive Platform Trial in Critical Illness
2550 participants · 3066 randomisations · 25 sites in 5 countries · rule set 455d0fba76e0 · generated 28 August 2026
Sites needing attention
Ordered by entry-delay drift, then by critical findings. Deliberately not a single composite score: a score hides which of the three problems a site has, and the coordinating centre needs to know which call to make.
Entry-delay drift
No validation rule catches drift. Every record at a drifting site is perfectly valid - it was simply entered late, which is not a rule violation. Only the trend across months shows a site falling further behind, and only measuring it against each site’s own initiation shows that it is not merely a young site still settling in.
Findings across the trial
| Rule | Name | Severity | Form | Findings | Participants | Sites | |
|---|---|---|---|---|---|---|---|
| 4 | RNG-002 | heart_rate_plausible | critical | daily_icu | 106 | 99 | 25 |
| 5 | RNG-003 | temperature_plausible | critical | daily_icu | 88 | 87 | 25 |
| 2 | LOG-002 | not_alive_after_death | critical | daily_icu | 42 | 18 | 14 |
| 16 | XDM-003 | vital_status_consistent_where_windows_agree | critical | outcome_30d | 25 | 12 | 9 |
| 12 | TMP-001 | discharge_not_before_admission | critical | outcome_30d | 24 | 24 | 16 |
| 13 | TMP-004 | ae_onset_before_randomisation | critical | adverse_events | 23 | 23 | 16 |
| 11 | STR-004 | single_randomisation_per_domain | critical | randomisation | 22 | 11 | 13 |
| 10 | STR-003 | participant_id_unique_to_one_site | critical | screening | 10 | 5 | 9 |
| 14 | XDM-001 | death_date_consistent_across_domains | critical | outcome_30d | 7 | 3 | 6 |
| 6 | STR-001 | required_field_present | major | daily_icu | 1682 | 916 | 25 |
| 9 | STR-001 | required_field_present | major | screening | 644 | 375 | 25 |
| 1 | LOG-001 | no_gap_in_daily_records | major | daily_icu | 138 | 137 | 24 |
| 8 | STR-001 | required_field_present | major | randomisation | 136 | 116 | 24 |
| 7 | STR-001 | required_field_present | major | outcome_30d | 100 | 98 | 22 |
| 3 | RNG-001 | weight_plausible | major | screening | 38 | 28 | 12 |
| 15 | XDM-002 | icu_admission_consistent_across_domains | major | outcome_30d | 12 | 5 | 9 |
How well do the rules actually work?
The validation engine is scored against a catalogue of defects deliberately injected into the data, so its performance is measured rather than asserted.
| Defect | Name | Expected rule | Injected | Detected | Recall |
|---|---|---|---|---|---|
| D01 | missing_required_field | STR-001 | 2635 | 2559 | 97.1% |
| D02 | missing_daily_record_gap | LOG-001 | 46 | 45 | 97.8% |
| D03 | out_of_range_value | RNG-001 | 16 | 16 | 100.0% |
| D03 | out_of_range_value | RNG-002 | 103 | 103 | 100.0% |
| D03 | out_of_range_value | RNG-003 | 88 | 87 | 98.9% |
| D04 | impossible_date_sequence | TMP-001 | 25 | 24 | 96.0% |
| D05 | ae_before_randomisation | TMP-004 | 23 | 23 | 100.0% |
| D06 | duplicate_participant_id | STR-003 | 6 | 5 | 83.3% |
| D07 | double_randomisation_same_domain | STR-004 | 8 | 8 | 100.0% |
| D08 | inconsistent_vital_status | LOG-002 | 14 | 14 | 100.0% |
| D09 | late_entry_drift | none - monitoring signal | 739 | 0 | n/a |
| D10 | terminal_digit_preference | none - monitoring signal | 453 | 0 | n/a |
| D11 | unit_conversion_failure | RNG-001 | 31 | 23 | 74.2% |
| D12 | ae_under_reporting | none - monitoring signal | 17 | 0 | n/a |
| D13 | allocation_update_not_applied | none - monitoring signal | 196 | 0 | n/a |
Overall: 97.1% - 2907 of 2995 defect records that a rule targets.
A further 1405 records belong to defect types that no rule in this milestone targets. They are reported as n/a rather than as 0%, because scoring a rule set against defects it was never written to catch would misrepresent it, and omitting them would misrepresent the defect catalogue. These are the statistical signals - entry drift, terminal-digit preference, adverse-event under-reporting - and the drift section above is the first of them to be addressed.
Findings not traceable to an injected defect
Not the same as false positives. A finding with no matching injected defect is usually a genuine problem the simulation created incidentally, or a real ambiguity in the data model.
| Rule | Name | Severity | Findings | Traceable to injection | Not traceable |
|---|---|---|---|---|---|
| LOG-001 | no_gap_in_daily_records | major | 138 | 46 | 92 |
| XDM-003 | vital_status_consistent_where_windows_agree | critical | 25 | 0 | 25 |
| XDM-002 | icu_admission_consistent_across_domains | major | 12 | 0 | 12 |
| LOG-002 | not_alive_after_death | critical | 42 | 35 | 7 |
| XDM-001 | death_date_consistent_across_domains | critical | 7 | 0 | 7 |
| STR-004 | single_randomisation_per_domain | critical | 22 | 16 | 6 |
| STR-001 | required_field_present | major | 2562 | 2560 | 2 |
| RNG-003 | temperature_plausible | critical | 88 | 87 | 1 |
| RNG-001 | weight_plausible | major | 38 | 38 | 0 |
| RNG-002 | heart_rate_plausible | critical | 106 | 106 | 0 |
| STR-003 | participant_id_unique_to_one_site | critical | 10 | 10 | 0 |
| TMP-001 | discharge_not_before_admission | critical | 24 | 24 | 0 |
| TMP-004 | ae_onset_before_randomisation | critical | 23 | 23 | 0 |
LOG-001 is the clearest example. No form in this trial carries an ICU discharge date, so a gap in daily records caused by discharge and readmission is indistinguishable from a gap caused by missing data entry. Querying both is what a data manager would actually do, and the fix is a schema change rather than a rule change.
What the ingest layer had to undo
Every transformation applied between the site’s export and the analysis dataset is logged. This table is the evidence of what was done to the data.
| Transformation | Sites | Log entries | Values affected | |
|---|---|---|---|---|
| 2 | date_format_normalised | 20 | 220 | 46918 |
| 1 | blank_to_na | 25 | 510 | 6757 |
| 4 | decimal_separator_normalised | 5 | 15 | 3673 |
| 3 | datetime_format_normalised | 20 | 20 | 2589 |
| 5 | encoding_transcoded | 1 | 5 | 1341 |
| 6 | unit_converted | 2 | 2 | 270 |
Primary endpoint completeness by site
A day with no daily record counts as unknown, never as a day free of life support. Missing records therefore understate the endpoint. The percentage incomplete is a direct measure of how much each site’s data is degrading the outcome the trial exists to measure.
| n | not_evaluable | n_complete | n_incomplete | died_within_window | mean_all | mean_complete_only | median_all | total_unknown_days |
|---|---|---|---|---|---|---|---|---|
| 3058 | 99 | 2233 | 726 | 960 | 16.18 | 14.55 | 22 | 1608 |
The mean over all records and the mean over complete records differ. That difference is not a rounding detail: it is larger than the one-day margin the analysis plan uses to declare two arms practically equivalent, which is why the endpoint reports its own completeness rather than returning a single number.
All data in this report is synthetic, generated by the code in this repository from a fixed seed. Site names are invented. This is an independent educational project with no affiliation to any real trial, hospital or research group.