8 changed files with 348 additions and 48 deletions
@ -0,0 +1,35 @@ |
|||||||
|
import { getNonIntersectedSpans } from '../../src/utils/spans'; |
||||||
|
|
||||||
|
import 'jest'; |
||||||
|
|
||||||
|
describe('getNonIntersectedSpans', function() { |
||||||
|
|
||||||
|
let spanBorders = [3, 5, 6, 8, 10, 20]; |
||||||
|
|
||||||
|
it('functional test', function() {
|
||||||
|
expect(getNonIntersectedSpans(4, 11, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(5, 11, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(4, 10, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(5, 10, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(4, 20, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(4, 21, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}, {from: 20, to: 21}]); |
||||||
|
expect(getNonIntersectedSpans(2, 20, spanBorders)).toEqual([{from: 2, to: 3}, {from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(2, 21, spanBorders)).toEqual([{from: 2, to: 3}, {from: 5, to: 6}, {from: 8, to: 10}, {from: 20, to: 21}]); |
||||||
|
expect(getNonIntersectedSpans(3, 11, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(3, 20, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(3, 20, spanBorders)).toEqual([{from: 5, to: 6}, {from: 8, to: 10}]); |
||||||
|
expect(getNonIntersectedSpans(4, 7, [3, 5, 6, 8])).toEqual([{from: 5, to: 6}]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('empty borders list', function() { |
||||||
|
expect(getNonIntersectedSpans(4, 10, [])).toEqual([]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('all in span', function() { |
||||||
|
expect(getNonIntersectedSpans(4, 10, [1, 20])).toEqual([]); |
||||||
|
expect(getNonIntersectedSpans(4, 10, [1, 10])).toEqual([]); |
||||||
|
expect(getNonIntersectedSpans(4, 10, [4, 20])).toEqual([]); |
||||||
|
expect(getNonIntersectedSpans(4, 10, [4, 10])).toEqual([]); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
@ -0,0 +1,140 @@ |
|||||||
|
import { AnalyticUnitId } from './analytic_unit_model'; |
||||||
|
import { Collection, makeDBQ } from '../services/data_service'; |
||||||
|
|
||||||
|
import * as _ from 'lodash'; |
||||||
|
|
||||||
|
let db = makeDBQ(Collection.DETECTION_SPANS); |
||||||
|
|
||||||
|
export enum DetectionStatus { |
||||||
|
READY = 'READY', |
||||||
|
RUNNING = 'RUNNING', |
||||||
|
FAILED = 'FAILED' |
||||||
|
} |
||||||
|
|
||||||
|
export type DetectionId = string; |
||||||
|
|
||||||
|
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, |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
//TODO: move this code to span model
|
||||||
|
|
||||||
|
import * as _ from 'lodash'; |
||||||
|
|
||||||
|
export declare type Span = { |
||||||
|
from: number, |
||||||
|
to: number |
||||||
|
} |
||||||
|
|
||||||
|
export function getNonIntersectedSpans(from: number, to: number, spanBorders: number[]): Span[] { |
||||||
|
// spanBorders array must be sorted ascending
|
||||||
|
let isFromProcessed = false; |
||||||
|
let alreadyDetected = false; |
||||||
|
let startDetectionRange = null; |
||||||
|
let result: Span[] = []; |
||||||
|
|
||||||
|
for(var border of spanBorders) { |
||||||
|
if(!isFromProcessed && border >= from) { |
||||||
|
isFromProcessed = true; |
||||||
|
if(border === from) { |
||||||
|
if(alreadyDetected) { |
||||||
|
startDetectionRange = from; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if(!alreadyDetected) { |
||||||
|
startDetectionRange = from; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(border >= to) { |
||||||
|
if(!alreadyDetected) { |
||||||
|
result.push({ from: startDetectionRange, to }); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if(alreadyDetected) { //end of already detected region, start point for new detection
|
||||||
|
startDetectionRange = border; |
||||||
|
} else { //end of new detection region
|
||||||
|
if(startDetectionRange !== null) { |
||||||
|
result.push({ from: startDetectionRange, to: border}); |
||||||
|
} |
||||||
|
} |
||||||
|
alreadyDetected = !alreadyDetected; |
||||||
|
} |
||||||
|
|
||||||
|
if(border < to) { |
||||||
|
result.push({ from: startDetectionRange, to }); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
Loading…
Reference in new issue