Skip to content

Commit 8e33b8c

Browse files
Create index.ts
1 parent 236bc56 commit 8e33b8c

1 file changed

Lines changed: 143 additions & 0 deletions

File tree

  • packages/agents/reference/src/airport-code-resolver
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* Airport/City Code Resolver — Agent 0.1
3+
*
4+
* Resolves IATA/ICAO airport and city codes to canonical airport records
5+
* with multi-airport city awareness and historical code handling.
6+
*
7+
* Implements the base Agent interface from @otaip/core.
8+
*/
9+
10+
import type {
11+
Agent,
12+
AgentInput,
13+
AgentOutput,
14+
AgentHealthStatus,
15+
} from '@otaip/core';
16+
import {
17+
AgentNotInitializedError,
18+
AgentInputValidationError,
19+
} from '@otaip/core';
20+
import type {
21+
AirportCodeResolverInput,
22+
AirportCodeResolverOutput,
23+
} from './types.js';
24+
import { loadAirportData, type AirportDataset } from './data-loader.js';
25+
import { buildIndexes } from './resolver.js';
26+
import { resolve } from './resolver.js';
27+
import { initFuzzyIndex, resetFuzzyIndex } from './fuzzy-match.js';
28+
29+
type AirportIndexes = ReturnType<typeof buildIndexes>;
30+
31+
const VALID_CODE_TYPES = new Set(['iata', 'icao', 'city', 'name', 'auto']);
32+
33+
export class AirportCodeResolver
34+
implements Agent<AirportCodeResolverInput, AirportCodeResolverOutput>
35+
{
36+
readonly id = '0.1';
37+
readonly name = 'Airport/City Code Resolver';
38+
readonly version = '0.1.0';
39+
40+
private dataset: AirportDataset | null = null;
41+
private indexes: AirportIndexes | null = null;
42+
private dataDir: string | undefined;
43+
44+
constructor(options?: { dataDir?: string }) {
45+
this.dataDir = options?.dataDir;
46+
}
47+
48+
async initialize(): Promise<void> {
49+
this.dataset = await loadAirportData(this.dataDir);
50+
this.indexes = buildIndexes(this.dataset);
51+
initFuzzyIndex(this.dataset.airports);
52+
}
53+
54+
async execute(
55+
input: AgentInput<AirportCodeResolverInput>,
56+
): Promise<AgentOutput<AirportCodeResolverOutput>> {
57+
if (!this.dataset || !this.indexes) {
58+
throw new AgentNotInitializedError(this.id);
59+
}
60+
61+
this.validateInput(input.data);
62+
63+
const result = resolve(input.data, this.indexes);
64+
65+
const warnings: string[] = [];
66+
67+
// Check data staleness (older than 30 days)
68+
const dataAge = Date.now() - this.dataset.loadedAt.getTime();
69+
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
70+
if (dataAge > THIRTY_DAYS_MS) {
71+
result.stale_data = true;
72+
warnings.push('Airport data is older than 30 days. Consider refreshing with pnpm run data:download.');
73+
}
74+
75+
return {
76+
data: result,
77+
confidence: result.match_confidence,
78+
warnings: warnings.length > 0 ? warnings : undefined,
79+
metadata: {
80+
agent_id: this.id,
81+
agent_version: this.version,
82+
dataset_loaded_at: this.dataset.loadedAt.toISOString(),
83+
airport_count: this.dataset.airports.length,
84+
},
85+
};
86+
}
87+
88+
async health(): Promise<AgentHealthStatus> {
89+
if (!this.dataset || !this.indexes) {
90+
return { status: 'unhealthy', details: 'Not initialized. Call initialize() first.' };
91+
}
92+
93+
if (this.dataset.airports.length === 0) {
94+
return { status: 'unhealthy', details: 'Airport dataset is empty.' };
95+
}
96+
97+
const dataAge = Date.now() - this.dataset.loadedAt.getTime();
98+
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
99+
if (dataAge > THIRTY_DAYS_MS) {
100+
return { status: 'degraded', details: 'Airport data is stale (>30 days old).' };
101+
}
102+
103+
return { status: 'healthy' };
104+
}
105+
106+
private validateInput(data: AirportCodeResolverInput): void {
107+
if (!data.code || typeof data.code !== 'string') {
108+
throw new AgentInputValidationError(this.id, 'code', 'Required string field. Provide an IATA, ICAO, city code, or airport name.');
109+
}
110+
111+
const trimmed = data.code.trim();
112+
if (trimmed.length < 1 || trimmed.length > 50) {
113+
throw new AgentInputValidationError(this.id, 'code', 'Must be 1-50 characters.');
114+
}
115+
116+
if (data.code_type !== undefined && !VALID_CODE_TYPES.has(data.code_type)) {
117+
throw new AgentInputValidationError(
118+
this.id,
119+
'code_type',
120+
`Must be one of: ${[...VALID_CODE_TYPES].join(', ')}`,
121+
);
122+
}
123+
}
124+
125+
/**
126+
* Tear down resources (used in testing).
127+
*/
128+
destroy(): void {
129+
this.dataset = null;
130+
this.indexes = null;
131+
resetFuzzyIndex();
132+
}
133+
}
134+
135+
export type { AirportCodeResolverInput, AirportCodeResolverOutput } from './types.js';
136+
export type {
137+
ResolvedAirport,
138+
MetroAirport,
139+
CodeType,
140+
AirportType,
141+
AirportStatus,
142+
} from './types.js';
143+

0 commit comments

Comments
 (0)