diff --git a/contracts/manifest.json b/contracts/manifest.json index 0f3d7a5..464233d 100644 --- a/contracts/manifest.json +++ b/contracts/manifest.json @@ -7,7 +7,7 @@ }, "server": { "file": "server-v1.openapi.json", - "sha256": "9cd5b962e9556ec227c52d0dc7d4ef4af562dcea86e16877c923c37de0f4f704" + "sha256": "b003a57c9b8c1a041b644363ef58afb8bfcede1fe076ce48a190d2a1ac915e3f" } } } diff --git a/contracts/server-v1.openapi.json b/contracts/server-v1.openapi.json index f6ddeac..cd08f0e 100644 --- a/contracts/server-v1.openapi.json +++ b/contracts/server-v1.openapi.json @@ -2598,6 +2598,18 @@ "title": "Map Y", "type": "number" }, + "max_pipe_diameter": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "节点关联管道的最大管径,单位:毫米", + "title": "Max Pipe Diameter" + }, "node_id": { "title": "Node Id", "type": "string" @@ -2613,6 +2625,7 @@ }, "required": [ "node_id", + "max_pipe_diameter", "project_x", "project_y", "map_x", @@ -35546,6 +35559,114 @@ ] } }, + "/api/v1/sensor-placement-candidates/{node_id}": { + "get": { + "operationId": "get_sensor_placement_candidates_node_id", + "parameters": [ + { + "in": "path", + "name": "node_id", + "required": true, + "schema": { + "maxLength": 32, + "minLength": 1, + "title": "Node Id", + "type": "string" + } + }, + { + "in": "header", + "name": "X-Project-Id", + "required": true, + "schema": { + "title": "X-Project-Id", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SensorPointResponse" + } + } + }, + "description": "Successful Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Insufficient permission" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Resource not found" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Resource conflict" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Validation error" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Dependency unavailable" + } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "summary": "获取监测点候选节点详情", + "tags": [ + "Sensor Placement" + ] + } + }, "/api/v1/sensor-placement-optimization-runs": { "post": { "operationId": "post_sensor_placement_optimization_runs", diff --git a/src/components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel.test.tsx b/src/components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel.test.tsx index 5b0206a..fa71ecb 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel.test.tsx +++ b/src/components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel.test.tsx @@ -1,5 +1,6 @@ import { act, fireEvent, render, screen } from "@testing-library/react"; import MonitoringPlaceOptimizationPanel, { + getMonitoringLayerVisibility, getTabIndicatorSx, getTabIndicatorTransform, } from "./MonitoringPlaceOptimizationPanel"; @@ -7,6 +8,7 @@ import { getSensorPlacementScheme } from "./schemeApi"; import type { SensorPlacementScheme } from "./types"; const mockSchemeEditorRender = jest.fn(); +const mockSchemeQueryRender = jest.fn(); jest.mock("@refinedev/core", () => ({ useNotification: () => ({ open: jest.fn() }), @@ -20,9 +22,16 @@ jest.mock("./OptimizationParameters", () => ({ jest.mock("./SchemeQuery", () => ({ __esModule: true, - default: ({ onEdit }: { onEdit: (schemeId: number) => void }) => ( - - ), + default: ({ + onEdit, + active, + }: { + onEdit: (schemeId: number) => void; + active?: boolean; + }) => { + mockSchemeQueryRender(active); + return ; + }, createMonitoringSchemeQueryState: () => ({}), })); @@ -66,6 +75,7 @@ const scheme: SensorPlacementScheme = { describe("MonitoringPlaceOptimizationPanel", () => { beforeEach(() => { mockSchemeEditorRender.mockClear(); + mockSchemeQueryRender.mockClear(); }); it("applies equal-width positioning to the rendered tab indicator", () => { @@ -90,6 +100,21 @@ describe("MonitoringPlaceOptimizationPanel", () => { expect(indicator).toHaveStyle({ transform: "translateX(200%)" }); }); + it("keeps query and editor layers mutually exclusive by active tab", () => { + expect(getMonitoringLayerVisibility(0)).toEqual({ + editor: false, + query: false, + }); + expect(getMonitoringLayerVisibility(1)).toEqual({ + editor: true, + query: false, + }); + expect(getMonitoringLayerVisibility(2)).toEqual({ + editor: false, + query: true, + }); + }); + it("prepares a queried scheme before switching to the result editor", async () => { let resolveScheme: (value: SensorPlacementScheme) => void = () => {}; mockGetSensorPlacementScheme.mockReturnValue( @@ -119,5 +144,23 @@ describe("MonitoringPlaceOptimizationPanel", () => { false, true, ]); + expect(mockSchemeQueryRender.mock.calls.at(-1)?.[0]).toBe(false); + }); + + it("keeps the active result layer visible when the panel is collapsed", async () => { + mockGetSensorPlacementScheme.mockResolvedValue(scheme); + render(); + + fireEvent.click(screen.getByRole("tab", { name: /方案查询/ })); + fireEvent.click(screen.getByRole("button", { name: "打开测试方案" })); + await screen.findByTestId("scheme-editor"); + + const collapseButton = screen + .getByTestId("ChevronRightIcon") + .closest("button"); + expect(collapseButton).not.toBeNull(); + fireEvent.click(collapseButton!); + + expect(mockSchemeEditorRender.mock.calls.at(-1)?.[0]).toBe(true); }); }); diff --git a/src/components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel.tsx b/src/components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel.tsx index f6e9388..5975baf 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel.tsx +++ b/src/components/olmap/MonitoringPlaceOptimization/MonitoringPlaceOptimizationPanel.tsx @@ -69,6 +69,11 @@ export const getTabIndicatorSx = (tabIndex: number) => ({ }, }); +export const getMonitoringLayerVisibility = (tabIndex: number) => ({ + editor: tabIndex === 1, + query: tabIndex === 2, +}); + interface PreparedSchemeEditorProps extends React.ComponentProps { onReady?: () => void; @@ -126,6 +131,7 @@ const MonitoringPlaceOptimizationPanel: React.FC< }; const drawerWidth = currentTab === 1 ? 820 : 520; + const layerVisibility = getMonitoringLayerVisibility(currentTab); const handleSchemeEditorReady = useCallback(() => { setCurrentTab(1); @@ -326,7 +332,7 @@ const MonitoringPlaceOptimizationPanel: React.FC< void) | undefined; +let mockGridColumns: Array<{ + field: string; + headerName?: string; + valueFormatter?: (value: unknown) => string; +}> = []; const mockMap = { addLayer: jest.fn(), @@ -32,19 +39,23 @@ jest.mock("@components/olmap/core/MapComponent", () => ({ jest.mock("@mui/x-data-grid", () => ({ DataGrid: (props: { + columns: typeof mockGridColumns; density?: string; initialState?: { density?: string }; rowHeight?: number; columnHeaderHeight?: number; - }) => ( -
- ), + }) => { + mockGridColumns = props.columns; + return ( +
+ ); + }, GridToolbar: () => null, })); @@ -119,11 +130,6 @@ jest.mock("ol/style", () => ({ Text: class {}, })); -jest.mock("ol/proj", () => ({ - fromLonLat: (coordinates: number[]) => coordinates, - toLonLat: (coordinates: number[]) => coordinates, -})); - jest.mock("@/utils/mapQueryService", () => ({ handleMapClickSelectFeatures: jest.fn(), })); @@ -135,6 +141,7 @@ jest.mock("./SchemeDrawingDialog", () => ({ jest.mock("./schemeApi", () => ({ exportSensorPlacementExcel: jest.fn(), + getSensorPlacementCandidate: jest.fn(), overwriteSensorPlacementScheme: jest.fn(), })); @@ -149,6 +156,7 @@ const scheme: SensorPlacementScheme = { sensor_points: [ { node_id: "J1", + max_pipe_diameter: 400, project_x: 10, project_y: 20, map_x: 13500010, @@ -161,10 +169,18 @@ const scheme: SensorPlacementScheme = { can_edit: true, }; +const mockGetSensorPlacementCandidate = jest.mocked( + getSensorPlacementCandidate, +); +const mockHandleMapClickSelectFeatures = jest.mocked( + handleMapClickSelectFeatures, +); + describe("SchemeEditor notifications", () => { beforeEach(() => { jest.clearAllMocks(); mockSingleClickHandler = undefined; + mockGridColumns = []; }); it("uses the same error notification contract as scheme query when replace starts outside an existing sensor", async () => { @@ -187,6 +203,38 @@ describe("SchemeEditor notifications", () => { }); }); + it("loads authoritative point details when adding a map-selected node", async () => { + mockHandleMapClickSelectFeatures.mockResolvedValue({ + get: (key: string) => (key === "id" ? "J2" : undefined), + getId: () => "J2", + } as never); + mockGetSensorPlacementCandidate.mockResolvedValue({ + node_id: "J2", + max_pipe_diameter: 500, + project_x: 30, + project_y: 40, + map_x: 13500030, + map_y: 3600040, + longitude: 121.1, + latitude: 31.1, + elevation: 6, + }); + render(); + + fireEvent.click(screen.getByRole("button", { name: "添加" })); + await waitFor(() => expect(mockSingleClickHandler).toBeDefined()); + await act(async () => { + mockSingleClickHandler?.({ + pixel: [0, 0], + coordinate: [13500030, 3600040], + stopPropagation: jest.fn(), + }); + await Promise.resolve(); + }); + + expect(mockGetSensorPlacementCandidate).toHaveBeenCalledWith("J2"); + }); + it("lets the Data Grid density selector control row sizing", () => { render(); @@ -207,4 +255,22 @@ describe("SchemeEditor notifications", () => { "automatic", ); }); + + it("places actions and status first and displays the selected pipe diameter", () => { + render(); + + expect(mockGridColumns.slice(0, 5).map((column) => column.field)).toEqual([ + "actions", + "adjustment_status", + "sequence", + "node_id", + "max_pipe_diameter", + ]); + const diameterColumn = mockGridColumns.find( + (column) => column.field === "max_pipe_diameter", + ); + expect(diameterColumn?.headerName).toBe("最大管径 (mm)"); + expect(diameterColumn?.valueFormatter?.(400)).toBe("400"); + expect(diameterColumn?.valueFormatter?.(null)).toBe("无"); + }); }); diff --git a/src/components/olmap/MonitoringPlaceOptimization/SchemeEditor.tsx b/src/components/olmap/MonitoringPlaceOptimization/SchemeEditor.tsx index 14895a1..f0f1d71 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/SchemeEditor.tsx +++ b/src/components/olmap/MonitoringPlaceOptimization/SchemeEditor.tsx @@ -45,10 +45,7 @@ import Point from "ol/geom/Point"; import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; import { Circle, Fill, Stroke, Style, Text } from "ol/style"; -import { fromLonLat, toLonLat } from "ol/proj"; import { useNotification } from "@refinedev/core"; -import { api } from "@/lib/api"; -import { config } from "@/config/config"; import { useMap } from "@components/olmap/core/MapComponent"; import { handleMapClickSelectFeatures } from "@/utils/mapQueryService"; import { @@ -65,6 +62,7 @@ import { } from "./schemeEditor"; import { exportSensorPlacementExcel, + getSensorPlacementCandidate, overwriteSensorPlacementScheme, } from "./schemeApi"; import SchemeDrawingDialog from "./SchemeDrawingDialog"; @@ -147,6 +145,8 @@ const SchemeEditor: React.FC = ({ const [saveDialogOpen, setSaveDialogOpen] = useState(false); const [drawingOpen, setDrawingOpen] = useState(false); const markerLayerRef = useRef | null>(null); + const activeRef = useRef(active); + activeRef.current = active; useEffect(() => { setEditor(createSchemeEditorState(scheme)); @@ -171,6 +171,7 @@ const SchemeEditor: React.FC = ({ }, zIndex: 120, }); + layer.setVisible(activeRef.current); markerLayerRef.current = layer; map.addLayer(layer); return () => { @@ -206,47 +207,8 @@ const SchemeEditor: React.FC = ({ const feature = await handleMapClickSelectFeatures(event, map); const nodeId = String(feature?.get("id") ?? feature?.getId() ?? "").trim(); if (!nodeId) return null; - const featureGeometry = feature?.getGeometry(); - const featureCoordinate = - featureGeometry instanceof Point - ? featureGeometry.getCoordinates() - : event.coordinate; - const projectedFeatureCoordinate = - Math.abs(featureCoordinate[0]) <= 180 && - Math.abs(featureCoordinate[1]) <= 90 - ? fromLonLat(featureCoordinate) - : featureCoordinate; - const resolution = map.getView().getResolution() ?? 1; - const isAlignedWithMap = - Math.hypot( - projectedFeatureCoordinate[0] - event.coordinate[0], - projectedFeatureCoordinate[1] - event.coordinate[1], - ) <= - resolution * 20; - const [mapX, mapY] = isAlignedWithMap - ? projectedFeatureCoordinate - : event.coordinate; try { - const response = await api.get<{ - id: string; - x: number; - y: number; - elevation: number; - }>(`${config.BACKEND_URL}/api/v1/junctions/properties`, { - params: { junction: nodeId }, - }); - if (!response.data?.id) return null; - const [longitude, latitude] = toLonLat([mapX, mapY]); - return { - node_id: String(response.data.id), - project_x: Number(response.data.x), - project_y: Number(response.data.y), - map_x: mapX, - map_y: mapY, - longitude, - latitude, - elevation: Number(response.data.elevation), - }; + return await getSensorPlacementCandidate(nodeId); } catch { return null; } @@ -436,6 +398,70 @@ const SchemeEditor: React.FC = ({ const columns = useMemo[]>( () => [ + { + field: "actions", + headerName: "操作", + width: scheme.can_edit ? 138 : 54, + sortable: false, + filterable: false, + renderCell: ({ row }) => ( + + + locateRow(row)} + sx={{ width: 40, height: 40 }} + > + + + + {scheme.can_edit && ( + <> + + activateMode("replace", row.node_id)} + sx={{ width: 40, height: 40 }} + > + + + + + + handleDelete(row.node_id)} + disabled={rows.length <= 1} + sx={{ width: 40, height: 40 }} + > + + + + + + )} + + ), + }, + { + field: "adjustment_status", + headerName: "调整状态", + width: 108, + renderCell: ({ value }) => { + const status = value as AdjustmentStatus; + return ( + + ); + }, + }, { field: "sequence", headerName: "序号", @@ -445,6 +471,21 @@ const SchemeEditor: React.FC = ({ sortable: false, }, { field: "node_id", headerName: "节点 ID", minWidth: 120, flex: 0.8 }, + { + field: "max_pipe_diameter", + headerName: "最大管径 (mm)", + description: "节点关联管道中的最大管径", + minWidth: 132, + flex: 0.75, + align: "right", + headerAlign: "right", + valueFormatter: (value) => { + if (value == null || !Number.isFinite(Number(value))) return "无"; + return Number(value).toLocaleString("zh-CN", { + maximumFractionDigits: 3, + }); + }, + }, { field: "longitude", headerName: "经度", @@ -507,70 +548,6 @@ const SchemeEditor: React.FC = ({ headerAlign: "right", valueFormatter: (value) => Number(value).toFixed(3), }, - { - field: "adjustment_status", - headerName: "调整状态", - width: 108, - renderCell: ({ value }) => { - const status = value as AdjustmentStatus; - return ( - - ); - }, - }, - { - field: "actions", - headerName: "操作", - width: scheme.can_edit ? 138 : 54, - sortable: false, - filterable: false, - renderCell: ({ row }) => ( - - - locateRow(row)} - sx={{ width: 40, height: 40 }} - > - - - - {scheme.can_edit && ( - <> - - activateMode("replace", row.node_id)} - sx={{ width: 40, height: 40 }} - > - - - - - - handleDelete(row.node_id)} - disabled={rows.length <= 1} - sx={{ width: 40, height: 40 }} - > - - - - - - )} - - ), - }, ], [activateMode, handleDelete, locateRow, rows.length, scheme.can_edit], ); diff --git a/src/components/olmap/MonitoringPlaceOptimization/SchemeQuery.tsx b/src/components/olmap/MonitoringPlaceOptimization/SchemeQuery.tsx index c57a603..7345846 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/SchemeQuery.tsx +++ b/src/components/olmap/MonitoringPlaceOptimization/SchemeQuery.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { Box, Button, @@ -53,6 +53,7 @@ interface SchemaItem { interface SchemeQueryProps { schemes?: SchemeRecord[]; + active?: boolean; onSchemesChange?: (schemes: SchemeRecord[]) => void; onEdit?: (id: number) => void; network?: string; @@ -77,6 +78,7 @@ export const createMonitoringSchemeQueryState = const SchemeQuery: React.FC = ({ schemes: externalSchemes, + active = true, onSchemesChange, onEdit, network = NETWORK_NAME, @@ -98,6 +100,8 @@ const SchemeQuery: React.FC = ({ const [highlightLayer, setHighlightLayer] = useState | null>(null); + const activeRef = useRef(active); + activeRef.current = active; const [highlightFeatures, setHighlightFeatures] = useState([]); // 使用外部提供的 schemes 或内部状态 const schemes = @@ -142,6 +146,7 @@ const SchemeQuery: React.FC = ({ queryable: false, }, }); + highlightLayer.setVisible(activeRef.current); map.addLayer(highlightLayer); setHighlightLayer(highlightLayer); @@ -151,6 +156,10 @@ const SchemeQuery: React.FC = ({ }; }, [map]); + useEffect(() => { + highlightLayer?.setVisible(active); + }, [active, highlightLayer]); + // 高亮要素的函数 useEffect(() => { if (!highlightLayer) { diff --git a/src/components/olmap/MonitoringPlaceOptimization/engineeringDrawing.test.ts b/src/components/olmap/MonitoringPlaceOptimization/engineeringDrawing.test.ts index cc18913..42dc0b4 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/engineeringDrawing.test.ts +++ b/src/components/olmap/MonitoringPlaceOptimization/engineeringDrawing.test.ts @@ -107,6 +107,7 @@ const point = ( map_y: number, ): SensorPointRow => ({ node_id, + max_pipe_diameter: 300, sequence, map_x, map_y, diff --git a/src/components/olmap/MonitoringPlaceOptimization/schemeApi.ts b/src/components/olmap/MonitoringPlaceOptimization/schemeApi.ts index 1428e13..f73f3e2 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/schemeApi.ts +++ b/src/components/olmap/MonitoringPlaceOptimization/schemeApi.ts @@ -3,6 +3,7 @@ import { config } from "@/config/config"; import type { AdjustmentStatus, SensorPlacementScheme, + SensorPoint, } from "./types"; export interface OptimizeSchemeInput { @@ -32,6 +33,15 @@ export const getSensorPlacementScheme = async ( return response.data; }; +export const getSensorPlacementCandidate = async ( + nodeId: string, +): Promise => { + const response = await api.get( + `${config.BACKEND_URL}/api/v1/sensor-placement-candidates/${encodeURIComponent(nodeId)}`, + ); + return response.data; +}; + export const overwriteSensorPlacementScheme = async ( schemeId: number, expectedSensorLocation: string[], diff --git a/src/components/olmap/MonitoringPlaceOptimization/schemeEditor.test.ts b/src/components/olmap/MonitoringPlaceOptimization/schemeEditor.test.ts index f26cd6d..b0c62b6 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/schemeEditor.test.ts +++ b/src/components/olmap/MonitoringPlaceOptimization/schemeEditor.test.ts @@ -13,6 +13,7 @@ import type { SensorPlacementScheme, SensorPoint } from "./types"; const point = (node_id: string): SensorPoint => ({ node_id, + max_pipe_diameter: 300, project_x: Number(node_id.slice(1)) * 10, project_y: Number(node_id.slice(1)) * 20, map_x: 13500000 + Number(node_id.slice(1)) * 10, diff --git a/src/components/olmap/MonitoringPlaceOptimization/types.ts b/src/components/olmap/MonitoringPlaceOptimization/types.ts index 1c3b058..38e0984 100644 --- a/src/components/olmap/MonitoringPlaceOptimization/types.ts +++ b/src/components/olmap/MonitoringPlaceOptimization/types.ts @@ -2,6 +2,7 @@ export type AdjustmentStatus = "current" | "original" | "added" | "replaced"; export interface SensorPoint { node_id: string; + max_pipe_diameter: number | null; project_x: number; project_y: number; map_x: number; diff --git a/src/generated/serverApi.ts b/src/generated/serverApi.ts index 611f8ce..c0737cd 100644 --- a/src/generated/serverApi.ts +++ b/src/generated/serverApi.ts @@ -4833,6 +4833,23 @@ export interface paths { patch?: never; trace?: never; }; + "/api/v1/sensor-placement-candidates/{node_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 获取监测点候选节点详情 */ + get: operations["get_sensor_placement_candidates_node_id"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/v1/sensor-placement-optimization-runs": { parameters: { query?: never; @@ -8153,6 +8170,11 @@ export interface components { map_x: number; /** Map Y */ map_y: number; + /** + * Max Pipe Diameter + * @description 节点关联管道的最大管径,单位:毫米 + */ + max_pipe_diameter: number | null; /** Node Id */ node_id: string; /** Project X */ @@ -31651,6 +31673,84 @@ export interface operations { }; }; }; + get_sensor_placement_candidates_node_id: { + parameters: { + query?: never; + header: { + "X-Project-Id": string; + }; + path: { + node_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SensorPointResponse"]; + }; + }; + /** @description Authentication required */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Insufficient permission */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Resource conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Dependency unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; post_sensor_placement_optimization_runs: { parameters: { query?: never;