project-nomad/admin/app/models/drug_label.ts

117 lines
3.4 KiB
TypeScript

import { DateTime } from 'luxon'
import { BaseModel, column, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm'
/**
* Drug Reference v1 — openFDA drug label catalog entry.
*
* One row per FDA `set_id` (stable GUID for a labeling, across all revisions).
* Re-ingesting updates existing rows idempotently via `set_id` UNIQUE key.
*
* Enum-ish columns (product_type) are plain varchars validated at the edge —
* the stl_files / inventory_items convention: no native DB enums, so the
* schema can evolve without ALTER TABLE.
*
* All section-text columns are optional: label records frequently omit
* sections (e.g. OTC labels have no `boxed_warning`). The mapper returns
* null for any absent or empty section.
*/
export default class DrugLabel extends BaseModel {
static table = 'drug_labels'
static namingStrategy = new SnakeCaseNamingStrategy()
@column({ isPrimary: true })
declare id: number
/** openFDA set_id — stable GUID across label revisions. Idempotent upsert key. */
@column()
declare set_id: string
/** openFDA id — per-revision GUID for provenance. */
@column()
declare spl_id: string | null
@column()
declare version: string | null
/** openfda.brand_name[0] — first element only. */
@column()
declare brand_name: string | null
/** openfda.generic_name joined with ", " (labels can list several). */
@column()
declare generic_name: string | null
/** openfda.manufacturer_name[0]. */
@column()
declare manufacturer: string | null
/** openfda.product_ndc joined with ", ". */
@column()
declare product_ndc: string | null
/** openfda.route joined with ", ". */
@column()
declare route: string | null
/**
* openfda.product_type[0]. Drives OTC vs Rx badge + filter.
* Expected values: 'HUMAN OTC DRUG' | 'HUMAN PRESCRIPTION DRUG'.
*/
@column()
declare product_type: string | null
/**
* Normalized brand+generic blob for FULLTEXT and LIKE search.
* Built by normalizeDrugName() at ingest time; never re-computed on read.
* Max 768 chars — stays inside InnoDB utf8mb4 index key-length budget.
*/
@column()
declare searchable_name: string | null
/** Flattened indications_and_usage sections, joined with \n\n. */
@column()
declare indications: string | null
/** Flattened dosage_and_administration sections. */
@column()
declare dosage: string | null
/** Flattened warnings sections. */
@column()
declare warnings: string | null
/** Flattened boxed_warning sections (absent on most OTC labels). */
@column()
declare boxed_warning: string | null
/**
* Flattened drug_interactions label text.
* Single-drug label info only — NOT a pairwise cross-drug checker.
*/
@column()
declare drug_interactions: string | null
/** Flattened contraindications sections. */
@column()
declare contraindications: string | null
/** Flattened when_using sections (common on OTC labels). */
@column()
declare when_using: string | null
/** Flattened stop_use sections (common on OTC labels). */
@column()
declare stop_use: string | null
/**
* Parsed from effective_time (YYYYMMDD → YYYY-MM-DD).
* Stored as a plain date string, not a Luxon DateTime, because
* the source format is just a date (no time component).
*/
@column()
declare source_updated_at: string | null
@column.dateTime({ autoCreate: true, autoUpdate: true, columnName: 'ingested_at' })
declare ingested_at: DateTime
}