///
import { type Cell, Default, handler, lift, recipe, str } from "commontools";
const toTitleCase = (value: string): string => {
return value
.toLowerCase()
.split(/\s+/)
.filter((part) => part.length > 0)
.map((part) => part[0].toUpperCase() + part.slice(1))
.join(" ");
};
const sanitizeText = (value: unknown): string | null => {
if (typeof value !== "string") return null;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
};
const sanitizeId = (value: unknown): string | null => {
const text = sanitizeText(value);
return text ? text.toUpperCase() : null;
};
const clampNumber = (
value: unknown,
min: number,
max: number,
fallback: number,
): number => {
if (typeof value !== "number" || !Number.isFinite(value)) {
return fallback;
}
const clamped = Math.min(Math.max(value, min), max);
return Math.round(clamped);
};
const sanitizeCondition = (value: unknown): string => {
const text = sanitizeText(value);
if (!text) return "Any";
return toTitleCase(text);
};
const sanitizeSite = (value: unknown): string => {
const text = sanitizeText(value);
if (!text) return "Unassigned";
return toTitleCase(text);
};
interface CandidateLike {
id?: string;
candidateId?: string;
age?: number;
condition?: string;
consentGiven?: boolean;
biomarkerScore?: number;
site?: string;
priorTherapy?: boolean;
}
/** Canonical representation of a enrollment candidate. */
interface TrialCandidate {
/** Uppercased participant identifier. */
id: string;
/** Participant age capped to a sensible inclusive range. */
age: number;
/** Primary condition label stored in title case. */
condition: string;
/** Whether informed consent has been collected. */
consentGiven: boolean;
/** Latest biomarker score from screening, 0-100 range. */
biomarkerScore: number;
/** Recruiting site in title case. */
site: string;
/** Indicates if prior therapy disqualifies the participant. */
priorTherapy: boolean;
}
const sanitizeCandidateEntry = (
value: CandidateLike | undefined,
): TrialCandidate | null => {
const id = sanitizeId(value?.id ?? value?.candidateId);
if (!id) return null;
const age = clampNumber(value?.age, 18, 90, 18);
const condition = sanitizeCondition(value?.condition);
const biomarker = clampNumber(value?.biomarkerScore, 0, 100, 0);
const consent = Boolean(value?.consentGiven);
const site = sanitizeSite(value?.site);
const priorTherapy = Boolean(value?.priorTherapy);
return {
id,
age,
condition,
consentGiven: consent,
biomarkerScore: biomarker,
site,
priorTherapy,
};
};
const defaultCandidates: TrialCandidate[] = [
{
id: "P-001",
age: 45,
condition: "Hypertension",
consentGiven: true,
biomarkerScore: 67,
site: "North Campus",
priorTherapy: false,
},
{
id: "P-002",
age: 52,
condition: "Diabetes",
consentGiven: false,
biomarkerScore: 71,
site: "North Campus",
priorTherapy: false,
},
{
id: "P-003",
age: 29,
condition: "Hypertension",
consentGiven: true,
biomarkerScore: 58,
site: "West Clinic",
priorTherapy: true,
},
{
id: "P-004",
age: 62,
condition: "Hypertension",
consentGiven: true,
biomarkerScore: 82,
site: "East Facility",
priorTherapy: false,
},
];
const sanitizeCandidates = (
value: readonly CandidateLike[] | undefined,
): TrialCandidate[] => {
if (!Array.isArray(value)) {
return defaultCandidates.map((candidate) => ({ ...candidate }));
}
const map = new Map();
for (const entry of value) {
const sanitized = sanitizeCandidateEntry(entry);
if (!sanitized) continue;
map.set(sanitized.id, sanitized);
}
if (map.size === 0) {
return defaultCandidates.map((candidate) => ({ ...candidate }));
}
const list = Array.from(map.values());
list.sort((a, b) => a.id.localeCompare(b.id));
return list;
};
/** Enrollment guardrails shared across derives and handlers. */
interface EnrollmentCriteria {
/** Minimum allowed age, inclusive. */
minAge: number;
/** Maximum allowed age, inclusive. */
maxAge: number;
/** Required condition label; null accepts any condition. */
requiredCondition: string | null;
/** Lowest acceptable biomarker score. */
minBiomarkerScore: number;
/** Whether consent is mandatory prior to enrollment. */
requireConsent: boolean;
/** Approved recruiting sites. Empty array means all sites. */
allowedSites: string[];
/** Permits participants that already received therapy. */
allowPriorTherapy: boolean;
}
const defaultCriteria: EnrollmentCriteria = {
minAge: 30,
maxAge: 70,
requiredCondition: "Hypertension",
minBiomarkerScore: 60,
requireConsent: true,
allowedSites: [],
allowPriorTherapy: false,
};
const sanitizeAllowedSites = (value: unknown): string[] => {
if (!Array.isArray(value)) return [];
const sites = new Set();
for (const entry of value) {
const text = sanitizeText(entry);
if (!text) continue;
sites.add(toTitleCase(text));
}
return Array.from(sites).sort((a, b) => a.localeCompare(b));
};
const sanitizeCriteria = (
value: Partial | undefined,
): EnrollmentCriteria => {
const minAge = clampNumber(value?.minAge, 18, 90, defaultCriteria.minAge);
const maxAge = clampNumber(value?.maxAge, minAge, 95, defaultCriteria.maxAge);
const requiredCondition = value?.requiredCondition === null
? null
: value?.requiredCondition === undefined
? defaultCriteria.requiredCondition
: sanitizeCondition(value.requiredCondition);
const minBiomarkerScore = clampNumber(
value?.minBiomarkerScore,
0,
100,
defaultCriteria.minBiomarkerScore,
);
return {
minAge,
maxAge,
requiredCondition,
minBiomarkerScore,
requireConsent: Boolean(
value?.requireConsent ?? defaultCriteria.requireConsent,
),
allowedSites: sanitizeAllowedSites(value?.allowedSites),
allowPriorTherapy: Boolean(
value?.allowPriorTherapy ?? defaultCriteria.allowPriorTherapy,
),
};
};
interface CriteriaPatch {
minAge?: number;
maxAge?: number;
requiredCondition?: string | null;
minBiomarkerScore?: number;
requireConsent?: boolean;
allowedSites?: string[];
allowPriorTherapy?: boolean;
}
const sanitizeCriteriaPatch = (
value: CriteriaPatch | undefined,
): Partial | null => {
if (value === undefined || value === null) return null;
const patch: Partial = {};
if (value.minAge !== undefined) patch.minAge = value.minAge;
if (value.maxAge !== undefined) patch.maxAge = value.maxAge;
if (value.requiredCondition !== undefined) {
patch.requiredCondition = value.requiredCondition === null
? null
: sanitizeCondition(value.requiredCondition);
}
if (value.minBiomarkerScore !== undefined) {
patch.minBiomarkerScore = value.minBiomarkerScore;
}
if (value.requireConsent !== undefined) {
patch.requireConsent = Boolean(value.requireConsent);
}
if (value.allowedSites !== undefined) {
patch.allowedSites = sanitizeAllowedSites(value.allowedSites);
}
if (value.allowPriorTherapy !== undefined) {
patch.allowPriorTherapy = Boolean(value.allowPriorTherapy);
}
return Object.keys(patch).length > 0 ? patch : null;
};
interface CandidatePatch {
id?: string;
candidateId?: string;
age?: number;
condition?: string;
consentGiven?: boolean;
biomarkerScore?: number;
site?: string;
priorTherapy?: boolean;
}
const sanitizeCandidatePatch = (
value: CandidatePatch | undefined,
): { id: string; updates: CandidateLike } | null => {
const id = sanitizeId(value?.id ?? value?.candidateId);
if (!id) return null;
const updates: CandidateLike = { id };
if (value?.age !== undefined) updates.age = value.age;
if (value?.condition !== undefined) updates.condition = value.condition;
if (value?.consentGiven !== undefined) {
updates.consentGiven = Boolean(value.consentGiven);
}
if (value?.biomarkerScore !== undefined) {
updates.biomarkerScore = value.biomarkerScore;
}
if (value?.site !== undefined) updates.site = value.site;
if (value?.priorTherapy !== undefined) {
updates.priorTherapy = Boolean(value.priorTherapy);
}
return { id, updates };
};
/** Detailed screening result for each participant. */
interface ScreeningResult {
candidate: TrialCandidate;
eligible: boolean;
reasons: string[];
}
const buildScreeningReport = (
candidates: readonly TrialCandidate[],
criteria: EnrollmentCriteria,
): ScreeningResult[] => {
const results: ScreeningResult[] = [];
const allowedSites = criteria.allowedSites;
const requireSiteMatch = allowedSites.length > 0;
for (const candidate of candidates) {
const reasons: string[] = [];
if (candidate.age < criteria.minAge) {
reasons.push("below minimum age");
} else if (candidate.age > criteria.maxAge) {
reasons.push("above maximum age");
}
if (
criteria.requiredCondition &&
candidate.condition !== criteria.requiredCondition
) {
reasons.push("condition mismatch");
}
if (candidate.biomarkerScore < criteria.minBiomarkerScore) {
reasons.push("biomarker below threshold");
}
if (criteria.requireConsent && !candidate.consentGiven) {
reasons.push("consent pending");
}
if (!criteria.allowPriorTherapy && candidate.priorTherapy) {
reasons.push("previous therapy excluded");
}
if (
requireSiteMatch &&
!allowedSites.includes(candidate.site)
) {
reasons.push("site not approved");
}
results.push({ candidate, eligible: reasons.length === 0, reasons });
}
results.sort((a, b) => a.candidate.id.localeCompare(b.candidate.id));
return results;
};
/** Aggregate eligible counts per recruiting site. */
interface SiteSummaryEntry {
site: string;
eligible: number;
total: number;
eligibleRatio: number;
}
const buildSiteSummary = (
report: readonly ScreeningResult[],
): SiteSummaryEntry[] => {
const totals = new Map();
for (const entry of report) {
const site = entry.candidate.site;
const current = totals.get(site) ?? { eligible: 0, total: 0 };
const eligible = entry.eligible ? current.eligible + 1 : current.eligible;
totals.set(site, { eligible, total: current.total + 1 });
}
const summary: SiteSummaryEntry[] = [];
for (const [site, counts] of totals.entries()) {
const ratio = counts.total === 0
? 0
: Math.round((counts.eligible / counts.total) * 100) / 100;
summary.push({
site,
eligible: counts.eligible,
total: counts.total,
eligibleRatio: ratio,
});
}
summary.sort((a, b) => a.site.localeCompare(b.site));
return summary;
};
interface ClinicalTrialEnrollmentArgs {
participants: Default;
criteria: Default;
}
const updateCriteria = handler(
(
event: CriteriaPatch | undefined,
context: { criteria: Cell },
) => {
const patch = sanitizeCriteriaPatch(event);
if (!patch) return;
const current = sanitizeCriteria(context.criteria.get());
const merged = sanitizeCriteria({ ...current, ...patch });
context.criteria.set(merged);
},
);
const recordScreening = handler(
(
event: CandidatePatch | undefined,
context: { participants: Cell },
) => {
const patch = sanitizeCandidatePatch(event);
if (!patch) return;
const current = sanitizeCandidates(context.participants.get());
const index = current.findIndex((candidate) => candidate.id === patch.id);
if (index === -1) return;
const merged = sanitizeCandidateEntry({
...current[index],
...patch.updates,
});
if (!merged) return;
const next = current.slice();
next[index] = merged;
context.participants.set(sanitizeCandidates(next));
},
);
export const clinicalTrialEnrollment = recipe(
"Clinical Trial Enrollment",
({ participants, criteria }) => {
const candidateView = lift(sanitizeCandidates)(participants);
const criteriaView = lift(sanitizeCriteria)(criteria);
const screening = lift(({ candid, crit }) =>
buildScreeningReport(candid, crit)
)({
candid: candidateView,
crit: criteriaView,
});
const eligibleCandidates = lift((report: ScreeningResult[]) =>
report.filter((entry) => entry.eligible).map((entry) => entry.candidate)
)(screening);
const ineligibleReport = lift((report: ScreeningResult[]) =>
report
.filter((entry) => !entry.eligible)
.map((entry) => ({
id: entry.candidate.id,
reasons: entry.reasons,
}))
)(screening);
const eligibleIds = lift((list: readonly TrialCandidate[]) =>
list.map((candidate) => candidate.id)
)(eligibleCandidates);
const candidateCount = lift((list: readonly TrialCandidate[]) =>
list.length
)(
candidateView,
);
const eligibleCount = lift((ids: readonly string[]) => ids.length)(
eligibleIds,
);
const eligibleSummary =
str`${eligibleCount} of ${candidateCount} participants eligible`;
const siteSummary = lift((report: ScreeningResult[]) =>
buildSiteSummary(report)
)(screening);
return {
candidates: candidateView,
criteria: criteriaView,
screening,
eligibleCandidates,
eligibleIds,
eligibleCount,
eligibleSummary,
ineligibleReport,
siteSummary,
updateCriteria: updateCriteria({ criteria }),
recordScreening: recordScreening({ participants }),
};
},
);
export type {
ClinicalTrialEnrollmentArgs,
EnrollmentCriteria,
ScreeningResult,
SiteSummaryEntry,
TrialCandidate,
};