feat(lidar): add ground segmentation diagnostic benchmark
This commit is contained in:
@@ -59,6 +59,62 @@ export interface LidarReplayDetail {
|
||||
stages: LidarStageReadiness[];
|
||||
}
|
||||
|
||||
export interface LidarGroundProviderSummary {
|
||||
providerId: string;
|
||||
sourceCommit: string | null;
|
||||
}
|
||||
|
||||
export interface LidarGroundBenchmark {
|
||||
benchmarkId: string;
|
||||
replayPackId: string;
|
||||
sessionId: string;
|
||||
status: "diagnostic-only";
|
||||
frames: number;
|
||||
points: number;
|
||||
inputDomain: {
|
||||
accepted: false;
|
||||
representation: string;
|
||||
physicalSensorHeightKnown: boolean;
|
||||
sensorScanGeometryKnown: boolean;
|
||||
reason: string;
|
||||
};
|
||||
labels: {
|
||||
status: "missing-independent-review";
|
||||
metricsAvailable: false;
|
||||
};
|
||||
current: {
|
||||
provider: LidarGroundProviderSummary;
|
||||
groundFraction: LidarDistribution;
|
||||
assignedFraction: LidarDistribution;
|
||||
latencyMs: LidarDistribution;
|
||||
};
|
||||
candidate: {
|
||||
provider: LidarGroundProviderSummary;
|
||||
groundFraction: LidarDistribution;
|
||||
assignedFraction: LidarDistribution;
|
||||
latencyMs: LidarDistribution;
|
||||
};
|
||||
comparison: {
|
||||
algorithmGroundIou: LidarDistribution;
|
||||
groundDisagreementFraction: LidarDistribution;
|
||||
isAccuracyMetric: false;
|
||||
};
|
||||
decision: {
|
||||
status: "do-not-promote-on-current-vendor-map";
|
||||
productionPromotion: false;
|
||||
reasons: string[];
|
||||
nextGate: string;
|
||||
};
|
||||
createdAtUtc: string | null;
|
||||
}
|
||||
|
||||
export interface LidarGroundBenchmarkCatalog {
|
||||
configured: boolean;
|
||||
validTotal: number;
|
||||
invalidTotal: number;
|
||||
items: LidarGroundBenchmark[];
|
||||
}
|
||||
|
||||
export class LidarReplayContractError extends Error {}
|
||||
|
||||
export class LidarReplayApiError extends Error {
|
||||
@@ -73,8 +129,10 @@ type LidarFetch = (
|
||||
) => Promise<Response>;
|
||||
|
||||
const SAFE_PACK_ID = /^lidar-replay-pack-[a-f0-9]{64}$/;
|
||||
const SAFE_GROUND_BENCHMARK_ID = /^ground-benchmark-[a-f0-9]{64}$/;
|
||||
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$/;
|
||||
const SHA256 = /^[a-f0-9]{64}$/;
|
||||
const GIT_SHA1 = /^[a-f0-9]{40}$/;
|
||||
|
||||
function record(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
@@ -258,6 +316,139 @@ export function parseLidarReplayDetail(value: unknown): LidarReplayDetail {
|
||||
};
|
||||
}
|
||||
|
||||
function groundProvider(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): LidarGroundProviderSummary {
|
||||
const source = record(value, label);
|
||||
return {
|
||||
providerId: string(source.provider_id, `${label}.provider_id`, SAFE_ID),
|
||||
sourceCommit:
|
||||
source.source_commit === undefined || source.source_commit === null
|
||||
? null
|
||||
: string(source.source_commit, `${label}.source_commit`, GIT_SHA1),
|
||||
};
|
||||
}
|
||||
|
||||
function groundBranch(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): LidarGroundBenchmark["current"] {
|
||||
const source = record(value, label);
|
||||
return {
|
||||
provider: groundProvider(source.provider, `${label}.provider`),
|
||||
groundFraction: distribution(
|
||||
source.ground_fraction,
|
||||
`${label}.ground_fraction`,
|
||||
),
|
||||
assignedFraction: distribution(
|
||||
source.assigned_fraction,
|
||||
`${label}.assigned_fraction`,
|
||||
),
|
||||
latencyMs: distribution(source.latency_ms, `${label}.latency_ms`),
|
||||
};
|
||||
}
|
||||
|
||||
function groundBenchmark(value: unknown): LidarGroundBenchmark {
|
||||
const source = record(value, "LiDAR ground benchmark");
|
||||
if (source.status !== "diagnostic-only") {
|
||||
throw new LidarReplayContractError("Ground benchmark status несовместим");
|
||||
}
|
||||
const inputDomain = record(source.input_domain, "input_domain");
|
||||
const labels = record(source.labels, "labels");
|
||||
const comparison = record(source.comparison, "comparison");
|
||||
const decision = record(source.decision, "decision");
|
||||
if (
|
||||
inputDomain.accepted !== false
|
||||
|| labels.status !== "missing-independent-review"
|
||||
|| labels.metrics_available !== false
|
||||
|| comparison.is_accuracy_metric !== false
|
||||
|| decision.status !== "do-not-promote-on-current-vendor-map"
|
||||
|| decision.production_promotion !== false
|
||||
) {
|
||||
throw new LidarReplayContractError(
|
||||
"Ground benchmark завышает readiness или accuracy",
|
||||
);
|
||||
}
|
||||
return {
|
||||
benchmarkId: string(
|
||||
source.benchmark_id,
|
||||
"benchmark_id",
|
||||
SAFE_GROUND_BENCHMARK_ID,
|
||||
),
|
||||
replayPackId: string(source.replay_pack_id, "replay_pack_id", SAFE_PACK_ID),
|
||||
sessionId: string(source.session_id, "session_id", SAFE_ID),
|
||||
status: "diagnostic-only",
|
||||
frames: integer(source.frames, "frames"),
|
||||
points: integer(source.points, "points"),
|
||||
inputDomain: {
|
||||
accepted: false,
|
||||
representation: string(
|
||||
inputDomain.representation,
|
||||
"input_domain.representation",
|
||||
SAFE_ID,
|
||||
),
|
||||
physicalSensorHeightKnown: boolean(
|
||||
inputDomain.physical_sensor_height_known,
|
||||
"input_domain.physical_sensor_height_known",
|
||||
),
|
||||
sensorScanGeometryKnown: boolean(
|
||||
inputDomain.sensor_scan_geometry_known,
|
||||
"input_domain.sensor_scan_geometry_known",
|
||||
),
|
||||
reason: string(inputDomain.reason, "input_domain.reason"),
|
||||
},
|
||||
labels: {
|
||||
status: "missing-independent-review",
|
||||
metricsAvailable: false,
|
||||
},
|
||||
current: groundBranch(source.current, "current"),
|
||||
candidate: groundBranch(source.candidate, "candidate"),
|
||||
comparison: {
|
||||
algorithmGroundIou: distribution(
|
||||
comparison.algorithm_to_algorithm_ground_iou,
|
||||
"comparison.algorithm_ground_iou",
|
||||
),
|
||||
groundDisagreementFraction: distribution(
|
||||
comparison.ground_disagreement_fraction,
|
||||
"comparison.ground_disagreement_fraction",
|
||||
),
|
||||
isAccuracyMetric: false,
|
||||
},
|
||||
decision: {
|
||||
status: "do-not-promote-on-current-vendor-map",
|
||||
productionPromotion: false,
|
||||
reasons: array(decision.reasons, "decision.reasons").map((reason) =>
|
||||
string(reason, "decision.reason")
|
||||
),
|
||||
nextGate: string(decision.next_gate, "decision.next_gate"),
|
||||
},
|
||||
createdAtUtc:
|
||||
source.created_at_utc === null || source.created_at_utc === undefined
|
||||
? null
|
||||
: string(source.created_at_utc, "created_at_utc"),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseLidarGroundBenchmarkCatalog(
|
||||
value: unknown,
|
||||
): LidarGroundBenchmarkCatalog {
|
||||
const source = record(value, "LiDAR ground catalog");
|
||||
if (
|
||||
source.schema_version
|
||||
!== "missioncore.lidar-ground-benchmark-catalog/v1"
|
||||
|| source.access !== "read-only"
|
||||
) {
|
||||
throw new LidarReplayContractError("LiDAR ground catalog несовместим");
|
||||
}
|
||||
return {
|
||||
configured: boolean(source.configured, "configured"),
|
||||
validTotal: integer(source.valid_total, "valid_total"),
|
||||
invalidTotal: integer(source.invalid_total, "invalid_total"),
|
||||
items: array(source.items, "items").map(groundBenchmark),
|
||||
};
|
||||
}
|
||||
|
||||
async function responseJson(
|
||||
response: Response,
|
||||
fallback: string,
|
||||
@@ -309,3 +500,24 @@ export async function fetchLidarReplayDetail(
|
||||
await responseJson(response, "Не удалось получить LiDAR quality report."),
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchLidarGroundBenchmarks(
|
||||
packId: string,
|
||||
options: { signal?: AbortSignal; fetcher?: LidarFetch } = {},
|
||||
): Promise<LidarGroundBenchmarkCatalog> {
|
||||
if (!SAFE_PACK_ID.test(packId)) {
|
||||
throw new LidarReplayContractError("Некорректный LiDAR pack id");
|
||||
}
|
||||
const fetcher = options.fetcher ?? fetch;
|
||||
const response = await fetcher(
|
||||
`/api/v1/lidar/ground-benchmarks?pack_id=${encodeURIComponent(packId)}&limit=20`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal: options.signal,
|
||||
},
|
||||
);
|
||||
return parseLidarGroundBenchmarkCatalog(
|
||||
await responseJson(response, "Не удалось получить LiDAR ground benchmark."),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user