feat(polygon): add full-frame operator review

This commit is contained in:
DCCONSTRUCTIONS
2026-07-25 16:45:10 +03:00
parent cea63f9a1e
commit b037076506
10 changed files with 2080 additions and 330 deletions
@@ -63,6 +63,36 @@ export interface GroundFailurePreview {
patchworkDisagreement: number[];
}
export interface GroundReviewFrameMetrics {
groundIou: number;
naturalGroundRecall: number;
obstacleNonGroundRecall: number;
latencyMs: number;
}
export interface GroundReviewFrameSummary {
sequence: number;
frameId: string;
sourcePointCount: number;
pointCount: number;
current: GroundReviewFrameMetrics;
patchwork: GroundReviewFrameMetrics;
groundIouDelta: number;
}
export interface GroundReview {
runId: string;
identitySha256: string;
sourceId: string;
frameCount: number;
previewPoints: number;
frames: GroundReviewFrameSummary[];
}
export interface GroundReviewFrame extends GroundFailurePreview {
intensity0To255: number[];
}
export class GroundQualificationContractError extends Error {}
export class GroundQualificationApiError extends Error {
@@ -130,6 +160,14 @@ function boolean(value: unknown, field: string): boolean {
return value;
}
function integer(value: unknown, field: string, minimum = 0): number {
const result = number(value, field, minimum);
if (!Number.isInteger(result)) {
throw new GroundQualificationContractError(`${field} должен быть целым числом.`);
}
return result;
}
function aggregate(value: unknown, field: string): QualificationMetricAggregate {
const source = record(value, field);
const micro = record(source.micro, `${field}.micro`);
@@ -316,6 +354,113 @@ export function decodeGroundFailurePreview(payload: unknown): GroundFailurePrevi
};
}
function reviewFrameMetrics(value: unknown, field: string): GroundReviewFrameMetrics {
const source = record(value, field);
return {
groundIou: fraction(source.ground_iou, `${field}.ground_iou`),
naturalGroundRecall: fraction(
source.natural_ground_recall,
`${field}.natural_ground_recall`,
),
obstacleNonGroundRecall: fraction(
source.obstacle_non_ground_recall,
`${field}.obstacle_non_ground_recall`,
),
latencyMs: number(source.latency_ms, `${field}.latency_ms`),
};
}
export function decodeGroundReview(payload: unknown): GroundReview {
const source = record(payload, "ground review");
if (
source.schema_version !== "missioncore.polygon-ground-review/v1"
|| source.access !== "read-only"
) {
throw new GroundQualificationContractError("Неизвестная схема покадрового просмотра.");
}
const frameCount = integer(source.frame_count, "frame_count", 1);
const frames = array(source.frames, "frames", 2_000).map(
(value, index): GroundReviewFrameSummary => {
const frame = record(value, `frames[${index}]`);
const sequence = integer(frame.sequence, `frames[${index}].sequence`);
if (sequence !== index) {
throw new GroundQualificationContractError("Кадры просмотра идут не по порядку.");
}
return {
sequence,
frameId: id(frame.frame_id, `frames[${index}].frame_id`),
sourcePointCount: integer(
frame.source_point_count,
`frames[${index}].source_point_count`,
1,
),
pointCount: integer(frame.point_count, `frames[${index}].point_count`, 1),
current: reviewFrameMetrics(frame.current, `frames[${index}].current`),
patchwork: reviewFrameMetrics(
frame.patchworkpp,
`frames[${index}].patchworkpp`,
),
groundIouDelta: number(
frame.ground_iou_delta,
`frames[${index}].ground_iou_delta`,
-1,
),
};
},
);
if (frames.length !== frameCount) {
throw new GroundQualificationContractError("Индекс просмотра неполный.");
}
frames.forEach((frame, index) => {
if (frame.pointCount > frame.sourcePointCount) {
throw new GroundQualificationContractError(
`frames[${index}] содержит больше preview-точек, чем source-точек.`,
);
}
});
const identitySha256 = text(source.identity_sha256, "identity_sha256", 64);
if (!SHA256.test(identitySha256)) {
throw new GroundQualificationContractError("Review identity должен содержать SHA-256.");
}
return {
runId: id(source.run_id, "run_id"),
identitySha256,
sourceId: text(source.source_id, "source_id"),
frameCount,
previewPoints: integer(source.preview_points, "preview_points", 1),
frames,
};
}
export function decodeGroundReviewFrame(payload: unknown): GroundReviewFrame {
const source = record(payload, "ground review frame");
if (
source.schema_version !== "missioncore.polygon-ground-review-frame/v1"
|| source.access !== "read-only"
) {
throw new GroundQualificationContractError("Неизвестная схема кадра просмотра.");
}
const compatible = {
...source,
schema_version: "missioncore.polygon-ground-failure-preview/v1",
};
const decoded = decodeGroundFailurePreview(compatible);
const intensity0To255 = array(
source.intensity_0_255,
"intensity_0_255",
50_000,
).map((value, index) => integer(value, `intensity_0_255[${index}]`));
if (
intensity0To255.length !== decoded.pointCount
|| intensity0To255.some((value) => value > 255)
) {
throw new GroundQualificationContractError(
"intensity_0_255 не совпадает с point_count.",
);
}
return { ...decoded, intensity0To255 };
}
async function requestJson(
url: string,
signal: AbortSignal | undefined,
@@ -391,3 +536,48 @@ export async function fetchGroundFailurePreview(
),
);
}
export async function fetchGroundReview(
runId: string,
{
signal,
fetcher = globalThis.fetch,
}: { signal?: AbortSignal; fetcher?: QualificationFetch } = {},
): Promise<GroundReview | null> {
if (!SAFE_ID.test(runId)) {
throw new GroundQualificationContractError("Недопустимый run_id.");
}
try {
return decodeGroundReview(
await requestJson(
`/api/v1/polygon/runs/${encodeURIComponent(runId)}/qualification/review`,
signal,
fetcher,
),
);
} catch (error) {
if (error instanceof GroundQualificationApiError && error.status === 404) return null;
throw error;
}
}
export async function fetchGroundReviewFrame(
runId: string,
frameId: string,
{
signal,
fetcher = globalThis.fetch,
}: { signal?: AbortSignal; fetcher?: QualificationFetch } = {},
): Promise<GroundReviewFrame> {
if (!SAFE_ID.test(runId) || !SAFE_ID.test(frameId)) {
throw new GroundQualificationContractError("Недопустимый идентификатор кадра.");
}
return decodeGroundReviewFrame(
await requestJson(
`/api/v1/polygon/runs/${encodeURIComponent(runId)}/qualification/review/frames/`
+ encodeURIComponent(frameId),
signal,
fetcher,
),
);
}