You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
4.2 KiB
158 lines
4.2 KiB
import { AnalyticUnitId } from './analytic_units'; |
|
import { Collection, makeDBQ } from '../services/data_service'; |
|
|
|
import * as _ from 'lodash'; |
|
import { getNonIntersectedSpans } from '../utils/spans'; |
|
|
|
let db = makeDBQ(Collection.DETECTION_SPANS); |
|
|
|
export enum DetectionStatus { |
|
READY = 'READY', |
|
RUNNING = 'RUNNING', |
|
FAILED = 'FAILED' |
|
} |
|
|
|
export type DetectionId = string; |
|
|
|
/** |
|
* Detection-span represents the state of dataset segment: |
|
* - READY: detection is done |
|
* - RUNNING: detection is running |
|
* - FAILED: detection failed |
|
*/ |
|
export class DetectionSpan { |
|
constructor( |
|
public analyticUnitId: AnalyticUnitId, |
|
public from: number, |
|
public to: number, |
|
public status: DetectionStatus, |
|
public id?: DetectionId, |
|
) { |
|
if(analyticUnitId === undefined) { |
|
throw new Error('AnalyticUnitId is undefined'); |
|
} |
|
if(from === undefined) { |
|
throw new Error('from is undefined'); |
|
} |
|
if(isNaN(from)) { |
|
throw new Error('from is NaN'); |
|
} |
|
if(to === undefined) { |
|
throw new Error('to is undefined'); |
|
} |
|
if(isNaN(to)) { |
|
throw new Error('to is NaN'); |
|
} |
|
if(status === undefined) { |
|
throw new Error('status is undefined'); |
|
} |
|
} |
|
|
|
public toObject() { |
|
return { |
|
_id: this.id, |
|
analyticUnitId: this.analyticUnitId, |
|
from: this.from, |
|
to: this.to, |
|
status: this.status |
|
}; |
|
} |
|
|
|
static fromObject(obj: any): DetectionSpan { |
|
if(obj === undefined) { |
|
throw new Error('obj is undefined'); |
|
} |
|
return new DetectionSpan( |
|
obj.analyticUnitId, |
|
+obj.from, +obj.to, |
|
obj.status, |
|
obj._id |
|
); |
|
} |
|
} |
|
|
|
export type FindManyQuery = { |
|
status?: DetectionStatus, |
|
// TODO: |
|
// from?: { $gte?: number, $lte?: number } |
|
// to?: { $gte?: number, $lte?: number } |
|
timeFromLTE?: number, |
|
timeToGTE?: number, |
|
timeFromGTE?: number, |
|
timeToLTE?: number, |
|
} |
|
|
|
export async function findMany(id: AnalyticUnitId, query?: FindManyQuery): Promise<DetectionSpan[]> { |
|
let dbQuery: any = { analyticUnitId: id }; |
|
if(query.status !== undefined) { |
|
dbQuery.status = query.status; |
|
} |
|
if(query.timeFromLTE !== undefined) { |
|
dbQuery.from = { $lte: query.timeFromLTE }; |
|
} |
|
if(query.timeToGTE !== undefined) { |
|
dbQuery.to = { $gte: query.timeToGTE }; |
|
} |
|
if(query.timeFromGTE !== undefined) { |
|
dbQuery.from = { $gte: query.timeFromGTE }; |
|
} |
|
if(query.timeToLTE !== undefined) { |
|
dbQuery.to = { $lte: query.timeToLTE }; |
|
} |
|
|
|
const spans = await db.findMany(dbQuery); |
|
if(spans === null) { |
|
return []; |
|
} |
|
return spans.map(DetectionSpan.fromObject); |
|
} |
|
|
|
export async function getIntersectedSpans( |
|
analyticUnitId: AnalyticUnitId, |
|
from: number, |
|
to: number, |
|
status?: DetectionStatus |
|
): Promise<DetectionSpan[]> { |
|
return findMany(analyticUnitId, { status, timeFromLTE: to, timeToGTE: from }); |
|
} |
|
|
|
export async function insertSpan(span: DetectionSpan) { |
|
let spanToInsert = span.toObject(); |
|
|
|
const intersections = await getIntersectedSpans(span.analyticUnitId, span.from, span.to, span.status); |
|
if(!_.isEmpty(intersections) && span.status === DetectionStatus.READY) { |
|
let minFrom: number = _.minBy(intersections, 'from').from; |
|
minFrom = Math.min(span.from, minFrom); |
|
|
|
let maxTo: number = _.maxBy(intersections, 'to').to; |
|
maxTo = Math.max(span.to, maxTo); |
|
|
|
const spansInside = await findMany(span.analyticUnitId, { timeFromGTE: minFrom, timeToLTE: maxTo }); |
|
const toRemove = _.concat(intersections.map(span => span.id), spansInside.map(span => span.id)); |
|
|
|
await db.removeMany(toRemove); |
|
|
|
spanToInsert = new DetectionSpan(span.analyticUnitId, minFrom, maxTo, span.status).toObject(); |
|
} |
|
return db.insertOne(spanToInsert); |
|
} |
|
|
|
/** |
|
* Sorts spans by `from` field and @returns an array of their borders |
|
*/ |
|
// TODO: remove after getNonIntersectedSpans refactoring |
|
export function getSpanBorders(spans: DetectionSpan[]): number[] { |
|
let spanBorders: number[] = []; |
|
|
|
_.sortBy(spans.map(span => span.toObject()), 'from') |
|
.forEach(span => { |
|
spanBorders.push(span.from); |
|
spanBorders.push(span.to); |
|
}); |
|
|
|
return spanBorders; |
|
} |
|
|
|
export function clearSpans(analyticUnitId: AnalyticUnitId) { |
|
return db.removeMany({ analyticUnitId }); |
|
}
|
|
|