diff --git a/src/adonisjs/public/editor/annotate/js/metrics.js b/src/adonisjs/public/editor/annotate/js/metrics.js
index 6c7e69bd..a8b430bd 100644
--- a/src/adonisjs/public/editor/annotate/js/metrics.js
+++ b/src/adonisjs/public/editor/annotate/js/metrics.js
@@ -1,10 +1,10 @@
class AnnotationMetrics {
- _selfOrderCount(categoriesOrder) {
+ _selfOrderCount(categoriesOrder, present) {
// sort by text position (second element)
const sortedL = categoriesOrder.sort((a, b) => a[1] - b[1])
// group by category (first element)
- // group = [category, position, count]
+ // group = [category, position of the first group element, count]
const grouped = []
for (let cat = 1; cat <= 8; cat++) {
let prev = -1
@@ -12,15 +12,17 @@ class AnnotationMetrics {
let catG = null
for (let i = 0; i < sortedL.length; i++) {
if (sortedL[i][0] === cat) {
+ // if any element in the previous position is not in the same category
if (prev == -1 || (sortedL[prev][0] !== cat &&
(prevG == -1 || sortedL[prev][1] > sortedL[prevG][1]))) {
- catG = [cat, sortedL[i][1], 1]
+ catG = [cat, sortedL[i][1], 1] // new category grouping
grouped.push(catG)
} else {
catG[2]++
}
prevG = i
}
+ // last distinct position in the sequence
if (i+1 == sortedL.length || sortedL[i+1][1] !== sortedL[i][1])
prev = i
}
@@ -42,17 +44,22 @@ class AnnotationMetrics {
subs++
sortedG[prev][2] += sortedG[i][2]
sortedG.splice(i, 1)
- // if (sortedG[prev][2] >= sortedG[i][2]) {
- // sortedG[prev][2] += sortedG[i][2]
- // sortedG.splice(i, 1)
- // } else {
- // sortedG[i][2] += sortedG[prev][2]
- // sortedG.splice(prev, 1)
- // }
}
}
}
}
+
+ if (present != null) {
+ present('\n\n=== Self Order Count ===')
+ present('--- sorted by position')
+ present(sortedL)
+ present('--- grouped by category')
+ present(JSON.parse(JSON.stringify(grouped)))
+ present('--- group sorted by position')
+ present(JSON.parse(JSON.stringify(sortedG)))
+ present('--- final group after ordering')
+ present(sortedG)
+ }
return {
groups: groupsText,
@@ -65,7 +72,7 @@ class AnnotationMetrics {
* Category clustering calculator for free recall
* https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3665324/
*/
- _clusteringFreeRecall (categoriesOrder) {
+ _clusteringFreeRecall (categoriesOrder, present) {
const n = categoriesOrder.length // number of recalled items
// sort by text position (second element)
@@ -110,6 +117,24 @@ class AnnotationMetrics {
const arc = (r - er) / (max - er) // adjusted ratio of clustering
+ if (present != null) {
+ present('\n\n=== Clustering Free Recall ===')
+ present(JSON.stringify(categoriesOrder))
+ present('--- n = ' + n)
+ present('--- sorted by position')
+ present(JSON.stringify(sortedL))
+ present('--- c = ' + c)
+ present('--- ni')
+ present(nc)
+ present('--- r = ' + r)
+ present('--- max = ' + max)
+ present('--- E(r) = ' + Math.round(er * 100) / 100)
+ present('--- RR = ' + Math.round(rr * 100) / 100)
+ present('--- MRR = ' + Math.round(mrr * 100) / 100)
+ present('--- DS = ' + Math.round(ds * 100) / 100)
+ present('--- ARC = ' + Math.round(arc * 100) / 100)
+ }
+
return arc
}
}
diff --git a/src/adonisjs/public/editor/annotate/metrics/index.html b/src/adonisjs/public/editor/annotate/metrics/index.html
index dfc93750..e46a4720 100644
--- a/src/adonisjs/public/editor/annotate/metrics/index.html
+++ b/src/adonisjs/public/editor/annotate/metrics/index.html
@@ -1,8 +1,11 @@
+
+
+
-
diff --git a/src/adonisjs/public/editor/annotate/metrics/metrics.js b/src/adonisjs/public/editor/annotate/metrics/metrics.js
deleted file mode 100644
index 71f1e5b7..00000000
--- a/src/adonisjs/public/editor/annotate/metrics/metrics.js
+++ /dev/null
@@ -1,187 +0,0 @@
-function sortSubstitutionCount(numbersList) {
- const sortedL = numbersList.slice().sort((a, b) => a - b)
- present(numbersList)
- present(sortedL)
- let subs = 0
- for (let i = 0; i < numbersList.length; i++) {
- if (numbersList[i] !== sortedL[i]) {
- subs++
- }
- }
- return Math.ceil(subs/2)
-}
-
-// present(sortSubstitutionCount([1, 1, 1, 2, 3, 3, 4]))
-// present(sortSubstitutionCount([1, 1, 1, 3, 2, 3, 4]))
-// present(sortSubstitutionCount([1, 1, 1, 3, 3, 2, 4]))
-// present(sortSubstitutionCount([1, 1, 1, 4, 3, 2, 3]))
-// present(sortSubstitutionCount([1, 4, 1, 3, 3, 2, 1]))
-// present(sortSubstitutionCount([4, 3, 3, 2, 1, 1, 1]))
-
-function selfOrderCount(categoriesOrder) {
- // sort by text position (second element)
- const sortedL = categoriesOrder.sort((a, b) => a[1] - b[1])
- present('Sorted by position')
- present(sortedL)
-
- // group by category (first element)
- // group = [category, position, count]
- const grouped = []
- for (let cat = 1; cat <= 8; cat++) {
- let prev = -1
- let prevG = -1
- let catG = null
- for (let i = 0; i < sortedL.length; i++) {
- if (sortedL[i][0] === cat) {
- if (prev == -1 || (sortedL[prev][0] !== cat &&
- (prevG == -1 || sortedL[prev][1] > sortedL[prevG][1]))) {
- catG = [cat, sortedL[i][1], 1]
- grouped.push(catG)
- } else {
- catG[2]++
- }
- prevG = i
- }
- if (i+1 == sortedL.length || sortedL[i+1][1] !== sortedL[i][1])
- prev = i
- }
- }
- present('Grouped by category')
- present(JSON.parse(JSON.stringify(grouped)))
-
- // sort groups by position (second element)
- const sortedG = grouped.sort((a, b) => a[1] - b[1])
- present('Group sorted by position')
- present(JSON.parse(JSON.stringify(sortedG)))
-
- // count order change to group together categories
- let subs = 0
- for (let cat = 1; cat <= 8; cat++) {
- let prev = -1
- for (let i = 0; i < sortedG.length; i++) {
- if (sortedG[i][0] === cat) {
- if (prev === -1)
- prev = i
- else {
- subs++
- if (sortedG[prev][2] >= sortedG[i][2]) {
- sortedG[prev][2] += sortedG[i][2]
- sortedG.splice(i, 1)
- } else {
- sortedG[i][2] += sortedG[prev][2]
- sortedG.splice(prev, 1)
- }
- }
- }
- }
- }
- present('Final group after ordering')
- present(sortedG)
-
- return subs
-}
-
-// present(selfOrderCount(
-// [[1, 10], [2, 20], [1, 20], [2, 30]]))
-// present(selfOrderCount(
-// [[1, 10], [2, 20], [1, 25], [2, 30]]))
-// present(selfOrderCount(
-// [[1, 10], [2, 20], [1, 20], [3, 20], [1, 30], [2, 30], [3, 30]]))
-// present(selfOrderCount(
-// [[2, 71], [2, 96], [3, 98], [2, 100], [5, 120], [5, 130], [5, 135], [3, 140], [5, 180]]))
-// present(selfOrderCount(
-// [[5, 135], [2, 100], [2, 71], [2, 96], [5, 130], [3, 98], [5, 180], [5, 120], [3, 140]]))
-// present(selfOrderCount(
-// [[2, 71], [2, 96], [3, 98], [2, 98], [5, 98], [5, 130], [5, 135], [3, 140], [5, 180]]))
-
-/*
- * Category clustering calculator for free recall
- * https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3665324/
- * tested here and traferred to /editor/annotate/js/metrics.js
-*/
-function clusteringFreeRecall(categoriesOrder, console) {
-
- const n = categoriesOrder.length // number of recalled items
-
- // sort by text position (second element)
- const sortedL = categoriesOrder.sort((a, b) => a[1] - b[1])
-
- const nc = {} // number of recalled items in each recalled category
- let r = 0 // number of category repetition
- for (let i = 0; i < sortedL.length; i++) {
- const cat = sortedL[i][0]
- if (!nc[cat])
- nc[cat] = 1
- else
- nc[cat]++
- let nextPos = i + 1
- while (nextPos < sortedL.length && sortedL[nextPos][1] === sortedL[i][1])
- nextPos++
- if (nextPos < sortedL.length) {
- let sp = nextPos
- while (sp < sortedL.length && sortedL[sp][1] === sortedL[nextPos][1]) {
- if (cat == sortedL[sp][0]) {
- r++
- break
- }
- sp++
- }
- }
- }
-
- const c = Object.keys(nc).length // number of recalled categories
- const max = n - c // maximum possible number of category repetitions
-
- let er = 0 // expected number of category repetitions
- for (const cat in nc)
- er += nc[cat] * nc[cat]
- er = er / n - 1
-
- const rr = r / (n - 1) // ratio of repetition
-
- const mrr = r / max // modified ratio of repetition
-
- const ds = r - er // deviation score
-
- const arc = (r - er) / (max - er) // adjusted ratio of clustering
-
- if (console) {
- present('\n\n=== Clustering Free Recall ===')
- present(JSON.stringify(categoriesOrder))
- present('--- n = ' + n)
- present('--- sorted by position')
- present(JSON.stringify(sortedL))
- present('--- c = ' + c)
- present('--- ni')
- present(nc)
- present('--- r = ' + r)
- present('--- max = ' + max)
- present('--- E(r) = ' + Math.round(er * 100) / 100)
- present('--- RR = ' + Math.round(rr * 100) / 100)
- present('--- MRR = ' + Math.round(mrr * 100) / 100)
- present('--- DS = ' + Math.round(ds * 100) / 100)
- present('--- ARC = ' + Math.round(arc * 100) / 100)
- }
-
- return arc
-}
-
-function present (output) {
- document.querySelector('#console').value += output + '\n'
-}
-
-present(clusteringFreeRecall(
- [[2, 1], [4, 2], [4, 3], [3, 4], [2, 5], [3, 6], [1, 7], [4, 8], [4, 9]], true
-))
-present(clusteringFreeRecall(
- [[3, 1], [4, 2], [4, 3], [3, 4], [1, 5], [1, 6], [3, 7], [1, 8], [1, 9], [2, 10], [2, 11], [2, 12], [4, 13], [4, 14], [3, 15]], true
-))
-present(clusteringFreeRecall(
- [[2, 1], [2, 2], [3, 3], [1, 4], [1, 5], [1, 6], [1, 7], [2, 8], [3, 9], [3, 10], [2, 11], [1, 12], [4, 13], [4, 14], [4, 15], [4, 16], [2, 17], [2, 18], [3, 19], [1, 20]], true
-))
-present(clusteringFreeRecall(
- [[5, 135], [2, 100], [2, 71], [2, 96], [5, 130], [3, 98], [5, 180], [5, 120], [3, 140]], true))
-present(clusteringFreeRecall(
- [[2, 71], [2, 96], [3, 98], [2, 98], [5, 98], [5, 130], [5, 135], [3, 140], [5, 180]], true))
-present(clusteringFreeRecall(
- [[2, 71], [2, 96], [3, 98], [2, 98], [5, 98], [7,98], [5, 130], [5, 135], [3, 140], [5, 180]], true))
\ No newline at end of file
diff --git a/src/adonisjs/public/editor/annotate/metrics/test-metrics.js b/src/adonisjs/public/editor/annotate/metrics/test-metrics.js
new file mode 100644
index 00000000..4d745ff4
--- /dev/null
+++ b/src/adonisjs/public/editor/annotate/metrics/test-metrics.js
@@ -0,0 +1,34 @@
+document.querySelector('#console').value = ''
+
+function present (output) {
+ document.querySelector('#console').value += output + '\n'
+}
+
+present(JSON.stringify(AnnotationMetrics.i._selfOrderCount(
+ [[1, 10], [2, 20], [1, 20], [2, 30]], present)))
+present(JSON.stringify(AnnotationMetrics.i._selfOrderCount(
+ [[1, 10], [2, 20], [1, 25], [2, 30]], present)))
+present(JSON.stringify(AnnotationMetrics.i._selfOrderCount(
+ [[1, 10], [2, 20], [1, 20], [3, 20], [1, 30], [2, 30], [3, 30]], present)))
+present(JSON.stringify(AnnotationMetrics.i._selfOrderCount(
+ [[2, 71], [2, 96], [3, 98], [2, 100], [5, 120], [5, 130], [5, 135], [3, 140], [5, 180]], present)))
+present(JSON.stringify(AnnotationMetrics.i._selfOrderCount(
+ [[5, 135], [2, 100], [2, 71], [2, 96], [5, 130], [3, 98], [5, 180], [5, 120], [3, 140]], present)))
+present(JSON.stringify(AnnotationMetrics.i._selfOrderCount(
+ [[2, 71], [2, 96], [3, 98], [2, 98], [5, 98], [5, 130], [5, 135], [3, 140], [5, 180]], present)))
+
+present(AnnotationMetrics.i._clusteringFreeRecall(
+ [[2, 1], [4, 2], [4, 3], [3, 4], [2, 5], [3, 6], [1, 7], [4, 8], [4, 9]], present
+))
+present(AnnotationMetrics.i._clusteringFreeRecall(
+ [[3, 1], [4, 2], [4, 3], [3, 4], [1, 5], [1, 6], [3, 7], [1, 8], [1, 9], [2, 10], [2, 11], [2, 12], [4, 13], [4, 14], [3, 15]], present
+))
+present(AnnotationMetrics.i._clusteringFreeRecall(
+ [[2, 1], [2, 2], [3, 3], [1, 4], [1, 5], [1, 6], [1, 7], [2, 8], [3, 9], [3, 10], [2, 11], [1, 12], [4, 13], [4, 14], [4, 15], [4, 16], [2, 17], [2, 18], [3, 19], [1, 20]], present
+))
+present(AnnotationMetrics.i._clusteringFreeRecall(
+ [[5, 135], [2, 100], [2, 71], [2, 96], [5, 130], [3, 98], [5, 180], [5, 120], [3, 140]], present))
+present(AnnotationMetrics.i._clusteringFreeRecall(
+ [[2, 71], [2, 96], [3, 98], [2, 98], [5, 98], [5, 130], [5, 135], [3, 140], [5, 180]], present))
+present(AnnotationMetrics.i._clusteringFreeRecall(
+ [[2, 71], [2, 96], [3, 98], [2, 98], [5, 98], [7,98], [5, 130], [5, 135], [3, 140], [5, 180]], present))
\ No newline at end of file
diff --git a/src/adonisjs/public/report/annotations/index.html b/src/adonisjs/public/report/annotations/index.html
index 868ac0e2..704f583e 100644
--- a/src/adonisjs/public/report/annotations/index.html
+++ b/src/adonisjs/public/report/annotations/index.html
@@ -23,6 +23,7 @@
+
@@ -61,7 +62,9 @@
-
+
+
+
@@ -98,7 +101,10 @@
-
+
+
+
diff --git a/src/adonisjs/public/report/category/cases/play/index-offline.html b/src/adonisjs/public/report/category/cases/play/index-offline.html
new file mode 100644
index 00000000..3bf92892
--- /dev/null
+++ b/src/adonisjs/public/report/category/cases/play/index-offline.html
@@ -0,0 +1,116 @@
+
+
+
+
+ Report Play
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Schema: list of fields to be retrieved
+
+
+
+
+
Tracking file:
+
+
+
+
+
+
+
+
+
+
Status:
+
+
Id:
+
+
Track:
+
+
+
State:
+
+
+
+
+
+
diff --git a/src/adonisjs/public/report/category/cases/play/schemas/heart-robot-blocks.csv b/src/adonisjs/public/report/category/cases/play/schemas/heart-robot-blocks.csv
new file mode 100644
index 00000000..a9237265
--- /dev/null
+++ b/src/adonisjs/public/report/category/cases/play/schemas/heart-robot-blocks.csv
@@ -0,0 +1 @@
+blocks
\ No newline at end of file
diff --git a/src/adonisjs/public/report/category/cases/play/schemas/heart-robot.csv b/src/adonisjs/public/report/category/cases/play/schemas/heart-robot-kolb-test.csv
similarity index 100%
rename from src/adonisjs/public/report/category/cases/play/schemas/heart-robot.csv
rename to src/adonisjs/public/report/category/cases/play/schemas/heart-robot-kolb-test.csv
diff --git a/src/adonisjs/public/report/category/cases/play/schemas/heart-robot-test.csv b/src/adonisjs/public/report/category/cases/play/schemas/heart-robot-test.csv
new file mode 100644
index 00000000..956f6346
--- /dev/null
+++ b/src/adonisjs/public/report/category/cases/play/schemas/heart-robot-test.csv
@@ -0,0 +1 @@
+Teste1.ta1,Teste2.ta2,Teste3.ta3,Teste4.ta4,Teste5.ta5,PosTeste1.tp1,PosTeste2.tp2,PosTeste3.tp3,PosTeste4.tp4,PosTeste5.tp5
\ No newline at end of file
diff --git a/src/adonisjs/public/report/js/report-annotations.js b/src/adonisjs/public/report/js/report-annotations.js
index f798548d..459e48cd 100644
--- a/src/adonisjs/public/report/js/report-annotations.js
+++ b/src/adonisjs/public/report/js/report-annotations.js
@@ -1,7 +1,7 @@
class ReportManager {
start () {
- this._download = this._download.bind(this)
- MessageBus.i.subscribe('report/download', this._download)
+ MessageBus.i.subscribe('report/download', this._downloadAnalysis.bind(this))
+ MessageBus.i.subscribe('report/bilou', this._downloadBILOU.bind(this))
this._roomId = new URL(document.location).searchParams.get('roomid')
}
@@ -78,6 +78,141 @@ class ReportManager {
return result
}
+ async _buildBILOU (caseId, annotations) {
+ const tt = await this._tokenize(caseId)
+ const tokens = tt.tokens
+
+ const ranges = []
+ for (const an of annotations) {
+ // find a proper category
+ let cat = null
+ for (const c of an.categories)
+ if (ReportManager.catList.includes(c)) {
+ cat = c
+ break
+ }
+
+ if (cat != null) {
+ if (!this._rangeConflict(ranges, an.fragments)) {
+ this._addRanges(ranges, an.fragments)
+ for (let f = 0; f < an.fragments.length; f++) {
+ let firstMatch = true
+ let firstToken = null
+ let prevLast = null
+ for (const tk of tokens) {
+ if (tk[1] >= an.fragments[f].start &&
+ tk[1] <= an.fragments[f].start + an.fragments[f].size - 1) {
+ tk[3] =
+ (an.fragments.length == 1 && firstMatch)
+ ? 'U'
+ : ((f == 0 && firstMatch)
+ ? 'B'
+ : ((f+1 < an.fragments.length) || (tk[2] < an.fragments[f].last))
+ ? 'I'
+ : 'L')
+ tk[4] = cat
+ if (firstMatch)
+ firstToken = tk
+ else if (firstToken != -1) {
+ firstToken[3] = 'B'
+ firstToken = -1
+ }
+ firstMatch = false
+ if (prevLast != null) {
+ prevLast[3] = 'I'
+ prevLast = null
+ }
+ if (tk[3] == 'L')
+ prevLast = tk
+ }
+ }
+ }
+ }
+ }
+ }
+
+ console.log('=== tokens NER')
+ console.log(tokens)
+
+ return {
+ doc_id: caseId,
+ text: tt.text,
+ labels: tokens
+ }
+ }
+
+ async _tokenize (caseId) {
+
+ const tokens = []
+
+ let result = {}
+
+ const cs = await MessageBus.i.request('case/source/get',
+ {room_id: this._roomId, case_id: caseId})
+
+ if (cs != null && cs.message != null) {
+ const compiled =
+ await Translator.instance.compileMarkdown(
+ cs.message.id, cs.message.source)
+
+ let text = ''
+ for (const knot in compiled.knots) {
+ const mkHTML =
+ await Translator.instance.generateHTML(compiled.knots[knot])
+ text += mkHTML
+ }
+
+ text = text.replace(/<[^>]+>/gm,'')
+ .replace(/<\/[^>]+>/gm, '')
+ .replace(/^[\r\n][\r\n]?/, '')
+
+ let c = 0
+ let tk = ''
+ let tks = -1
+ while (c <= text.length) {
+ if (c == text.length || ReportManager.separators.includes(text[c])) {
+ if (tks != -1) {
+ tokens.push([tk, tks, c-1, 'O', null])
+ tk = ''
+ tks = -1
+ }
+ } else {
+ if (tks == -1)
+ tks = c
+ tk += text[c]
+ }
+ c++
+ }
+
+ result = {
+ text: text,
+ tokens: tokens
+ }
+ }
+
+ return result
+ }
+
+ _rangeConflict (ranges, fragments) {
+ const start = fragments[0].start
+ const final = fragments[fragments.length-1].start + fragments[fragments.length-1].size - 1
+ let r = 0
+ while (r < ranges.length && start < ranges[r][1]) {
+ if ((start >= ranges[r][0] && start <= ranges[r][1]) ||
+ (final >= ranges[r][0] && final <= ranges[r][1]) ||
+ (ranges[r][0] >= start && ranges[r][0] <= final) ||
+ (ranges[r][1] >= start && ranges[r][1] <= final))
+ return true
+ r++
+ }
+ return false
+ }
+
+ _addRanges (ranges, fragments) {
+ for (const f of fragments)
+ ranges.push([f.start, f.start + f.size - 1])
+ }
+
_calculateMetrics (annotations) {
let ctideas = 0, ctright = 0, ctinfright = 0
let ctwrong = 0, ctrightencap = 0, ctinfrightencap = 0, ctwrongencap = 0
@@ -141,7 +276,26 @@ class ReportManager {
`${(ctideas == 0) ? 0 : selfOrder.score / ctideas},${clustering}${countCat},"${o1csv}","${o2csv}"`
}
- async _download () {
+ async _downloadBILOU () {
+ const tprefix = document.querySelector('#tprefix').value
+
+ const cases = await this._requestCases()
+
+ console.log(cases)
+ let table = ''
+
+ if (cases != null) {
+ for (const c of cases.message) {
+ const ant = await this._loadAnnotations(c.id)
+ const bilou = await this._buildBILOU(c.id, ant.annotations)
+ table += JSON.stringify(bilou) + '\n'
+ }
+
+ this._download(table)
+ }
+ }
+
+ async _downloadAnalysis () {
const tprefix = document.querySelector('#tprefix').value
const cases = await this._requestCases()
@@ -172,21 +326,28 @@ class ReportManager {
metrics + '\n'
}
- const element = document.createElement('a')
- element.setAttribute('href',
- 'data:text/plain;charset=utf-8,' + encodeURIComponent(table))
- element.setAttribute('download', 'annotations.csv')
- element.style.display = 'none'
- document.body.appendChild(element)
- element.click()
- document.body.removeChild(element)
+ this._download(table)
}
}
+
+ _download (table) {
+ const element = document.createElement('a')
+ element.setAttribute('href',
+ 'data:text/plain;charset=utf-8,' + encodeURIComponent(table))
+ element.setAttribute('download', 'annotations.csv')
+ element.style.display = 'none'
+ document.body.appendChild(element)
+ element.click()
+ document.body.removeChild(element)
+ }
}
(function () {
ReportManager.i = new ReportManager()
+ ReportManager.separators = [
+ ' ', '\n', '\r', '\t', '.', ',', ';', ':', '(', ')', '[', ']', '{', '}']
+
// Copy of the same constants in annotator.js
// isc: Illness Script Components
ReportManager.category = {
diff --git a/src/adonisjs/public/report/js/report-offline.js b/src/adonisjs/public/report/js/report-offline.js
new file mode 100644
index 00000000..5b875c6a
--- /dev/null
+++ b/src/adonisjs/public/report/js/report-offline.js
@@ -0,0 +1,163 @@
+import { Bus } from '/dccs/lib/oid/oid-full-dev.js'
+
+export class ReportManager {
+ start () {
+ MessageBus.i.subscribe('table/updated', this._updateCSV.bind(this))
+ Bus.i.subscribe('json/loaded', this._updateJSON.bind(this))
+ Bus.i.subscribe('report/add', this._addReport.bind(this))
+ Bus.i.subscribe('report/download', this._downloadReport.bind(this))
+
+ document.querySelector('#report-track').value = ''
+ document.querySelector('#report-state').value = ''
+ }
+
+ _updateCSV (topic, message) {
+ console.log('===== CSV received')
+ console.log(message.table.schema)
+ this._schema = message.table.schema
+ if (this._schema[0] == 'blocks')
+ this._table = '"user id","time","type","value"\n'
+ else {
+ this._table = '"user id","start","date"'
+ for (const s of this._schema)
+ this._table += ',"' + s + '","time","miliseconds"'
+ this._table += '\n'
+ }
+ document.querySelector('#report-status').innerHTML = 'Schema loaded'
+ }
+
+ _updateJSON(topic, message) {
+ console.log('===== JSON received')
+
+ this._id = '[empty]'
+ this._track = {}
+ this._state = {}
+ if (message.value != null) {
+ const idRest = message.value.split('[[TRACK]]')
+ this._id = idRest[0].trim()
+ if (idRest.length > 1) {
+ const trackState = idRest[1].split('[[STATE]]')
+ this._track = JSON.parse(trackState[0].trim())
+ if (trackState.length > 1)
+ this._state = JSON.parse(trackState[1].trim())
+ }
+ }
+
+ document.querySelector('#report-status').innerHTML = 'Tracking loaded'
+
+ document.querySelector('#report-id').innerHTML = this._id
+ document.querySelector('#report-track').value = JSON.stringify(this._track, null, 2)
+ document.querySelector('#report-state').value = JSON.stringify(this._state, null, 2)
+ }
+
+ _addReport () {
+ if (this._schema != null && this._table != null && this._track != null) {
+ if (this._schema[0] == 'blocks')
+ this._addBlocks()
+ else
+ this._addVariables()
+ }
+
+ document.querySelector('#report-status').innerHTML = 'Report added'
+
+ document.querySelector('#report-id').innerHTML = ''
+ document.querySelector('#report-track').value = ''
+ document.querySelector('#report-state').value = ''
+ }
+
+ _addBlocks () {
+ const zeroPad = (num) => String(num).padStart(2, '0')
+
+ let table = this._table
+ const track = this._prepareTrack(this._track.knotTrack, this._track.varTrack)
+ let lastTime = new Date(track.timeStart)
+ const head = '"' + this._id + '","'
+
+ for (const v of this._track.varTrack) {
+ if (v.blocks || v.talk) {
+ table += head + v.changed + '","'
+ if (v.blocks) {
+ if (v.blocks.value[0] != '{' && v.blocks.value[0] != '[')
+ table += 'a","' + v.blocks.value + '"\n'
+ else {
+ const vblock = v.blocks.value.split('\n')
+
+ let fblock = ''
+ for (const b of vblock)
+ fblock += JSON.stringify(JSON.parse(b), null, 2) + '\n'
+
+ table += 'b","' + fblock.replace(/"/g, '""') + '"\n'
+ }
+ } else if (v.talk) {
+ table += 't","' + v.talk.value.replace(/"/g, '""').replace(/
/g, '\n') + '"\n'
+ }
+ }
+ }
+
+ this._table = table
+ }
+
+ _addVariables () {
+ const zeroPad = (num) => String(num).padStart(2, '0')
+
+ let table = this._table
+ const track = this._prepareTrack(this._track.knotTrack, this._track.varTrack)
+ let lastTime = new Date(track.timeStart)
+ table += '"' + this._id + '","' +
+ track.timeStart + '","' +
+ zeroPad(lastTime.getDate()) + '/' +
+ zeroPad(lastTime.getMonth() + 1) + '/' +
+ zeroPad(lastTime.getFullYear()) + '"'
+ for (const s of this._schema) {
+ let time = -1
+ if (track[s] != null) {
+ const t = new Date(track[s])
+ time = new Date(t - lastTime)
+ lastTime = t
+ }
+
+ const variables = this._track.variables
+ console.log('=== variable ' + s)
+ console.log(variables[s])
+ table += ',"' +
+ (variables[s] == null
+ ? ''
+ : (typeof variables[s] === 'string' || variables[s] instanceof String)
+ ? variables[s].replace(/"/g, '""')
+ : variables[s]
+ ) +
+ '","' + (time == -1 ? ''
+ : zeroPad(time.getMinutes()) + ':' +
+ zeroPad(time.getSeconds())) + '","' +
+ ((time == -1) ? '' : time.getTime()) + '"'
+ }
+ this._table = table + '\n'
+ }
+
+ _prepareTrack (knotTrack, varTrack) {
+ const track = {timeStart: knotTrack[0].timeStart}
+ for (const v of varTrack) {
+ const changed = v.changed
+ for (const t in v)
+ if (t != 'changed' && !track[v[t]])
+ track[t] = changed
+ }
+ return track
+ }
+
+ _downloadReport () {
+ if (this._table != null) {
+ const element = document.createElement('a')
+ element.setAttribute('href',
+ 'data:text/plain;charset=utf-8,' + encodeURIComponent(this._table))
+ element.setAttribute('download', 'log.csv')
+ element.style.display = 'none'
+ document.body.appendChild(element)
+ element.click()
+ document.body.removeChild(element)
+ }
+ }
+}
+
+ReportManager.i = new ReportManager()
+window.reportManager = ReportManager.i