287 lines
7.5 KiB
TypeScript
287 lines
7.5 KiB
TypeScript
import axios, { type AxiosInstance } from 'axios'
|
||
|
||
import type { FullStatusResponse } from '../types/fullStatus'
|
||
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
|
||
}
|
||
|
||
export async function getSiteStatusFull(siteId: number): Promise<FullStatusResponse> {
|
||
const { data } = await client.get<FullStatusResponse>(`/sites/${siteId}/status/full`)
|
||
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
|
||
}
|
||
|
||
/** 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 }
|