// Response shapes returned by the API. Dates are ISO 8601 strings. import type { SummaryTotals } from './osha.ts' import type { actionKinds, actionStatuses, AreaKind, employmentTypes, IncidentStatus, OshaCaseType, OshaOutcome, OshaSpecialCase, IncidentType, rcaMethods, Role, rootCauseCategories, Severity, } from './enums.ts' export type ActionKind = (typeof actionKinds)[number] export type ActionStatus = (typeof actionStatuses)[number] export type RcaMethod = (typeof rcaMethods)[number] export type RootCauseCategory = (typeof rootCauseCategories)[number] export interface UserRef { id: string name: string } export interface User { id: string email: string name: string role: Role active: boolean lastLoginAt: string | null createdAt: string } export interface Organization { id: string name: string } export interface Area { id: string siteId: string parentId: string | null name: string kind: AreaKind archivedAt: string | null } export interface Establishment { streetAddress: string | null city: string | null state: string | null zip: string | null naicsCode: string | null industryDescription: string | null ein: string | null establishmentType: number } export interface Site extends Partial { id: string name: string address: string | null timezone: string archivedAt: string | null areas: Area[] } export interface Incident { id: string number: number reference: string title: string type: IncidentType severity: Severity status: IncidentStatus occurredAt: string reportedAt: string updatedAt: string closedAt: string | null site: { id: string; name: string } area: { id: string; name: string } | null locationDetail: string | null description: string | null immediateAction: string | null anonymous: boolean reporter: UserRef | null /** Name given by someone reporting without an account. */ reporterName: string | null /** How the report arrived; `kiosk` covers kiosk tablets and QR codes on reporting links. */ channel: 'web' | 'kiosk' kiosk: { id: string; name: string } | null } export interface Kiosk { id: string name: string token: string site: { id: string; name: string } | null lastUsedAt: string | null revokedAt: string | null createdAt: string } /** What a reporting link may see: just enough to fill in the report form. */ export interface KioskConfig { organization: { name: string } kiosk: { name: string } sites: Site[] } export interface Investigation { id: string lead: UserRef | null method: RcaMethod summary: string | null startedAt: string completedAt: string | null } export interface RootCause { id: string category: RootCauseCategory description: string whys: string[] } export interface CorrectiveAction { id: string incident: { number: number; reference: string; title: string } rootCauseId: string | null kind: ActionKind title: string description: string | null assignee: UserRef | null dueDate: string | null status: ActionStatus overdue: boolean completedAt: string | null verifiedBy: UserRef | null verifiedAt: string | null createdAt: string } export interface Attachment { id: string fileName: string contentType: string sizeBytes: number uploadedBy: UserRef | null createdAt: string url: string } export interface TransitionOption { to: IncidentStatus label: string /** Why the transition can't happen yet; absent when it is allowed. */ blockedReason?: string } export interface IncidentDetail extends Incident { investigation: Investigation | null rootCauses: RootCause[] actions: CorrectiveAction[] attachments: Attachment[] transitions: TransitionOption[] /** Number of injured or ill people recorded; details are only for safety managers. */ injuredCount: number /** What the signed-in user may do on this incident. */ can: { edit: boolean; manageInvestigation: boolean; manageActions: boolean; manageOsha: boolean } } export interface Comment { id: string author: UserRef | null body: string createdAt: string } export interface TimelineEntry { id: number at: string actor: UserRef | null action: string changes: Record | null } export interface Paginated { items: T[] page: number pageSize: number total: number } export interface OverviewStats { openIncidents: number reportedLast7Days: number actionsDueThisWeek: number overdueActions: number /** Average days from report to close for incidents closed in the last 90 days. */ averageCloseDays: number | null byStatus: Record actionProgress: { completed: number; total: number } /** Incidents per week for the last 12 weeks, oldest first. */ weeklyTrend: { weekStart: string; count: number }[] bySite: { siteId: string; name: string; count: number }[] } /** A number for the selected period and the same-length period before it. */ export interface Kpi { value: number | null previous: number | null } /** Incident types grouped for trend charts. */ export type TypeGroup = 'hazards' | 'injuries' | 'damage' export interface AnalyticsStats { from: string to: string previousFrom: string bucket: 'week' | 'month' kpis: { reports: Kpi /** Leading: hazards and near misses reported. */ leadingReports: Kpi injuries: Kpi /** Leading: hazards and near misses per injury or illness. */ nearMissRatio: Kpi /** Leading: share of reports moved out of "reported" within 24 hours. */ triagedWithin24h: Kpi /** Leading: share of actions completed by their due date. */ actionsOnTime: Kpi averageDaysToClose: Kpi /** Lagging: OSHA-recordable cases (supervisors and above). */ recordables: Kpi | null overdueActions: number } trend: ({ start: string } & Record)[] byLocation: { id: string | null; name: string; count: number }[] byRootCause: { category: RootCauseCategory; count: number }[] /** heatmap[weekday][hour], weekday 0 = Monday, in the server's time zone. */ heatmap: number[][] actionsByMonth: { month: string; onTime: number; late: number }[] /** Rolling 12-month OSHA rates at each month end; null for reporters. */ rates: { month: string; trir: number | null; dart: number | null }[] | null yearly: { year: number; recordables: number; dartCases: number; hours: number; trir: number | null; dart: number | null }[] | null } export interface SavedView { id: string page: 'analytics' | 'incidents' name: string filters: Record shared: boolean mine: boolean } // ---- OSHA recordkeeping ---- export interface OshaCase { id: string incident: { id: string; number: number; reference: string; title: string; occurredAt: string } caseNumber: string name: string jobTitle: string | null employmentType: (typeof employmentTypes)[number] bodyPart: string | null natureOfInjury: string | null caseType: OshaCaseType workRelated: boolean | null newCase: boolean | null died: boolean dateOfDeath: string | null daysAwayFromWork: boolean daysAway: number restrictedOrTransfer: boolean daysRestricted: number medicalTreatment: boolean lossOfConsciousness: boolean significantDiagnosis: boolean specialCase: OshaSpecialCase privacyCase: boolean recordable: boolean outcome: OshaOutcome | null inpatientHospitalization: boolean amputation: boolean lossOfEye: boolean reportedToOshaAt: string | null /** Set when 1904.39 requires a direct report to OSHA. */ severeReport: { withinHours: 8 | 24; reasons: string[]; dueBy: string } | null employeeAddress: string | null dateOfBirth: string | null dateOfHire: string | null sex: 'M' | 'F' | null physicianName: string | null treatmentFacility: string | null treatedInEmergencyRoom: boolean timeStartedWork: string | null activityBefore: string | null whatHappened: string | null objectSubstance: string | null /** Form 301 / ITA fields still empty for a recordable case. */ missingFields: string[] updatedAt: string } /** One line of the Form 300 Log. */ export interface OshaLogRow { caseId: string caseNumber: string incidentNumber: number /** "Privacy case" for privacy concern cases. */ employeeName: string jobTitle: string dateOfInjury: string location: string description: string outcome: OshaOutcome daysAway: number daysRestricted: number caseType: OshaCaseType missingFields: string[] } export interface OshaYear { site: { id: string; name: string } & Establishment companyName: string year: number rows: OshaLogRow[] totals: SummaryTotals months: { month: number; hours: number | null; averageEmployees: number | null }[] totalHoursWorked: number annualAverageEmployees: number maxEmployees: number rates: { trir: number | null; dart: number | null; ltir: number | null } checks: { errors: string[]; warnings: string[] } missingEstablishmentFields: string[] } export interface Form301 { case: OshaCase establishment: { name: string; companyName: string } & Establishment incident: { reference: string; occurredAt: string; title: string; description: string | null; site: string; area: string | null; locationDetail: string | null } }