168 lines
4.6 KiB
TypeScript
168 lines
4.6 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
export const CommodityType = z.enum([
|
|
// Energy commodities
|
|
'natural_gas',
|
|
'wti_crude',
|
|
'coal',
|
|
// Petroleum
|
|
'brent_crude',
|
|
'dubai_crude',
|
|
'gasoline',
|
|
'diesel',
|
|
'heating_oil',
|
|
// Natural gas futures
|
|
'ng_futures_1',
|
|
'ng_futures_2',
|
|
'ng_futures_3',
|
|
'ng_futures_4',
|
|
// Market indicators
|
|
'ovx',
|
|
'vix',
|
|
'dxy',
|
|
'financial_stress',
|
|
// Supply metrics
|
|
'spr_level',
|
|
'us_crude_production',
|
|
// Geopolitical risk
|
|
'gpr_daily',
|
|
]);
|
|
export type CommodityType = z.infer<typeof CommodityType>;
|
|
|
|
export const CommodityPriceSchema = z.object({
|
|
commodity: CommodityType,
|
|
price: z.number(),
|
|
unit: z.string(),
|
|
timestamp: z.date(),
|
|
source: z.string(),
|
|
});
|
|
export type CommodityPrice = z.infer<typeof CommodityPriceSchema>;
|
|
|
|
export const COMMODITY_UNITS: Record<CommodityType, string> = {
|
|
natural_gas: '$/Million BTU',
|
|
wti_crude: '$/Barrel',
|
|
coal: '$/Metric Ton',
|
|
brent_crude: '$/Barrel',
|
|
dubai_crude: '$/Barrel',
|
|
gasoline: '$/Gallon',
|
|
diesel: '$/Gallon',
|
|
heating_oil: '$/Gallon',
|
|
ng_futures_1: '$/Million BTU',
|
|
ng_futures_2: '$/Million BTU',
|
|
ng_futures_3: '$/Million BTU',
|
|
ng_futures_4: '$/Million BTU',
|
|
ovx: 'Index',
|
|
vix: 'Index',
|
|
dxy: 'Index',
|
|
financial_stress: 'Index',
|
|
spr_level: 'Million Barrels',
|
|
us_crude_production: 'Million Barrels/Day',
|
|
gpr_daily: 'Index',
|
|
};
|
|
|
|
export const COMMODITY_DISPLAY_NAMES: Record<CommodityType, string> = {
|
|
natural_gas: 'Natural Gas',
|
|
wti_crude: 'WTI Crude',
|
|
coal: 'Coal',
|
|
brent_crude: 'Brent Crude',
|
|
dubai_crude: 'Dubai Crude',
|
|
gasoline: 'Gasoline',
|
|
diesel: 'Diesel',
|
|
heating_oil: 'Heating Oil',
|
|
ng_futures_1: 'NG Futures (Month 1)',
|
|
ng_futures_2: 'NG Futures (Month 2)',
|
|
ng_futures_3: 'NG Futures (Month 3)',
|
|
ng_futures_4: 'NG Futures (Month 4)',
|
|
ovx: 'Oil Volatility (OVX)',
|
|
vix: 'Market Volatility (VIX)',
|
|
dxy: 'US Dollar Index',
|
|
financial_stress: 'Financial Stress',
|
|
spr_level: 'SPR Level',
|
|
us_crude_production: 'US Crude Production',
|
|
gpr_daily: 'Geopolitical Risk',
|
|
};
|
|
|
|
export const COMMODITY_CATEGORIES = {
|
|
petroleum: ['wti_crude', 'brent_crude', 'dubai_crude', 'gasoline', 'diesel', 'heating_oil'],
|
|
energy_commodity: ['natural_gas', 'coal'],
|
|
futures: ['ng_futures_1', 'ng_futures_2', 'ng_futures_3', 'ng_futures_4'],
|
|
market_indicator: ['ovx', 'vix', 'dxy', 'financial_stress'],
|
|
supply: ['spr_level', 'us_crude_production'],
|
|
geopolitical: ['gpr_daily'],
|
|
} as const;
|
|
|
|
/** Coerce EIA string values to numbers, treating empty/null as null */
|
|
const eiaNumericValue = z.union([z.string(), z.number(), z.null()]).transform((val): number | null => {
|
|
if (val === null || val === '') return null;
|
|
const num = Number(val);
|
|
return Number.isNaN(num) ? null : num;
|
|
});
|
|
|
|
/**
|
|
* EIA natural gas price row.
|
|
* Endpoint: /v2/natural-gas/pri/fut/data/
|
|
* Series: RNGWHHD (Henry Hub Natural Gas Spot Price)
|
|
*/
|
|
export const eiaNaturalGasRowSchema = z.object({
|
|
period: z.string(),
|
|
'series-description': z.string(),
|
|
value: eiaNumericValue,
|
|
units: z.string().optional(),
|
|
});
|
|
|
|
export type EiaNaturalGasRow = z.infer<typeof eiaNaturalGasRowSchema>;
|
|
|
|
/**
|
|
* EIA petroleum/crude oil price row.
|
|
* Endpoint: /v2/petroleum/pri/spt/data/
|
|
* Series: RWTC (WTI Cushing Oklahoma Spot Price)
|
|
*/
|
|
export const eiaWtiCrudeRowSchema = z.object({
|
|
period: z.string(),
|
|
'series-description': z.string().optional(),
|
|
value: eiaNumericValue,
|
|
units: z.string().optional(),
|
|
});
|
|
|
|
export type EiaWtiCrudeRow = z.infer<typeof eiaWtiCrudeRowSchema>;
|
|
|
|
/**
|
|
* Parsed commodity price data point (from EIA/FRED/GPR).
|
|
*/
|
|
export interface CommodityPricePoint {
|
|
timestamp: Date;
|
|
commodity: CommodityType;
|
|
price: number | null;
|
|
unit: string;
|
|
source: string;
|
|
}
|
|
|
|
/**
|
|
* Generic EIA API response wrapper for commodity endpoints.
|
|
*/
|
|
export function eiaCommodityResponseSchema<T extends z.ZodType>(dataRowSchema: T) {
|
|
return z.object({
|
|
response: z.object({
|
|
total: z.union([z.string(), z.number()]).transform(Number),
|
|
data: z.array(dataRowSchema),
|
|
}),
|
|
});
|
|
}
|
|
|
|
export const eiaNaturalGasResponseSchema = eiaCommodityResponseSchema(eiaNaturalGasRowSchema);
|
|
export const eiaWtiCrudeResponseSchema = eiaCommodityResponseSchema(eiaWtiCrudeRowSchema);
|
|
|
|
export type EiaNaturalGasResponse = z.infer<typeof eiaNaturalGasResponseSchema>;
|
|
export type EiaWtiCrudeResponse = z.infer<typeof eiaWtiCrudeResponseSchema>;
|
|
|
|
/**
|
|
* Parse an EIA daily period string to a UTC Date.
|
|
* Commodity data uses daily format: "2026-02-02"
|
|
*/
|
|
export function parseEiaCommodityPeriod(period: string): Date {
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(period)) {
|
|
return new Date(`${period}T00:00:00Z`);
|
|
}
|
|
return new Date(period);
|
|
}
|