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.
275 lines
7.5 KiB
275 lines
7.5 KiB
import { AnalyticUnitId } from './analytic_units'; |
|
import * as AnalyticUnit from '../models/analytic_units'; |
|
import * as AnalyticUnitCache from '../models/analytic_unit_cache_model'; |
|
import { Collection, makeDBQ } from '../services/data_service'; |
|
|
|
import * as _ from 'lodash'; |
|
|
|
let db = makeDBQ(Collection.SEGMENTS); |
|
|
|
|
|
export type SegmentId = string; |
|
|
|
export class Segment { |
|
constructor( |
|
public analyticUnitId: AnalyticUnitId, |
|
public from: number, |
|
public to: number, |
|
public labeled: boolean = false, |
|
public deleted: boolean = false, |
|
public id?: SegmentId, |
|
public message?: string |
|
) { |
|
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'); |
|
} |
|
} |
|
|
|
public toObject() { |
|
return { |
|
_id: this.id, |
|
analyticUnitId: this.analyticUnitId, |
|
from: this.from, |
|
to: this.to, |
|
labeled: this.labeled, |
|
deleted: this.deleted, |
|
message: this.message |
|
}; |
|
} |
|
|
|
public toTemplate(): any { |
|
return { |
|
...this.toObject(), |
|
_id: undefined, |
|
analyticUnitId: undefined |
|
}; |
|
} |
|
|
|
static fromObject(obj: any): Segment { |
|
if(obj === undefined) { |
|
throw new Error('obj is undefined'); |
|
} |
|
return new Segment( |
|
obj.analyticUnitId, |
|
+obj.from, +obj.to, |
|
obj.labeled, obj.deleted, |
|
obj._id, obj.message |
|
); |
|
} |
|
|
|
public equals(obj: Segment) : boolean { |
|
return this.analyticUnitId === obj.analyticUnitId && |
|
this.from === obj.from && |
|
this.to === obj.to && |
|
this.labeled === this.labeled && |
|
this.deleted === this.deleted; |
|
} |
|
} |
|
|
|
export type FindManyQuery = { |
|
$or?: any, |
|
from?: { $gte?: number, $lte?: number }, |
|
to?: { $gte?: number, $lte?: number }, |
|
labeled?: boolean, |
|
deleted?: boolean |
|
} |
|
|
|
export async function findOne(segmentId: SegmentId): Promise<Segment> { |
|
return db.findOne({ _id: segmentId }); |
|
} |
|
|
|
export async function findMany(id: AnalyticUnitId, query: FindManyQuery): Promise<Segment[]> { |
|
var dbQuery: any = { analyticUnitId: id }; |
|
if(query.labeled !== undefined) { |
|
dbQuery.labeled = query.labeled; |
|
} |
|
if(query.deleted !== undefined) { |
|
dbQuery.deleted = query.deleted; |
|
} |
|
if(query.from !== undefined) { |
|
dbQuery.from = query.from; |
|
} |
|
if(query.to !== undefined) { |
|
dbQuery.to = query.to; |
|
} |
|
if(query.$or !== undefined) { |
|
dbQuery.$or = query.$or; |
|
} |
|
let segs = await db.findMany(dbQuery); |
|
if(segs === null) { |
|
return []; |
|
} |
|
return segs.map(Segment.fromObject); |
|
} |
|
|
|
export async function findByAnalyticUnitIds(analyticUnitIds: AnalyticUnitId[]): Promise<any[]> { |
|
const segments = await db.findMany({ analyticUnitId: { $in: analyticUnitIds } }); |
|
|
|
if(segments === null) { |
|
return []; |
|
} |
|
return segments.map(Segment.fromObject); |
|
} |
|
|
|
/** |
|
* If `from` and `to` are defined: @returns segments intersected with `[from; to]` |
|
* If `to` is `undefined`: @returns segments intersected with `[-inf; from]` |
|
* If `from` is `undefined`: @returns segments intersected with `[to: +inf]` |
|
* If `from` and `to` are undefined: @returns all segments |
|
*/ |
|
export async function findIntersectedSegments( |
|
analyticUnitId: AnalyticUnit.AnalyticUnitId, |
|
from?: number, |
|
to?: number |
|
): Promise<Segment[]> { |
|
let query: FindManyQuery = {}; |
|
if(from !== undefined) { |
|
query.to = { $gte: from }; |
|
} |
|
if(to !== undefined) { |
|
query.from = { $lte: to }; |
|
} |
|
return findMany(analyticUnitId, query); |
|
} |
|
|
|
|
|
// TODO: rewrite all this horrible function |
|
// TODO: use utils.segments.IntegerSegmentsSet |
|
/** |
|
* Merges an array of segments with ones existing in the DB |
|
* Inserts resulting segments into DB |
|
* @param segments segments to be inserted |
|
* @returns IDs of added and removed segments |
|
*/ |
|
export async function mergeAndInsertSegments(segments: Segment[]): Promise<{ |
|
addedIds: SegmentId[], |
|
removedIds: SegmentId[] |
|
}> { |
|
if(_.isEmpty(segments)) { |
|
return { addedIds: [], removedIds: [] }; |
|
} |
|
const analyticUnitId: AnalyticUnitId = segments[0].analyticUnitId; |
|
const unit = await AnalyticUnit.findById(analyticUnitId); |
|
if(unit === null) { |
|
throw new Error('Can`t find analytic unit ' + analyticUnitId); |
|
} |
|
const cache = await AnalyticUnitCache.findById(analyticUnitId); |
|
|
|
const detector = unit.detectorType; |
|
|
|
let segmentIdsToRemove: SegmentId[] = []; |
|
let segmentsToInsert: Segment[] = []; |
|
|
|
for(let segment of segments) { |
|
if(await isIntersectedWithExistingLabeled(segment)) { |
|
continue; |
|
} |
|
|
|
if(!segment.deleted && !segment.labeled) { |
|
if(await isIntersectedWithExistingDeleted(segment)) { |
|
continue; |
|
} |
|
} |
|
|
|
let intersectedSegments: Segment[] = []; |
|
if(detector === AnalyticUnit.DetectorType.PATTERN) { |
|
intersectedSegments = await findMany(analyticUnitId, { |
|
to: { $gte: segment.from }, |
|
from: { $lte: segment.to }, |
|
labeled: segment.labeled, |
|
deleted: segment.deleted |
|
}); |
|
} else { |
|
let intersectionRangeExtension = 0; |
|
if(cache !== null) { |
|
const timeStep = cache.timeStep; |
|
if(timeStep !== undefined) { |
|
intersectionRangeExtension = timeStep; |
|
} |
|
} |
|
intersectedSegments = await findMany(analyticUnitId, { |
|
to: { $gte: segment.from - intersectionRangeExtension }, |
|
from: { $lte: segment.to + intersectionRangeExtension }, |
|
labeled: segment.labeled, |
|
deleted: segment.deleted |
|
}); |
|
} |
|
|
|
if(intersectedSegments.length > 0) { |
|
let intersectedIds = intersectedSegments.map(s => s.id); |
|
let minFromSegment = _.minBy(intersectedSegments.concat(segment), s => s.from); |
|
let maxToSegment = _.maxBy(intersectedSegments.concat(segment), s => s.to); |
|
|
|
if(minFromSegment === undefined) { |
|
throw new Error('minFromSegment is undefined'); |
|
} |
|
|
|
if(maxToSegment === undefined) { |
|
throw new Error('maxToSegment is undefined'); |
|
} |
|
|
|
let from = minFromSegment.from; |
|
let to = maxToSegment.to; |
|
let newSegment = Segment.fromObject(segment.toObject()); |
|
newSegment.from = from; |
|
newSegment.to = to; |
|
segmentIdsToRemove = segmentIdsToRemove.concat(_.compact(intersectedIds)); |
|
segmentsToInsert.push(newSegment); |
|
} else { |
|
segmentsToInsert.push(segment); |
|
} |
|
} |
|
|
|
await db.removeMany(segmentIdsToRemove); |
|
const addedIds = await db.insertMany(segmentsToInsert.map(s => s.toObject())); |
|
return { |
|
addedIds, |
|
removedIds: segmentIdsToRemove |
|
}; |
|
} |
|
|
|
export async function insertMany(segments: any[]): Promise<SegmentId[]> { |
|
return db.insertMany(segments); |
|
} |
|
|
|
export async function setSegmentsDeleted(ids: SegmentId[]) { |
|
return db.updateMany(ids, { deleted: true, labeled: false }); |
|
} |
|
|
|
export function removeSegments(idsToRemove: SegmentId[]) { |
|
return db.removeMany(idsToRemove); |
|
} |
|
|
|
async function isIntersectedWithExistingLabeled(segment: Segment): Promise<boolean> { |
|
const intersected = await findMany(segment.analyticUnitId, { |
|
labeled: true, |
|
deleted: false, |
|
from: { $lte: segment.to }, |
|
to: { $gte: segment.from } |
|
}); |
|
|
|
return intersected.length > 0; |
|
} |
|
|
|
async function isIntersectedWithExistingDeleted(segment: Segment): Promise<boolean> { |
|
const intersected = await findMany(segment.analyticUnitId, { |
|
labeled: false, |
|
deleted: true, |
|
from: { $lte: segment.to }, |
|
to: { $gte: segment.from } |
|
}); |
|
|
|
return intersected.length > 0; |
|
}
|
|
|