Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AlertsResource } from './resources/alerts.js'
import { ComplianceResource } from './resources/compliance.js'
import { ContextResource } from './resources/context.js'
import { EventsResource } from './resources/events.js'
import { HealthResource } from './resources/health.js'
import { LatticeResource } from './resources/lattice.js'
import { MemoryResource } from './resources/memory.js'

Expand Down Expand Up @@ -54,6 +55,7 @@ export class ThinkFleetMemory {
readonly events: EventsResource
readonly alerts: AlertsResource
readonly compliance: ComplianceResource
readonly health: HealthResource

constructor(options: ThinkFleetMemoryOptions) {
if (!options.apiKey) {
Expand All @@ -80,5 +82,6 @@ export class ThinkFleetMemory {
this.events = new EventsResource(http)
this.alerts = new AlertsResource(http)
this.compliance = new ComplianceResource(http)
this.health = new HealthResource(http)
}
}
18 changes: 18 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ export type {
ConsentStatus,
} from './resources/consent.js'
export { LatticeResource } from './resources/lattice.js'
export { HealthResource } from './resources/health.js'

// Types — health
export type {
Biomarker,
Sex,
ActivityLevel,
ConditionStatus,
DemographicsInput,
ConditionInput,
HealthAgeComponent,
BiologicalAge,
PredictedHealthCondition,
BiomarkerReading,
HealthProfile,
CohortConditionRisk,
CohortHealthRisk,
} from './types/health.js'

// Types — common
export type { BaseModel } from './types/common.js'
Expand Down
147 changes: 147 additions & 0 deletions src/resources/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import type { HttpClient } from '../core/http-client.js'
import type { RequestOptions } from '../core/types.js'
import { MemoryItemType, MemoryScope, type MemoryItem } from '../types/memory.js'
import type {
Biomarker,
CohortHealthRisk,
ConditionInput,
DemographicsInput,
HealthProfile,
Subject,
} from '../types/health.js'

/**
* Health — biological ("health") age + condition prediction for the
* memory engine's health vertical.
*
* Health data is just memory data: you record biomarkers, demographics,
* and ICD-10 diagnoses as memory items, and the engine derives a
* biological age (PhenoAge core + composite adjustments) and condition
* predictions (biomarkers trending toward / above clinical thresholds),
* plus cohort base rates ("of patients like this one, X% have Y").
*
* You decide where the data originates — EHR feed, document extraction,
* wearable, manual entry. The SDK only gives you the typed way in and out.
*
* Requires the `@thinkfleet/pack-healthcare` pack enabled on the project;
* the read methods return FAILED_PRECONDITION otherwise.
*
* Recorded items are stored as non-activity `fact` memories, so they feed
* the health engine without being mined as behavioral patterns.
*
* @example
* ```ts
* const subject = { kind: 'patient', externalId: 'p-123' }
* await tf.health.recordDemographics(subject, { ageYears: 54, sex: 'female', weightKg: 82, heightCm: 170, activity: 'low' })
* await tf.health.recordBiomarker(subject, 'hba1c', 6.2, { unit: '%' })
* await tf.health.recordCondition(subject, { icd10: 'I10', status: 'active' })
*
* const profile = await tf.health.getProfile(subject)
* console.log(profile.biologicalAge?.biologicalAgeYears, profile.predictedConditions)
*
* const cohort = await tf.health.getCohortRisk(subject)
* for (const r of cohort.risks) console.log(`${r.condition}: ${(r.cohortPrevalence * 100).toFixed(0)}% of similar patients`)
* ```
*/
export class HealthResource {
constructor(private readonly http: HttpClient) {}

// ── Input — record health signals (stored as memory items) ──

/**
* Record a biomarker reading. Send whatever unit the lab reported via
* `opts.unit`; the engine normalizes it.
*/
async recordBiomarker(
subject: Subject,
biomarker: Biomarker | (string & {}),
value: number,
opts?: { unit?: string; observedAt?: string },
options?: RequestOptions,
): Promise<MemoryItem> {
const health: Record<string, unknown> = { biomarker, value }
if (opts?.unit) health.unit = opts.unit
if (opts?.observedAt) health.observedAt = opts.observedAt
return this.http.post<MemoryItem>(
'/admin/memory',
{
content: `${biomarker} = ${value}${opts?.unit ? ` ${opts.unit}` : ''}`,
type: MemoryItemType.FACT,
scope: MemoryScope.PROJECT,
category: 'health',
source: 'sdk:health',
metadata: { subject, health },
},
options,
)
}

/** Record/refresh a subject's demographics. Latest values win. */
async recordDemographics(
subject: Subject,
demographics: DemographicsInput,
options?: RequestOptions,
): Promise<MemoryItem> {
return this.http.post<MemoryItem>(
'/admin/memory',
{
content: 'Demographics update',
type: MemoryItemType.FACT,
scope: MemoryScope.PROJECT,
category: 'health',
source: 'sdk:health',
metadata: { subject, demographic: demographics },
},
options,
)
}

/** Record an ICD-10 diagnosis. */
async recordCondition(
subject: Subject,
condition: ConditionInput,
options?: RequestOptions,
): Promise<MemoryItem> {
return this.http.post<MemoryItem>(
'/admin/memory',
{
content: `Diagnosis ${condition.icd10}`,
type: MemoryItemType.FACT,
scope: MemoryScope.PROJECT,
category: 'health',
source: 'sdk:health',
metadata: { subject, condition },
},
options,
)
}

// ── Read — derived profile + cohort outcomes ──

/**
* Biological-age estimate + condition predictions + latest biomarkers
* for a subject, derived from their recorded health data.
*/
async getProfile(subject: Subject, options?: RequestOptions): Promise<HealthProfile> {
return this.http.post<HealthProfile>('/lattice/health/profile', { subject }, options)
}

/**
* Cohort outcomes — condition prevalence among the patients most similar
* to this subject by baseline features. An epidemiological base rate to
* complement the individual projections from `getProfile`.
*
* @param opts.k cohort size (nearest patients); default 25.
*/
async getCohortRisk(
subject: Subject,
opts?: { k?: number },
options?: RequestOptions,
): Promise<CohortHealthRisk> {
return this.http.post<CohortHealthRisk>(
'/lattice/health/cohort-risk',
{ subject, ...(opts?.k != null ? { k: opts.k } : {}) },
options,
)
}
}
130 changes: 130 additions & 0 deletions src/types/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Health prediction types — biological ("health") age + condition
// prediction + cohort outcomes, served by the memory engine's health
// vertical (gated behind the @thinkfleet/pack-healthcare pack).
//
// Health data IS memory data: you record biomarkers / demographics /
// diagnoses as memory items (see HealthResource.record*), and the engine
// derives the profile from them. You decide where the data comes from —
// EHR, document extraction, manual entry — the SDK just gives you the
// typed way in.

import type { Subject } from './lattice.js'

export type { Subject }

/** Canonical biomarker keys the engine understands. Free-form string on
* the wire (so new markers don't break the contract); these are the
* recognized values. */
export type Biomarker =
// PhenoAge panel
| 'albumin'
| 'creatinine'
| 'glucose_fasting'
| 'crp'
| 'lymphocyte_pct'
| 'mcv'
| 'rdw'
| 'alkaline_phosphatase'
| 'wbc'
// Cardiometabolic
| 'hba1c'
| 'ldl'
| 'hdl'
| 'total_cholesterol'
| 'triglycerides'
| 'systolic_bp'
| 'diastolic_bp'

export type Sex = 'male' | 'female' | 'unknown'
export type ActivityLevel = 'sedentary' | 'low' | 'moderate' | 'high'
export type ConditionStatus = 'active' | 'resolved' | 'historical'

export interface DemographicsInput {
ageYears?: number
sex?: Sex
weightKg?: number
heightCm?: number
activity?: ActivityLevel
}

export interface ConditionInput {
/** ICD-10 code, e.g. "E11.9". */
icd10: string
status?: ConditionStatus
/** ISO timestamp. */
onsetAt?: string
}

// ── Read shapes ──

export interface HealthAgeComponent {
label: string
yearsDelta: number
}

export interface BiologicalAge {
biologicalAgeYears: number
chronologicalAgeYears: number
deltaYears: number
/** "phenoage_hybrid" | "composite" */
method: string
confidence: number
components: HealthAgeComponent[]
/** 10-year mortality score (0..1) from PhenoAge, when available. */
mortalityScore?: number | null
}

export interface PredictedHealthCondition {
/** Canonical key, e.g. "type2_diabetes". */
condition: string
label: string
/** "above_threshold_now" | "threshold_projection" */
basis: string
biomarker: string
currentValue: number
threshold: number
/** ISO timestamp; set only for threshold_projection. */
projectedOnsetAt?: string | null
confidence: number
rationale: string
sourceMemoryIds: string[]
}

export interface BiomarkerReading {
biomarker: string
value: number
unit: string
observedAt: string
}

export interface HealthProfile {
subject: Subject
biologicalAge?: BiologicalAge | null
predictedConditions: PredictedHealthCondition[]
/** ICD-10 codes already diagnosed (active) on record. */
diagnosedConditions: string[]
latestBiomarkers: BiomarkerReading[]
/** Always populated — these are screening indicators, not a diagnosis. */
disclaimer: string
generatedAt: string
}

export interface CohortConditionRisk {
condition: string
/** Fraction of the cohort carrying this condition (0..1). */
cohortPrevalence: number
cohortSize: number
countWith: number
meanSimilarity: number
confidence: number
rationale: string
}

export interface CohortHealthRisk {
subject: Subject
cohortSize: number
populationSize: number
risks: CohortConditionRisk[]
disclaimer: string
generatedAt: string
}
Loading