fix(polygon): separate dataset sources from runs

This commit is contained in:
DCCONSTRUCTIONS
2026-07-25 18:40:56 +03:00
parent 2e5c4ba1c5
commit e97573e221
13 changed files with 1036 additions and 658 deletions
@@ -73,6 +73,9 @@ export interface GroundReviewFrameMetrics {
export interface GroundReviewFrameSummary {
sequence: number;
frameId: string;
datasetSequenceId: string;
datasetFrameNumber: number;
sensorTimestampNs: string;
sourcePointCount: number;
pointCount: number;
current: GroundReviewFrameMetrics;
@@ -108,6 +111,8 @@ type QualificationFetch = (
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const SHA256 = /^[a-f0-9]{64}$/;
const GOOSE_FRAME_ID =
/^([A-Za-z0-9][A-Za-z0-9_-]{0,95})__([0-9]{4})_([0-9]{16,20})$/;
function record(value: unknown, field: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -373,7 +378,7 @@ function reviewFrameMetrics(value: unknown, field: string): GroundReviewFrameMet
export function decodeGroundReview(payload: unknown): GroundReview {
const source = record(payload, "ground review");
if (
source.schema_version !== "missioncore.polygon-ground-review/v1"
source.schema_version !== "missioncore.polygon-ground-review/v2"
|| source.access !== "read-only"
) {
throw new GroundQualificationContractError("Неизвестная схема покадрового просмотра.");
@@ -386,9 +391,36 @@ export function decodeGroundReview(payload: unknown): GroundReview {
if (sequence !== index) {
throw new GroundQualificationContractError("Кадры просмотра идут не по порядку.");
}
const frameId = id(frame.frame_id, `frames[${index}].frame_id`);
const frameIdentity = parseGooseFrameId(frameId);
const datasetSequenceId = id(
frame.dataset_sequence_id,
`frames[${index}].dataset_sequence_id`,
);
const datasetFrameNumber = integer(
frame.dataset_frame_number,
`frames[${index}].dataset_frame_number`,
);
const sensorTimestampNs = text(
frame.sensor_timestamp_ns,
`frames[${index}].sensor_timestamp_ns`,
20,
);
if (
datasetSequenceId !== frameIdentity.datasetSequenceId
|| datasetFrameNumber !== frameIdentity.datasetFrameNumber
|| sensorTimestampNs !== frameIdentity.sensorTimestampNs
) {
throw new GroundQualificationContractError(
`frames[${index}] противоречит идентичности GOOSE-кадра.`,
);
}
return {
sequence,
frameId: id(frame.frame_id, `frames[${index}].frame_id`),
frameId,
datasetSequenceId,
datasetFrameNumber,
sensorTimestampNs,
sourcePointCount: integer(
frame.source_point_count,
`frames[${index}].source_point_count`,
@@ -432,6 +464,26 @@ export function decodeGroundReview(payload: unknown): GroundReview {
};
}
export interface GooseFrameIdentity {
datasetSequenceId: string;
datasetFrameNumber: number;
sensorTimestampNs: string;
}
export function parseGooseFrameId(frameId: string): GooseFrameIdentity {
const match = GOOSE_FRAME_ID.exec(frameId);
if (!match) {
throw new GroundQualificationContractError(
"frame_id не содержит GOOSE sequence/frame/timestamp.",
);
}
return {
datasetSequenceId: match[1],
datasetFrameNumber: Number(match[2]),
sensorTimestampNs: match[3],
};
}
export function decodeGroundReviewFrame(payload: unknown): GroundReviewFrame {
const source = record(payload, "ground review frame");
if (