signalk-parquet
Version:
Vessel data Parquet file archive with automated value and geospatial triggers. History API compliant with cloud backups and queries.
187 lines • 6.46 kB
TypeScript
/**
* Aggregation Service
*
* Handles multi-tier data aggregation from raw data to progressively
* downsampled tiers for efficient long-term storage and querying.
*
* Tiers:
* - raw: Original data (~1s resolution)
* - 5s: 5-second aggregates
* - 60s: 1-minute aggregates
* - 1h: Hourly aggregates
*/
import { ServerAPI } from '@signalk/server-api';
import { AggregationTier } from '../utils/hive-path-builder';
import { PathRetentionRule } from '../utils/retention-rules';
export interface AggregationConfig {
outputDirectory: string;
filenamePrefix: string;
retentionDays: {
raw: number;
'5s': number;
'60s': number;
'1h': number;
};
pathRetentionOverrides?: PathRetentionRule[];
}
export declare const TIER_RETENTION_MULTIPLIER: Record<AggregationTier, number>;
/**
* Build the per-tier retention block from a raw-tier value. 0 stays
* 0 in every tier (= keep forever).
*/
export declare function buildPerTierRetention(rawDays: number): AggregationConfig['retentionDays'];
export interface AggregationProgress {
jobId: string;
status: 'running' | 'completed' | 'cancelled' | 'error';
tier: AggregationTier;
processed: number;
total: number;
currentFile?: string;
startTime: Date;
completedAt?: Date;
error?: string;
}
export interface BulkAggregationProgress {
jobId: string;
status: 'scanning' | 'running' | 'completed' | 'cancelled' | 'error';
phase: 'scan' | 'aggregation';
currentDate?: string;
datesProcessed: number;
datesTotal: number;
percent: number;
filesCreated: number;
recordsAggregated: number;
startTime: Date;
completedAt?: Date;
error?: string;
errors: string[];
}
export interface AggregationResult {
sourceTier: AggregationTier;
targetTier: AggregationTier;
filesProcessed: number;
recordsAggregated: number;
filesCreated: number;
duration: number;
errors: string[];
}
export declare class AggregationService {
private readonly config;
private readonly app;
private readonly hivePathBuilder;
private readonly retentionRules;
private currentJob;
private cancelRequested;
constructor(config: AggregationConfig, app: ServerAPI);
/**
* Run aggregation for a specific date.
* Optional pathFilter restricts which signalk paths are aggregated (used by
* targeted migrations like position re-aggregation).
*/
aggregateDate(date: Date, pathFilter?: (signalkPath: string) => boolean): Promise<AggregationResult[]>;
/**
* Aggregate from one tier to the next
*/
aggregateTier(sourceTier: AggregationTier, targetTier: AggregationTier, date: Date, pathFilter?: (signalkPath: string) => boolean): Promise<AggregationResult>;
/**
* Aggregate a group of files for a specific context/path
*/
private aggregateGroup;
/**
* Build the aggregation SQL query, branching on angular vs scalar paths
*/
private buildAggregationQuery;
/**
* Build angular-specific aggregation query using vector decomposition:
* ATAN2(AVG(SIN(value)), AVG(COS(value)))
*/
private buildAngularAggregationQuery;
/**
* Build position aggregation query.
*
* Selects one representative point per bucket by ranking candidates:
* 1. Fewest glitchy neighbors — a neighbor is glitchy when the implied
* speed between it and the candidate exceeds POSITION_MAX_SPEED_MPS.
* Both neighbors clean beats one; one beats none.
* 2. Timestamp at or after the bucket midpoint.
* 3. Closest to the bucket midpoint.
*
* A bucket always produces a row if any candidate exists; buckets made
* entirely of glitches still emit their least-bad candidate.
*/
private buildPositionAggregationQuery;
/**
* Group files by context and path
*/
private groupFilesByContextPath;
/**
* Parse a group key back to context and path
*/
private parseGroupKey;
/**
* Clean up old data based on retention settings.
*
* Per-tier retention defaults come from `config.retentionDays`. A
* value of 0 at any tier means "keep forever" — that tier is skipped
* entirely. Per-path overrides take precedence: when a file's path
* matches a retention rule, the rule's `days` (scaled by the tier
* multiplier) is used instead of the global tier default.
*
* Files outside the Hive layout (legacy flat storage) are not
* touched: there is no path/year/day to anchor a retention decision
* on.
*/
cleanupOldData(): Promise<{
deletedFiles: number;
failedFiles: number;
freedBytes: number;
}>;
/**
* Pick the retention to apply for one (path, tier) pair.
*
* - If a path-rule matches and `skipAggregation` is set: use rule.days
* for every tier (no multiplier). The path lives only in raw, so
* any stale rows in upper tiers should sweep at the same horizon
* as raw rather than linger 2/4/12x longer.
* - Else if a path-rule matches: use rule.days × tierMultiplier
* (rule.days is interpreted as raw-tier retention; upper tiers
* scale by the same convention as the global default).
* - Else: use the global tier default.
* - 0 (from either source) means infinite — return null so the caller
* skips deletion.
*/
private resolveEffectiveRetentionDays;
/**
* Get current job progress
*/
getProgress(): AggregationProgress | null;
/**
* Cancel current job
*/
cancel(): void;
/**
* Schedule daily aggregation (call from consolidation timer)
*/
runDailyAggregation(): Promise<AggregationResult[]>;
/**
* Discover all unique dates in tier=raw across all contexts and paths
*/
discoverRawDates(startDate?: Date, endDate?: Date): Promise<Date[]>;
/**
* Start bulk aggregation as a background job
*/
startBulkAggregation(startDate?: Date, endDate?: Date): string;
/**
* Run bulk aggregation across all dates in tier=raw
*/
private runBulkAggregation;
/**
* Get bulk aggregation job progress
*/
getBulkProgress(jobId: string): BulkAggregationProgress | null;
/**
* Cancel a bulk aggregation job
*/
cancelBulk(jobId: string): boolean;
}
//# sourceMappingURL=aggregation-service.d.ts.map