import axios, { type AxiosInstance } from 'axios' import type { FullStatusResponse } from '../types/fullStatus' import type { SiteConfigurationResponse, SitePvForecastCalibrationRow, } from '../types/siteConfiguration' import type { Notification } from '../types/dashboard' import type { CurrentPlanResponse, RunPlanResponse } from '../types/plan' const client: AxiosInstance = axios.create({ baseURL: '/api/v1', headers: { Accept: 'application/json' }, timeout: 30_000, }) /** Příklad: health / readiness až budou v FastAPI exponované. */ export async function getBackendHealth(): Promise { const { data } = await client.get('/health') return data } export type HealthDetailedResponse = { db: 'ok' | 'error' scheduler: 'running' | 'stopped' telemetry_loop: 'running' | 'stopped' last_telemetry_age_sec: number last_plan_age_sec: number active_jobs: { id: string; next_run_time: string | null }[] } export async function getBackendHealthDetailed(): Promise { const { data } = await client.get('/health/detailed') return data } /** Aktivní lokality pro výběr v UI (`GET /me/sites`). */ export type MeSiteRow = { id: number code: string name: string timezone: string latitude?: number | string | null longitude?: number | string | null active: boolean notes?: string | null created_at?: string } export async function getMySites(): Promise { const { data } = await client.get('/me/sites') return Array.isArray(data) ? data : [] } export async function getSiteStatusFull(siteId: number): Promise { const { data } = await client.get(`/sites/${siteId}/status/full`) return data } export async function getSiteConfiguration(siteId: number): Promise { const { data } = await client.get(`/sites/${siteId}/configuration`) return data } /** PATCH /sites/{id}/configuration/pv-forecast-calibration — pouze uvedená pole. */ export type PvForecastCalibrationPatchPayload = { delta_learn_min_ts?: string pv_curtailment_policy_effective_from?: string | null top_n_days?: number | null non_top_day_factor?: number | null day_weight_gamma?: number | null half_life_days?: number | null threshold_w?: number | null } export async function patchPvForecastCalibration( siteId: number, payload: PvForecastCalibrationPatchPayload, ): Promise { const { data } = await client.patch( `/sites/${siteId}/configuration/pv-forecast-calibration`, payload, ) return data as SitePvForecastCalibrationRow } export type SiteNotificationsResponse = { notifications: Notification[] } export async function getSiteNotifications(siteId: number): Promise { const { data } = await client.get(`/sites/${siteId}/notifications`, { timeout: 30_000, }) return { notifications: Array.isArray(data?.notifications) ? data.notifications : [], } } export type SetSiteModePayload = { mode: string notes: string | null valid_until: string | null } export type SetSiteModeResponse = { success: boolean mode: string activated_at: string } export async function postSiteMode( siteId: number, payload: SetSiteModePayload, ): Promise { const { data } = await client.post(`/sites/${siteId}/mode`, payload) return data } export async function getCurrentPlan(siteId: number): Promise { const { data } = await client.get(`/sites/${siteId}/plan/current`, { timeout: 60_000, }) return data } /** Řada FVE předpovědi (součet polí) po 15 min — doplnění grafu za horizont uloženého plánu. */ export type ForecastPvSlotRow = { interval_start: string pv_forecast_total_w?: number | null } export async function getForecastPvSlotsRange( siteId: number, fromIso: string, toIso: string, ): Promise { const { data } = await client.get<{ slots?: ForecastPvSlotRow[] }>( `/sites/${siteId}/forecast/pv-slots`, { params: { from: fromIso, to: toIso }, timeout: 45_000 }, ) return Array.isArray(data?.slots) ? data.slots : [] } /** Řádek z GET /sites/{id}/forecast/pv-slots-corrected — backend může doplnit další pole. */ export type ForecastPvSlotCorrectedRow = { interval_start: string pv_forecast_total_w?: number | null pv_forecast_corrected_w?: number | null slot_of_day?: number | null } /** Jedna položka slot profilu z `ems.fn_pv_forecast_delta_profile` (JSON). */ export type PvDeltaProfileSlotEntry = { slot_of_day: number delta_w: number sample_count: number } /** Volitelný JSON profilu delty (ladění / budoucí UI); `deltas` = součet přes pole, `deltas_by_array` = per pole. */ export type PvForecastDeltaProfileJson = { site_id?: number data_from?: string data_to?: string delta_learn_min_ts?: string half_life_days?: number threshold_w?: number top_n_days?: number | null deltas?: PvDeltaProfileSlotEntry[] deltas_by_array?: Record } export type ForecastPvSlotsCorrectedParams = { delta_from?: string delta_to?: string half_life_days?: number threshold_w?: number } export async function getForecastPvSlotsRangeCorrected( siteId: number, fromIso: string, toIso: string, params?: ForecastPvSlotsCorrectedParams, ): Promise { const { data } = await client.get<{ slots?: ForecastPvSlotCorrectedRow[] }>( `/sites/${siteId}/forecast/pv-slots-corrected`, { params: { from: fromIso, to: toIso, ...params }, timeout: 45_000 }, ) return Array.isArray(data?.slots) ? data.slots : [] } export type PvDeltaProfileQueryParams = { half_life_days?: number threshold_w?: number top_n_days?: number | null non_top_day_factor?: number | null day_weight_gamma?: number | null } /** GET /sites/{id}/forecast/pv-delta-profile — přímo JSON z `ems.fn_pv_forecast_delta_profile`. */ export async function getPvForecastDeltaProfile( siteId: number, fromIso: string, toIso: string, params?: PvDeltaProfileQueryParams, ): Promise { const { data } = await client.get( `/sites/${siteId}/forecast/pv-delta-profile`, { params: { from: fromIso, to: toIso, ...params }, timeout: 45_000 }, ) return data != null && typeof data === 'object' ? data : {} } export type Telemetry15mRow = { slot_start: string site_id: number avg_pv_w?: number | null avg_load_w?: number | null avg_grid_w?: number | null avg_battery_w?: number | null last_soc_pct?: number | null sample_count?: number | null } export async function getTelemetry15mRange( siteId: number, fromIso: string, toIso: string, ): Promise { const { data } = await client.get<{ slots?: Telemetry15mRow[] }>(`/sites/${siteId}/timeseries/telemetry-15m`, { params: { from: fromIso, to: toIso }, timeout: 60_000, }) return Array.isArray(data?.slots) ? data.slots : [] } export type BaselineLoadSlotRow = { interval_start: string forecast_w: number confidence_w?: number } export async function getBaselineLoadSlotsRange( siteId: number, fromIso: string, toIso: string, ): Promise { const { data } = await client.get<{ slots?: BaselineLoadSlotRow[] }>( `/sites/${siteId}/forecast/load-baseline-slots`, { params: { from: fromIso, to: toIso }, timeout: 60_000 }, ) return Array.isArray(data?.slots) ? data.slots : [] } /** GET /api/v1/sites/{id}/prices?date=YYYY-MM-DD */ export type SiteEffectivePriceRowDto = { site_id: number interval_start: string interval_end?: string effective_buy_price_czk_kwh?: number | string | null effective_sell_price_czk_kwh?: number | string | null } export async function getSitePrices(siteId: number, date: string): Promise { const { data } = await client.get(`/sites/${siteId}/prices`, { params: { date }, timeout: 60_000, }) return Array.isArray(data) ? data : [] } export async function getSitePricesSlotsRange( siteId: number, fromIso: string, toIso: string, ): Promise { const { data } = await client.get<{ slots?: SiteEffectivePriceRowDto[] }>( `/sites/${siteId}/prices/slots`, { params: { from: fromIso, to: toIso }, timeout: 60_000 }, ) return Array.isArray(data?.slots) ? data.slots : [] } export type ForecastPvIntervalRow = { interval_start: string power_w?: number | string | null pv_array_id?: number } export type ForecastPvDayResponse = { pv_a: ForecastPvIntervalRow[] pv_b: ForecastPvIntervalRow[] } export async function getSiteForecastPv(siteId: number, date: string): Promise { const { data } = await client.get(`/sites/${siteId}/forecast/pv`, { params: { date }, timeout: 60_000, }) return { pv_a: Array.isArray(data?.pv_a) ? data.pv_a : [], pv_b: Array.isArray(data?.pv_b) ? data.pv_b : [], } } export type NegPricePredictionDto = { predicted_date: string window_start_hour: number window_end_hour: number probability_pct: number expected_min_price: number | null reason: string } export type NegativePredictionsResponseDto = { predictions: NegPricePredictionDto[] insufficient_history: boolean } export async function getNegativePricePredictions( siteId: number, ): Promise { const { data } = await client.get( `/sites/${siteId}/prices/negative-predictions`, { timeout: 30_000 }, ) return { predictions: Array.isArray(data?.predictions) ? data.predictions : [], insufficient_history: Boolean(data?.insufficient_history), } } export async function postRunPlan( siteId: number, planType: 'daily' | 'rolling', ): Promise { const { data } = await client.post( `/sites/${siteId}/plan/run`, null, { params: { type: planType }, timeout: 120_000 }, ) return data } export type PricesImportResponse = { slots_imported: number date: string first_price_czk_kwh: number } export async function postImportSitePrices( siteId: number, date?: string, ): Promise { const { data } = await client.post( `/sites/${siteId}/prices/import`, null, { params: date ? { date } : undefined, timeout: 60_000, }, ) return data } export type ForecastRunResponse = { intervals_saved: number pv_arrays: number } export async function postRunForecast(siteId: number): Promise { const { data } = await client.post( `/sites/${siteId}/forecast/run`, null, { timeout: 120_000 }, ) return data } /** Aktivní EV session (GET .../ev/sessions/active) – join vozidlo + nabíječka */ export type ActiveEvSessionRow = { id: number charger_id: number vehicle_id: number | null session_start: string energy_delivered_wh: number target_soc_pct: number | null target_deadline: string | null make: string | null model: string | null battery_capacity_kwh: number | null default_target_soc_pct: number | null default_deadline_hour: number | null charger_code: string charger_name: string | null } export async function getActiveEvSessions(siteId: number): Promise { const { data } = await client.get( `/sites/${siteId}/ev/sessions/active`, ) return data } export type PatchEvSessionPayload = { target_soc_pct: number | null target_deadline: string | null } export type PatchEvSessionResponse = { success: boolean session_id: number } export async function patchEvSession( siteId: number, sessionId: number, payload: PatchEvSessionPayload, ): Promise { const { data } = await client.patch( `/sites/${siteId}/ev/sessions/${sessionId}`, payload, ) return data } /** Živé hodnoty registrů Deye (GET …/control/registers). */ export type DeyeRegistersLive = { reg108_charge_a: number reg109_discharge_a: number reg141_energy_mode: number reg142_limit_control: number reg143_export_limit_w: number reg178_peak_shaving_switch: number reg191_peak_shaving_w: number read_at: string } export async function getDeyeRegisters(siteId: number): Promise { const { data } = await client.get(`/sites/${siteId}/control/registers`, { timeout: 15_000, }) return data } export type ModbusJournalCommandDto = { id: number register: number register_name: string | null value_to_write: number value_written: number | null value_verified: number | null status: string attempt_count: number created_at: string } export type ModbusJournalResponse = { commands: ModbusJournalCommandDto[] } export async function getCommandJournal( siteId: number, limit = 50, ): Promise { const { data } = await client.get( `/sites/${siteId}/control/journal`, { params: { limit }, timeout: 15_000 }, ) return { commands: Array.isArray(data?.commands) ? data.commands : [], } } export { client as backendClient }