UNPKG

@damourlabs/euro-clinical-trials

Version:

A Nuxt 3 application for visualizing and managing clinical trials data in Europe, built with modern web technologies.

224 lines (209 loc) 8.34 kB
// Drizzle ORM schema for Eligibility and Enrollment tables import { pgTable, uuid, varchar, text, timestamp, integer, check, unique, pgEnum } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; import { z } from 'zod'; import type { Expect, IsSameType } from '~/types/equality'; import { trials } from '.'; import { CriteriaTypeEnum, SexEnum } from './enums'; export const eligibilityFieldDefaultValues = { minAge: 0, maxAge: 100, sex: SexEnum.Enum.All, criteriaType: CriteriaTypeEnum.Enum.Inclusion, currentEnrollment: 0, targetEnrollment: 100, }; // ---------------------------- // --- Eligibility Criteria --- // ---------------------------- export const pgSex = pgEnum('sex_enum', SexEnum.options); export const eligibilityCriteria = pgTable('eligibility_criteria', { uuid: uuid('uuid').primaryKey().defaultRandom(), trialUuid: uuid('trial_uuid').notNull().references(() => trials.uuid, { onDelete: 'cascade' }), minAge: integer('min_age').notNull().default(eligibilityFieldDefaultValues.minAge), maxAge: integer('max_age').notNull().default(eligibilityFieldDefaultValues.maxAge), sex: pgSex("sex").notNull().default(eligibilityFieldDefaultValues.sex), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(), }, (table) => [ check('eligibility_criteria_age_range', sql`${table.minAge} <= ${table.maxAge}`), check('eligibility_criteria_age_positive', sql`${table.minAge} >= 0 AND ${table.maxAge} >= 0`), ]); export const eligibilityCriteriaSchema = z.object({ uuid: z.string() .uuid() .describe("Unique identifier for the eligibility criteria, automatically generated if not provided") , trialUuid: z.string() .uuid() .describe("UUID of the trial these criteria belong to") , minAge: z.number() .int() .min(0) .describe("Minimum age for participation") .default(eligibilityFieldDefaultValues.minAge) , maxAge: z.number() .int() .min(0) .describe("Maximum age for participation") .default(eligibilityFieldDefaultValues.maxAge) , sex: SexEnum .describe("Sex requirement for participation") .default(eligibilityFieldDefaultValues.sex) , createdAt: z.date() .nullable() .describe("Timestamp of when the criteria were created") , }); // ------------------ // --- Conditions --- // ------------------ export const conditions = pgTable('conditions', { uuid: uuid('uuid').primaryKey().defaultRandom(), name: varchar('name', { length: 255 }).notNull().unique(), description: text('description'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(), }, (table) => [ check('conditions_name_not_empty', sql`LENGTH(TRIM(${table.name})) > 0`), ]); export const conditionsSchema = z.object({ uuid: z.string() .uuid() .describe("Unique identifier for the condition, automatically generated if not provided") , name: z.string() .max(255) .min(1) .describe("Name of the condition") , description: z.string() .nullable() .describe("Description of the condition") , createdAt: z.date() .nullable() .describe("Timestamp of when the condition was created") , }); // ------------------------------ // --- Eligibility Conditions --- // ------------------------------ export const eligibilityConditions = pgTable('eligibility_conditions', { uuid: uuid('uuid').primaryKey().defaultRandom(), eligibilityCriteriaUuid: uuid('eligibility_criteria_uuid').notNull().references(() => eligibilityCriteria.uuid, { onDelete: 'cascade' }), conditionUuid: uuid('condition_uuid').notNull().references(() => conditions.uuid, { onDelete: 'cascade' }), }, (table) => [ unique('eligibility_conditions_unique').on(table.eligibilityCriteriaUuid, table.conditionUuid), ]); export const eligibilityConditionsSchema = z.object({ uuid: z.string() .uuid() .describe("Unique identifier for the eligibility condition link, automatically generated if not provided") , eligibilityCriteriaUuid: z.string() .uuid() .describe("UUID of the eligibility criteria") , conditionUuid: z.string() .uuid() .describe("UUID of the condition") , }); // -------------------------------- // --- Criteria for Eligibility --- // -------------------------------- export const pgCriteriaType = pgEnum('criteria_type_enum', CriteriaTypeEnum.options); export const criteria = pgTable('criteria', { uuid: uuid('uuid').primaryKey().defaultRandom(), eligibilityCriteriaUuid: uuid('eligibility_criteria_uuid').notNull().references(() => eligibilityCriteria.uuid, { onDelete: 'cascade' }), description: text('description').notNull(), criteriaType: pgCriteriaType("criteria_type").notNull().default(eligibilityFieldDefaultValues.criteriaType), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(), }, (table) => [ check('criteria_description_not_empty', sql`LENGTH(TRIM(${table.description})) > 0`), ]); export const criteriaSchema = z.object({ uuid: z.string() .uuid() .describe("Unique identifier for the criteria, automatically generated if not provided") , eligibilityCriteriaUuid: z.string() .uuid() .describe("UUID of the eligibility criteria this criterion belongs to") , description: z.string() .min(1) .describe("Description of the criterion") , criteriaType: z.enum(['Inclusion', 'Exclusion', 'Eligibility']) .describe("Type of criterion") .default(eligibilityFieldDefaultValues.criteriaType) , createdAt: z.date() .nullable() .describe("Timestamp of when the criterion was created") , }); // ------------------------------ // --- Participant Enrollment --- // ------------------------------ export const participantEnrollment = pgTable('participant_enrollment', { uuid: uuid('uuid').primaryKey().defaultRandom(), trialUuid: uuid('trial_uuid').notNull().unique().references(() => trials.uuid, { onDelete: 'cascade' }), currentEnrollment: integer('current_enrollment').notNull().default(eligibilityFieldDefaultValues.currentEnrollment), targetEnrollment: integer('target_enrollment').notNull().default(eligibilityFieldDefaultValues.targetEnrollment), eligibilityCriteriaUuid: uuid('eligibility_criteria_uuid').notNull().references(() => eligibilityCriteria.uuid, { onDelete: 'restrict' }), }, (table) => [ check('participant_enrollment_numbers_positive', sql`${table.currentEnrollment} >= 0 AND ${table.targetEnrollment} >= 0`), check('participant_enrollment_realistic', sql`${table.currentEnrollment} <= ${table.targetEnrollment}`), ]); export const participantEnrollmentSchema = z.object({ uuid: z.string() .uuid() .describe("Unique identifier for the participant enrollment, automatically generated if not provided") , trialUuid: z.string() .uuid() .describe("UUID of the trial this enrollment belongs to") , currentEnrollment: z.number() .int() .min(0) .describe("Current number of enrolled participants") .default(eligibilityFieldDefaultValues.currentEnrollment) , targetEnrollment: z.number() .int() .min(0) .describe("Target number of participants to enroll") .default(eligibilityFieldDefaultValues.targetEnrollment) , eligibilityCriteriaUuid: z.string() .uuid() .describe("UUID of the eligibility criteria") , }); export type EligibilityCriteria = z.infer<typeof eligibilityCriteriaSchema>; export type Condition = z.infer<typeof conditionsSchema>; export type EligibilityCondition = z.infer<typeof eligibilityConditionsSchema>; export type Criteria = z.infer<typeof criteriaSchema>; export type ParticipantEnrollment = z.infer<typeof participantEnrollmentSchema>; // TODO: Fix type mismatches between Zod schemas and Drizzle table types type _eligibilityCriteriaSchemaMatchesTable = Expect<IsSameType<EligibilityCriteria, typeof eligibilityCriteria.$inferSelect>> type _conditionSchemaMatchesTable = Expect<IsSameType<Condition, typeof conditions.$inferSelect>> type _eligibilityConditionSchemaMatchesTable = Expect<IsSameType<EligibilityCondition, typeof eligibilityConditions.$inferSelect>> type _criteriaSchemaMatchesTable = Expect<IsSameType<Criteria, typeof criteria.$inferSelect>> type _participantEnrollmentSchemaMatchesTable = Expect<IsSameType<ParticipantEnrollment, typeof participantEnrollment.$inferSelect>>