busi488energy/src/lib/schemas/commodities.ts
2026-04-05 15:20:15 -04:00

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);
}