Files
ems/frontend/src/api/backend.ts
Dusan Vojacek 5a66cfa63f
Some checks failed
CI and deploy / migration-check (push) Failing after 12s
CI and deploy / deploy (push) Has been skipped
ladime a ladime
2026-04-22 21:05:14 +02:00

397 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import axios, { type AxiosInstance } from 'axios'
import type { FullStatusResponse } from '../types/fullStatus'
import type { SiteConfigurationResponse } 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<unknown> {
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<HealthDetailedResponse> {
const { data } = await client.get<HealthDetailedResponse>('/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<MeSiteRow[]> {
const { data } = await client.get<MeSiteRow[]>('/me/sites')
return Array.isArray(data) ? data : []
}
export async function getSiteStatusFull(siteId: number): Promise<FullStatusResponse> {
const { data } = await client.get<FullStatusResponse>(`/sites/${siteId}/status/full`)
return data
}
export async function getSiteConfiguration(siteId: number): Promise<SiteConfigurationResponse> {
const { data } = await client.get<SiteConfigurationResponse>(`/sites/${siteId}/configuration`)
return data
}
export type SiteNotificationsResponse = {
notifications: Notification[]
}
export async function getSiteNotifications(siteId: number): Promise<SiteNotificationsResponse> {
const { data } = await client.get<SiteNotificationsResponse>(`/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<SetSiteModeResponse> {
const { data } = await client.post<SetSiteModeResponse>(`/sites/${siteId}/mode`, payload)
return data
}
export async function getCurrentPlan(siteId: number): Promise<CurrentPlanResponse> {
const { data } = await client.get<CurrentPlanResponse>(`/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<ForecastPvSlotRow[]> {
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 : []
}
export type ForecastPvSlotCorrectedRow = {
interval_start: string
pv_forecast_total_w?: number | null
pv_forecast_corrected_w?: number | null
slot_of_day?: number | null
}
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<ForecastPvSlotCorrectedRow[]> {
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 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<Telemetry15mRow[]> {
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<BaselineLoadSlotRow[]> {
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<SiteEffectivePriceRowDto[]> {
const { data } = await client.get<SiteEffectivePriceRowDto[]>(`/sites/${siteId}/prices`, {
params: { date },
timeout: 60_000,
})
return Array.isArray(data) ? data : []
}
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<ForecastPvDayResponse> {
const { data } = await client.get<ForecastPvDayResponse>(`/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<NegativePredictionsResponseDto> {
const { data } = await client.get<NegativePredictionsResponseDto>(
`/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<RunPlanResponse> {
const { data } = await client.post<RunPlanResponse>(
`/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<PricesImportResponse> {
const { data } = await client.post<PricesImportResponse>(
`/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<ForecastRunResponse> {
const { data } = await client.post<ForecastRunResponse>(
`/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<ActiveEvSessionRow[]> {
const { data } = await client.get<ActiveEvSessionRow[]>(
`/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<PatchEvSessionResponse> {
const { data } = await client.patch<PatchEvSessionResponse>(
`/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<DeyeRegistersLive> {
const { data } = await client.get<DeyeRegistersLive>(`/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<ModbusJournalResponse> {
const { data } = await client.get<ModbusJournalResponse>(
`/sites/${siteId}/control/journal`,
{ params: { limit }, timeout: 15_000 },
)
return {
commands: Array.isArray(data?.commands) ? data.commands : [],
}
}
export { client as backendClient }