190 lines
3.4 KiB
TypeScript
190 lines
3.4 KiB
TypeScript
import { LocateMode, UtilityType } from '@prisma/client';
|
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
import { Type } from 'class-transformer';
|
|
import {
|
|
ArrayMaxSize,
|
|
ArrayMinSize,
|
|
IsArray,
|
|
IsDateString,
|
|
IsEnum,
|
|
IsIn,
|
|
IsInt,
|
|
IsNumber,
|
|
IsOptional,
|
|
IsString,
|
|
IsUUID,
|
|
Max,
|
|
MaxLength,
|
|
Min,
|
|
ValidateNested,
|
|
} from 'class-validator';
|
|
|
|
export const APP_FIX_TYPES = [
|
|
'AUTONOMOUS',
|
|
'DGPS',
|
|
'FLOAT',
|
|
'FIXED',
|
|
'NO_FIX',
|
|
] as const;
|
|
export type AppFixType = (typeof APP_FIX_TYPES)[number];
|
|
|
|
// Frozen app MQTT wire profile v1 from meta/contracts/telemetry-schema.md. This is deliberately
|
|
// separate from MqttPointDto: the app wire uses the normative fix tokens FLOAT/FIXED/NO_FIX,
|
|
// while the legacy device path uses Prisma's FLOAT_RTK/FIXED_RTK/NONE storage tokens.
|
|
export class AppLogPointDto {
|
|
@ApiProperty({
|
|
format: 'uuid',
|
|
description: 'Client-generated UUIDv7 idempotency key.',
|
|
})
|
|
@IsUUID()
|
|
pointId: string;
|
|
|
|
@ApiProperty({ format: 'date-time' })
|
|
@IsDateString()
|
|
createdAt: string;
|
|
|
|
@IsIn(['APP'])
|
|
origin: 'APP';
|
|
|
|
@IsIn(['APP_MQTT'])
|
|
uploadPath: 'APP_MQTT';
|
|
|
|
@IsNumber()
|
|
@Min(-90)
|
|
@Max(90)
|
|
lat: number;
|
|
|
|
@IsNumber()
|
|
@Min(-180)
|
|
@Max(180)
|
|
lng: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
alt?: number;
|
|
|
|
@IsDateString()
|
|
ts: string;
|
|
|
|
@IsOptional()
|
|
@IsIn(APP_FIX_TYPES)
|
|
fix?: AppFixType;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(0)
|
|
hAcc?: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(0)
|
|
vAcc?: number;
|
|
|
|
@IsOptional()
|
|
@IsInt()
|
|
@Min(0)
|
|
sats?: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(0)
|
|
hdop?: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(0)
|
|
depth?: number;
|
|
|
|
@IsOptional()
|
|
@IsInt()
|
|
@Min(0)
|
|
freqHz?: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(0)
|
|
currentMa?: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
signalDb?: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
gainDb?: number;
|
|
|
|
@IsOptional()
|
|
@IsEnum(LocateMode)
|
|
mode?: LocateMode;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
phaseDeg?: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(0)
|
|
@Max(360)
|
|
compassDeg?: number;
|
|
|
|
@IsOptional()
|
|
@IsNumber()
|
|
@Min(0)
|
|
@Max(100)
|
|
distortionPct?: number;
|
|
|
|
@IsOptional()
|
|
@IsEnum(UtilityType)
|
|
utility?: UtilityType;
|
|
|
|
@IsOptional()
|
|
@IsInt()
|
|
seq?: number;
|
|
|
|
@IsIn(['IN_SPEC', 'OUT_OF_SPEC', 'MANUAL', 'PPK_CORRECTED', 'WAIVED', 'NONCOMPLIANT'])
|
|
qualityFlag: 'IN_SPEC' | 'OUT_OF_SPEC' | 'MANUAL' | 'PPK_CORRECTED' | 'WAIVED' | 'NONCOMPLIANT';
|
|
}
|
|
|
|
export class AppLogPointsMessageDto {
|
|
@ApiPropertyOptional({ description: 'MQTT app-log wire profile version.' })
|
|
@IsString()
|
|
@MaxLength(16)
|
|
schemaVersion: string;
|
|
|
|
@ApiPropertyOptional({
|
|
description: 'Job id; exactly one of jobId or ticket is required.',
|
|
})
|
|
@IsOptional()
|
|
@IsString()
|
|
jobId?: string;
|
|
|
|
@ApiPropertyOptional({
|
|
description: 'Ticket number; exactly one of jobId or ticket is required.',
|
|
})
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(64)
|
|
ticket?: string;
|
|
|
|
@ApiProperty({ type: [AppLogPointDto] })
|
|
@IsArray()
|
|
@ArrayMinSize(1)
|
|
@ArrayMaxSize(500)
|
|
@ValidateNested({ each: true })
|
|
@Type(() => AppLogPointDto)
|
|
points: AppLogPointDto[];
|
|
}
|
|
|
|
export type AppLogAckOutcome = 'ACCEPTED' | 'DUPLICATE' | 'REJECTED';
|
|
|
|
export interface AppLogAckResult {
|
|
pointId: string;
|
|
outcome: AppLogAckOutcome;
|
|
reasonCode?: string;
|
|
}
|
|
|
|
export interface AppLogAck {
|
|
schemaVersion: string;
|
|
results: AppLogAckResult[];
|
|
}
|