/** * @fileoverview Shared observation-pull logic for the phenotype tools * (`brapi_build_phenotype_matrix`, `brapi_germplasm_performance`). Owns the * study-anchored observation collection strategy and the raw/normalized row * shapes, so both tools pull observations identically without duplicating the * server-dependent fallback chain. * * Observation pull strategy (server-dependent), per study: * 1. Prefer `/observations?studyDbId=…` (BrAPI spec path). * 2. Fall back to `/observationunits?studyDbId=…` with embedded * `observations[]` arrays when `/observations` returns empty or the dialect * drops the study filter — required for the BrAPI Community Test Server, * where `/observations?studyDbId` is not honored. * 3. When units carry `germplasmDbId`s but no embedded observations, pull * `/observations?germplasmDbId=…` per germplasm and filter back to the * study — stays study-anchored without an unbounded scan. * * `pullStudyObservations` returns `null` only when no usable observation path * exists for the study (caller throws its typed `no_observation_path` error); * an empty array means the paths exist but yielded no rows. * * @module mcp-server/tools/shared/observations */ import type { Context } from '@cyanheads/mcp-ts-core'; import type { ServerConfig } from '../../../config/server-config.js'; import { type BrapiClient } from '../../../services/brapi-client/index.js'; import type { BrapiDialect } from '../../../services/brapi-dialect/index.js'; import type { CallDescriptor } from '../../../services/capability-registry/types.js'; import type { RegisteredServer } from '../../../services/server-registry/index.js'; /** Raw BrAPI `/observations` row shape (passthrough). */ export interface RawObservationRow { germplasmDbId?: string | null; germplasmName?: string | null; observationDbId?: string | null; observationVariableDbId?: string | null; observationVariableName?: string | null; season?: unknown; seasonDbId?: string | null; studyDbId?: string | null; value?: string | null; [key: string]: unknown; } /** Normalized observation — required fields are non-empty strings after extraction. */ export interface NormObs { germplasmDbId: string; germplasmName: string; observationVariableDbId: string; observationVariableName: string; /** Human season label (`"spring 2013"` / seasonDbId), when the row carries one. */ season?: string; studyDbId: string; value: string; } /** Filters that scope an observation pull beyond the study anchor. */ export interface ObservationPullFilters { extraFilters?: Record | undefined; germplasm?: string[] | undefined; variables?: string[] | undefined; } export interface PullStudyObservationsArgs { client: BrapiClient; config: ServerConfig; connection: RegisteredServer; ctx: Context; dialect: BrapiDialect; input: ObservationPullFilters; loadLimit: number; profile: Record; studyDbId: string; warnings: string[]; } /** * Collect observations for a single study, trying `/observations` first and * falling back to `/observationunits` (embedded, then germplasm-anchored). * Returns `null` when no usable path exists; `[]` when paths exist but are * empty. */ export declare function pullStudyObservations(args: PullStudyObservationsArgs): Promise; /** Normalise a raw /observations row; drops rows missing any required field. */ export declare function normalizeObsRow(row: RawObservationRow): NormObs | null; /** * Derive a human season label from a BrAPI observation row. Servers carry * season either as a nested object (`{ seasonName, year, seasonDbId }`) or a * flat `seasonDbId` string. Returns undefined when neither is present. */ export declare function seasonLabel(row: RawObservationRow): string | undefined; //# sourceMappingURL=observations.d.ts.map