fix(lab): keep full route result indexable

This commit is contained in:
DCCONSTRUCTIONS
2026-08-29 13:54:40 +03:00
parent 9dec37a8a9
commit 0a0cde7b0e
5 changed files with 189 additions and 23 deletions
@@ -129,6 +129,10 @@ export interface VegetationFullRouteReview {
height: 600;
timelineStartSeconds: number;
timelineEndSeconds: number;
timelineArtifact: {
sha256: string;
byteLength: number;
};
frameSourceTimesNs: readonly number[];
decodeRepair: {
repairedFrameCount: 1;
@@ -742,22 +746,30 @@ function fullRouteReviewValue(value: unknown): VegetationFullRouteReview | null
if (timelineEndSeconds <= timelineStartSeconds) {
throw new VegetationShadowContractError("vegetation.route_full_review: timeline invalid.");
}
const frameSourceTimesNs = arrayValue(
row.frame_source_times_ns,
"vegetation.route_full_review.frame_source_times_ns",
).map((raw, index) => {
const time = integerValue(raw, `vegetation.route_full_review.frame_source_times_ns[${index}]`);
if (!Number.isSafeInteger(time)) {
throw new VegetationShadowContractError("vegetation.route_full_review: unsafe frame time.");
}
return time;
});
if (
frameSourceTimesNs.length !== 6830
|| frameSourceTimesNs.some((time, index) => index > 0 && time <= frameSourceTimesNs[index - 1]!)
) {
throw new VegetationShadowContractError("vegetation.route_full_review: frame timeline changed.");
const timeline = objectValue(row.timeline, "vegetation.route_full_review.timeline");
exact(
timeline.path,
"video/frame-source-times-ns.bin",
"vegetation.route_full_review.timeline.path",
);
exact(
timeline.encoding,
"uint64-le-nanoseconds",
"vegetation.route_full_review.timeline.encoding",
);
exact(timeline.frame_count, 6830, "vegetation.route_full_review.timeline.frame_count");
const timelineSha256 = textValue(
timeline.sha256,
"vegetation.route_full_review.timeline.sha256",
);
if (!SHA256.test(timelineSha256)) {
throw new VegetationShadowContractError("vegetation.route_full_review: timeline digest invalid.");
}
const timelineByteLength = integerValue(
timeline.byte_length,
"vegetation.route_full_review.timeline.byte_length",
);
exact(timelineByteLength, 6830 * 8, "vegetation.route_full_review.timeline.byte_length");
const decodeRepair = objectValue(
row.decode_repair,
"vegetation.route_full_review.decode_repair",
@@ -808,7 +820,8 @@ function fullRouteReviewValue(value: unknown): VegetationFullRouteReview | null
height: 600,
timelineStartSeconds,
timelineEndSeconds,
frameSourceTimesNs,
timelineArtifact: { sha256: timelineSha256, byteLength: timelineByteLength },
frameSourceTimesNs: [],
decodeRepair: {
repairedFrameCount: 1,
sequence: 6092,
@@ -935,11 +948,49 @@ export async function fetchVegetationShadowResult(
if (!response.ok) {
throw new VegetationShadowContractError(`Vegetation LAB недоступна: HTTP ${response.status}.`);
}
return parseResult(
const result = parseResult(
await response.json(),
resultId,
"/api/v1/laboratory/vegetation-shadow",
);
if (!result.routeFullReview) return result;
const timelineResponse = await fetcher(
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/route-timeline`,
{ method: "GET", headers: { Accept: "application/octet-stream" }, signal },
);
if (!timelineResponse.ok) {
throw new VegetationShadowContractError(
`Vegetation LAB timeline недоступна: HTTP ${timelineResponse.status}.`,
);
}
if (
timelineResponse.headers.get("etag")
!== `"${result.routeFullReview.timelineArtifact.sha256}"`
) {
throw new VegetationShadowContractError("Vegetation LAB timeline digest изменён.");
}
const timelinePayload = await timelineResponse.arrayBuffer();
if (timelinePayload.byteLength !== result.routeFullReview.timelineArtifact.byteLength) {
throw new VegetationShadowContractError("Vegetation LAB timeline size изменён.");
}
const timelineView = new DataView(timelinePayload);
const frameSourceTimesNs = Array.from({ length: result.routeFullReview.frameCount }, (_, index) => {
const value = Number(timelineView.getBigUint64(index * 8, true));
if (!Number.isSafeInteger(value)) {
throw new VegetationShadowContractError("Vegetation LAB timeline содержит unsafe time.");
}
return value;
});
if (
frameSourceTimesNs[0] !== Math.round(result.routeFullReview.timelineStartSeconds * 1_000_000_000)
|| frameSourceTimesNs.some((time, index) => index > 0 && time <= frameSourceTimesNs[index - 1]!)
) {
throw new VegetationShadowContractError("Vegetation LAB timeline нарушена.");
}
return {
...result,
routeFullReview: { ...result.routeFullReview, frameSourceTimesNs },
};
}
export async function fetchVegetationBenchmarkResult(