Initial GeoIntel V1 foundation
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-16 23:36:32 +02:00
commit 6ea3586a3e
605 changed files with 45284 additions and 0 deletions
View File
+12
View File
@@ -0,0 +1,12 @@
import { apiGet, apiPost, apiPatch } from './client'
import type { AreaCreate, AreaListResponse, AreaRead } from '../../types'
export const areasApi = {
list: (projectId: string): Promise<AreaListResponse> => apiGet<AreaListResponse>(`/api/v1/projects/${projectId}/areas`),
create: (projectId: string, payload: AreaCreate): Promise<AreaRead> =>
apiPost<AreaRead>(`/api/v1/projects/${projectId}/areas`, payload),
get: (projectId: string, areaId: string): Promise<AreaRead> =>
apiGet<AreaRead>(`/api/v1/projects/${projectId}/areas/${areaId}`),
update: (projectId: string, areaId: string, payload: Partial<AreaCreate>): Promise<AreaRead> =>
apiPatch<AreaRead>(`/api/v1/projects/${projectId}/areas/${areaId}`, payload),
}
+66
View File
@@ -0,0 +1,66 @@
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "";
export function apiUrl(path: string): string {
return `${API_BASE_URL}${path}`;
}
export class ApiHttpError extends Error {
readonly code: string;
readonly details?: unknown;
constructor(message: string, code = "REQUEST_ERROR", details?: unknown) {
super(message);
this.name = "ApiHttpError";
this.code = code;
this.details = details;
}
}
async function parseResponse<T>(response: Response): Promise<T> {
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const code = payload?.error?.code ?? "REQUEST_ERROR";
const message = payload?.error?.message ?? `Request failed (${response.status})`;
const details = payload?.error?.details;
throw new ApiHttpError(message, code, details);
}
return payload.data as T;
}
export async function apiGet<T>(path: string): Promise<T> {
const response = await fetch(apiUrl(path));
return parseResponse<T>(response);
}
export async function apiPost<T>(path: string, body?: object): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
return parseResponse<T>(response);
}
export async function apiPatch<T>(path: string, body?: object): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
return parseResponse<T>(response);
}
export async function apiDelete<T>(path: string): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "DELETE",
});
return parseResponse<T>(response);
}
export async function apiMultipart<T>(path: string, form: FormData): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "POST",
body: form,
});
return parseResponse<T>(response);
}
+109
View File
@@ -0,0 +1,109 @@
import { apiGet, apiMultipart, apiPost } from './client'
import type {
DatasetCreateResponse,
DatasetListResponse,
RasterMetadataResponse,
RasterInspectResponse,
RasterStatsResponse,
RasterPreviewResponse,
JobRead,
VectorBBoxResponse,
VectorStatsResponse,
VectorSummary,
RasterNdviRequest,
RasterNdwiRequest,
RasterNdbiRequest,
} from '../../types'
export const datasetsApi = {
list: (projectId: string): Promise<DatasetListResponse> =>
apiGet<DatasetListResponse>(`/api/v1/projects/${projectId}/datasets`),
upload: (
projectId: string,
payload: {
file: File
datasetType: string
source: string
datasetRole: string
sourceName?: string
referenceLayerName?: string
sourceMetadataJson?: string
provenanceMetadataJson?: string
areaId?: string
},
): Promise<DatasetCreateResponse> => {
const form = new FormData()
form.append('file', payload.file)
form.append('dataset_type', payload.datasetType)
form.append('source', payload.source)
form.append('dataset_role', payload.datasetRole)
if (payload.sourceName) {
form.append('source_name', payload.sourceName)
}
if (payload.referenceLayerName) {
form.append('reference_layer_name', payload.referenceLayerName)
}
if (payload.sourceMetadataJson) {
form.append('source_metadata_json', payload.sourceMetadataJson)
}
if (payload.provenanceMetadataJson) {
form.append('provenance_metadata_json', payload.provenanceMetadataJson)
}
if (payload.areaId) {
form.append('area_id', payload.areaId)
}
return apiMultipart<DatasetCreateResponse>(`/api/v1/projects/${projectId}/datasets/upload`, form)
},
refreshMetadata: (projectId: string, datasetId: string): Promise<DatasetCreateResponse> =>
apiPost<DatasetCreateResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/metadata/refresh`, {}),
inspectRaster: (projectId: string, datasetId: string): Promise<RasterInspectResponse> =>
apiGet<RasterInspectResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/inspect`),
rasterStats: (projectId: string, datasetId: string): Promise<RasterStatsResponse> =>
apiGet<RasterStatsResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/stats`),
inspectVector: (projectId: string, datasetId: string): Promise<{ dataset: DatasetCreateResponse; summary: VectorSummary | null; metadata: unknown }> =>
apiGet<{ dataset: DatasetCreateResponse; summary: VectorSummary | null; metadata: unknown }>(
`/api/v1/projects/${projectId}/datasets/${datasetId}/vector/inspect`,
),
vectorBbox: (projectId: string, datasetId: string): Promise<VectorBBoxResponse> =>
apiGet<VectorBBoxResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/vector/bbox`),
vectorSummary: (projectId: string, datasetId: string): Promise<VectorSummary> =>
apiGet<VectorSummary>(`/api/v1/projects/${projectId}/datasets/${datasetId}/vector/summary`),
vectorStats: (projectId: string, datasetId: string): Promise<VectorStatsResponse> =>
apiGet<VectorStatsResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/vector/stats`),
vectorClip: (projectId: string, datasetId: string, payload: { area_id: string; output_name?: string }) =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/vector/clip`, payload),
vectorBuffer: (projectId: string, datasetId: string, payload: { distance_m: number; dissolve?: boolean; output_name?: string }) =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/vector/buffer`, payload),
vectorIntersect: (projectId: string, datasetId: string, payload: { other_dataset_id: string; output_name?: string }) =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/vector/intersect`, payload),
rasterMetadata: (projectId: string, datasetId: string): Promise<RasterMetadataResponse> =>
apiGet<RasterMetadataResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/metadata`),
rasterInspect: (projectId: string, datasetId: string): Promise<RasterInspectResponse> =>
apiGet<RasterInspectResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/inspect`),
rasterTile: (
projectId: string,
datasetId: string,
payload: { tile_size?: number; overlap?: number; output_name?: string } = {},
): Promise<JobRead> =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/tile`, payload),
rasterReproject: (
projectId: string,
datasetId: string,
payload: { target_crs: string; resampling?: string; output_name?: string },
): Promise<JobRead> => apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/reproject`, payload),
rasterNdvi: (projectId: string, datasetId: string, payload: RasterNdviRequest): Promise<JobRead> =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/indices/ndvi`, payload),
rasterNdwi: (projectId: string, datasetId: string, payload: RasterNdwiRequest): Promise<JobRead> =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/indices/ndwi`, payload),
rasterNdbi: (projectId: string, datasetId: string, payload: RasterNdbiRequest): Promise<JobRead> =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/indices/ndbi`, payload),
rasterPreview: (projectId: string, datasetId: string): Promise<RasterPreviewResponse> =>
apiGet<RasterPreviewResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/preview`),
rasterClip: (
projectId: string,
datasetId: string,
payload: { area_id: string; output_name?: string },
): Promise<JobRead> => apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/clip`, payload),
getContent: (projectId: string, datasetId: string): Promise<GeoJSON.FeatureCollection> =>
apiGet<GeoJSON.FeatureCollection>(`/api/v1/projects/${projectId}/datasets/${datasetId}/content`),
}
+6
View File
@@ -0,0 +1,6 @@
import { apiPost } from './client'
import type { DemoWorkflowResponse } from '../../types'
export const demoApi = {
seedWorkflow: (): Promise<DemoWorkflowResponse> => apiPost<DemoWorkflowResponse>('/api/v1/demo/workflow'),
}
+44
View File
@@ -0,0 +1,44 @@
import { apiGet, apiPost } from './client'
import type {
DetectionListResponse,
DetectionModelsResponse,
DetectionQaRequest,
DetectionQaResult,
DetectionRunListResponse,
DetectionRunRead,
DetectionRunRequest,
DetectionRunResponse,
} from '../../types'
function queryString(params: Record<string, string | number | null | undefined>): string {
const searchParams = new URLSearchParams()
Object.entries(params).forEach(([key, value]) => {
if (value !== null && value !== undefined && value !== '') {
searchParams.set(key, String(value))
}
})
const query = searchParams.toString()
return query ? `?${query}` : ''
}
export const detectionApi = {
listModels: (): Promise<DetectionModelsResponse> => apiGet<DetectionModelsResponse>('/api/v1/detection/models'),
run: (payload: DetectionRunRequest): Promise<DetectionRunResponse> =>
apiPost<DetectionRunResponse>('/api/v1/detection/run', payload),
listRuns: (params: { project_id?: string | null; dataset_id?: string | null } = {}): Promise<DetectionRunListResponse> =>
apiGet<DetectionRunListResponse>(`/api/v1/detection/runs${queryString(params)}`),
getRun: (analysisRunId: string): Promise<DetectionRunRead> =>
apiGet<DetectionRunRead>(`/api/v1/detection/runs/${analysisRunId}`),
listDetections: (
analysisRunId: string,
params: { dataset_id?: string | null; class_name?: string | null; min_confidence?: number | null } = {},
): Promise<DetectionListResponse> =>
apiGet<DetectionListResponse>(`/api/v1/detection/runs/${analysisRunId}/detections${queryString(params)}`),
getRunGeoJson: (
analysisRunId: string,
params: { class_name?: string | null; min_confidence?: number | null } = {},
): Promise<GeoJSON.FeatureCollection> =>
apiGet<GeoJSON.FeatureCollection>(`/api/v1/detection/runs/${analysisRunId}/geojson${queryString(params)}`),
compareWithReference: (analysisRunId: string, payload: DetectionQaRequest): Promise<DetectionQaResult> =>
apiPost<DetectionQaResult>(`/api/v1/detection/runs/${analysisRunId}/qa/reference`, payload),
}
+21
View File
@@ -0,0 +1,21 @@
import { apiGet, apiPost, apiUrl } from './client'
import type { ExportContentResponse, ExportCreateResponse, ExportKind, ExportListResponse, ExportRead } from '../../types'
export const exportsApi = {
exportGeojson: (
payload: { dataset_id?: string; analysis_run_id?: string; export_kind?: ExportKind; name?: string } | string,
): Promise<ExportCreateResponse> => {
const body = typeof payload === 'string' ? { dataset_id: payload, export_kind: 'dataset' } : payload
return apiPost<ExportCreateResponse>(`/api/v1/exports/geojson`, body)
},
exportProjectMetadata: (projectId: string, name?: string): Promise<ExportCreateResponse> =>
apiPost<ExportCreateResponse>(`/api/v1/exports/metadata`, { project_id: projectId, name }),
exportProjectReport: (projectId: string, name?: string): Promise<ExportCreateResponse> =>
apiPost<ExportCreateResponse>(`/api/v1/exports/report`, { project_id: projectId, name }),
listProjectExports: (projectId: string): Promise<ExportListResponse> =>
apiGet<ExportListResponse>(`/api/v1/exports/projects/${projectId}/exports`),
getExport: (exportId: string): Promise<ExportRead> => apiGet<ExportRead>(`/api/v1/exports/${exportId}`),
getContent: (exportId: string): Promise<ExportContentResponse> =>
apiGet<ExportContentResponse>(`/api/v1/exports/${exportId}/content`),
downloadUrl: (exportId: string): string => apiUrl(`/api/v1/exports/${exportId}/download`),
}
+67
View File
@@ -0,0 +1,67 @@
import { apiGet, apiPost } from './client'
import type {
ProviderCapability,
ProviderCapabilitiesResponse,
ProviderImportResponse,
ProviderLayersResponse,
ProviderStatusResponse,
SystemCapabilitiesResponse,
} from '../../types'
const normalize = (layers: string[] = []) => layers.filter((value) => value.trim().length > 0)
export const externalApi = {
listSystemCapabilities: (): Promise<SystemCapabilitiesResponse> =>
apiGet<SystemCapabilitiesResponse>('/api/v1/system/capabilities'),
listProviders: (): Promise<ProviderCapabilitiesResponse> =>
apiGet<ProviderCapabilitiesResponse>('/api/v1/external/providers'),
listProviderCapabilities: (): Promise<ProviderCapabilitiesResponse> =>
apiGet<ProviderCapabilitiesResponse>('/api/v1/external/providers/capabilities'),
getProvider: (providerName: string): Promise<ProviderCapability> =>
apiGet<ProviderCapability>(`/api/v1/external/providers/${providerName}`),
getProviderLayers: (providerName: string): Promise<ProviderLayersResponse> =>
apiGet<ProviderLayersResponse>(`/api/v1/external/providers/${providerName}/layers`),
getProviderStatus: (providerName: string): Promise<ProviderStatusResponse> =>
apiGet<ProviderStatusResponse>(`/api/v1/external/providers/${providerName}/status`),
requestProviderImport: (providerName: string, payload: {
projectId: string
areaId?: string
layers: string[]
datasetRole?: string
}): Promise<ProviderImportResponse> =>
apiPost<ProviderImportResponse>(`/api/v1/external/providers/${providerName}/import`, {
project_id: payload.projectId,
area_id: payload.areaId ?? null,
layers: normalize(payload.layers),
dataset_role: payload.datasetRole ?? null,
}),
runOsmFetch: (payload: {
projectId: string
areaId?: string
layers: string[]
}): Promise<ProviderFetchResponse> =>
apiPost<ProviderFetchResponse>('/api/v1/external/osm/fetch', {
project_id: payload.projectId,
area_id: payload.areaId ?? null,
layers: normalize(payload.layers),
}),
runGrbFetch: (payload: {
projectId: string
areaId?: string
layers: string[]
}): Promise<ProviderFetchResponse> =>
apiPost<ProviderFetchResponse>('/api/v1/external/grb/fetch', {
project_id: payload.projectId,
area_id: payload.areaId ?? null,
layers: normalize(payload.layers),
}),
}
export interface ProviderFetchResponse {
provider: string
status: string
message: string
requested_layers: string[]
project_id: string
area_id: string | null
}
+10
View File
@@ -0,0 +1,10 @@
export { areasApi } from './areas'
export { datasetsApi } from './datasets'
export { demoApi } from './demo'
export { detectionApi } from './detection'
export { externalApi } from './external'
export { qaApi } from './qa'
export { segmentationApi } from './segmentation'
export { exportsApi } from './exports'
export { jobsApi } from './jobs'
export { projectsApi } from './projects'
+31
View File
@@ -0,0 +1,31 @@
import { apiGet, apiPost } from './client'
import type { JobListResponse, JobRead } from '../../types'
export const jobsApi = {
create: (projectId: string, payload: {
job_type: string
project_id: string
dataset_id?: string | null
input_dataset_id?: string | null
output_dataset_id?: string | null
parameters_json?: Record<string, unknown>
}): Promise<JobRead> =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/jobs`, payload),
list: (projectId: string, options?: { dataset_id?: string; limit?: number; offset?: number }): Promise<JobListResponse> => {
const query = new URLSearchParams()
if (options?.dataset_id) {
query.set('dataset_id', options.dataset_id)
}
if (options?.limit) {
query.set('limit', String(options.limit))
}
if (options?.offset) {
query.set('offset', String(options.offset))
}
const queryPart = query.toString() ? `?${query}` : ''
return apiGet<JobListResponse>(`/api/v1/projects/${projectId}/jobs${queryPart}`)
},
get: (projectId: string, jobId: string): Promise<JobRead> => apiGet<JobRead>(`/api/v1/projects/${projectId}/jobs/${jobId}`),
status: (projectId: string, jobId: string): Promise<{ status: string; error_message?: string | null; started_at?: string | null; finished_at?: string | null; result_json?: Record<string, unknown> | null }> =>
apiGet(`/api/v1/projects/${projectId}/jobs/${jobId}/status`),
}
+12
View File
@@ -0,0 +1,12 @@
import { apiDelete, apiGet, apiPatch, apiPost } from './client'
import type { ProjectCreate, ProjectListResponse, ProjectRead } from '../../types'
export const projectsApi = {
list: (): Promise<ProjectListResponse> => apiGet<ProjectListResponse>('/api/v1/projects'),
create: (payload: ProjectCreate): Promise<ProjectRead> => apiPost<ProjectRead>('/api/v1/projects', payload),
get: (id: string): Promise<ProjectRead> => apiGet<ProjectRead>(`/api/v1/projects/${id}`),
update: (id: string, payload: Partial<ProjectCreate>): Promise<ProjectRead> =>
apiPatch<ProjectRead>(`/api/v1/projects/${id}`, payload),
delete: (id: string): Promise<{ deleted: boolean }> =>
apiDelete<{ deleted: boolean }>(`/api/v1/projects/${id}`),
}
+9
View File
@@ -0,0 +1,9 @@
import { apiGet, apiPost } from './client'
import type { QaComparisonRequest, JobRead, QualityCheckListResponse } from '../../types'
export const qaApi = {
runQa: (payload: QaComparisonRequest): Promise<JobRead> =>
apiPost<JobRead>('/api/v1/qa/detections-vs-reference', payload),
listQualityChecks: (projectId: string): Promise<QualityCheckListResponse> =>
apiGet<QualityCheckListResponse>(`/api/v1/projects/${projectId}/quality-checks`),
}
+44
View File
@@ -0,0 +1,44 @@
import { apiGet, apiPost } from './client'
import type {
SegmentationListResponse,
SegmentationModelsResponse,
SegmentationQaRequest,
SegmentationQaResult,
SegmentationRunListResponse,
SegmentationRunRead,
SegmentationRunRequest,
SegmentationRunResponse,
} from '../../types'
function queryString(params: Record<string, string | number | null | undefined>): string {
const searchParams = new URLSearchParams()
Object.entries(params).forEach(([key, value]) => {
if (value !== null && value !== undefined && value !== '') {
searchParams.set(key, String(value))
}
})
const query = searchParams.toString()
return query ? `?${query}` : ''
}
export const segmentationApi = {
listModels: (): Promise<SegmentationModelsResponse> => apiGet<SegmentationModelsResponse>('/api/v1/segmentation/models'),
run: (payload: SegmentationRunRequest): Promise<SegmentationRunResponse> =>
apiPost<SegmentationRunResponse>('/api/v1/segmentation/run', payload),
listRuns: (params: { project_id?: string | null; dataset_id?: string | null } = {}): Promise<SegmentationRunListResponse> =>
apiGet<SegmentationRunListResponse>(`/api/v1/segmentation/runs${queryString(params)}`),
getRun: (analysisRunId: string): Promise<SegmentationRunRead> =>
apiGet<SegmentationRunRead>(`/api/v1/segmentation/runs/${analysisRunId}`),
listSegmentations: (
analysisRunId: string,
params: { dataset_id?: string | null; class_name?: string | null; min_confidence?: number | null } = {},
): Promise<SegmentationListResponse> =>
apiGet<SegmentationListResponse>(`/api/v1/segmentation/runs/${analysisRunId}/segmentations${queryString(params)}`),
getRunGeoJson: (
analysisRunId: string,
params: { class_name?: string | null; min_confidence?: number | null } = {},
): Promise<GeoJSON.FeatureCollection> =>
apiGet<GeoJSON.FeatureCollection>(`/api/v1/segmentation/runs/${analysisRunId}/geojson${queryString(params)}`),
compareWithReference: (analysisRunId: string, payload: SegmentationQaRequest): Promise<SegmentationQaResult> =>
apiPost<SegmentationQaResult>(`/api/v1/segmentation/runs/${analysisRunId}/qa/reference`, payload),
}