Skip to content

Commit e9057d5

Browse files
committed
Cache Confusion Evaluation
1 parent f138dbf commit e9057d5

41 files changed

Lines changed: 420 additions & 548 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

client/src/api/evaluation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
import axios, { type AxiosResponse } from "axios"
66
import { getBlob, type BlobResponse } from "@/api/utils"
77
import type { UUID } from "@/types/corpora"
8-
import type { ConfusionWrapper, DistributionWrapper, Metrics, TermComparison } from "@/types/evaluation"
8+
import type { Confusion, DistributionWrapper, Metrics, TermComparison } from "@/types/evaluation"
99
import type { DocumentEntities, JobEntities, JobsEntities } from "@/types/evaluation/entities"
1010

11-
type ConfusionResponse = AxiosResponse<ConfusionWrapper>
11+
type ConfusionResponse = AxiosResponse<Confusion>
1212
type DistributionResponse = AxiosResponse<DistributionWrapper>
1313
type MetricsResponse = AxiosResponse<Metrics>
1414
type DocumentEntitiesResponse = AxiosResponse<DocumentEntities>

client/src/components/modals/ComparisonModal.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import * as API from "@/api/evaluation"
3333
import * as Utils from "@/api/utils"
3434
import stores from "@/stores"
35-
import { literalsForTermComparison } from "@/stores/evaluation"
35+
import { literalsForTermComparison } from "@/ts/termcomparison"
3636
import type { TermComparison } from "@/types/evaluation"
3737
3838
// Stores
@@ -57,8 +57,8 @@ const title = computed<string>(() => {
5757
})
5858
const annotations = computed(() => {
5959
const firstSample = props.samples.samples[0]
60-
const hypoAnnotations = Object.keys(firstSample.hypoTerm.annotations)
61-
const refAnnotations = Object.keys(firstSample.refTerm.annotations)
60+
const hypoAnnotations = Object.keys(firstSample.hyp.annotations)
61+
const refAnnotations = Object.keys(firstSample.ref.annotations)
6262
return [...new Set([...hypoAnnotations, ...refAnnotations])].filter((i) => !ignorableAnnotations.includes(i))
6363
})
6464
@@ -93,10 +93,10 @@ const filteredColumns = computed(() => {
9393
const items = computed(() => {
9494
if (!props.samples.samples) return []
9595
return props.samples.samples.map((sample: TermComparison) => {
96-
const hypoAnnotations = Object.entries(sample.hypoTerm.annotations).map((i) => ({
96+
const hypoAnnotations = Object.entries(sample.hyp.annotations).map((i) => ({
9797
[`${props.hypothesisJob}-${i[0]}`]: i[1],
9898
}))
99-
const refAnnotations = Object.entries(sample.refTerm.annotations).map((i) => ({
99+
const refAnnotations = Object.entries(sample.ref.annotations).map((i) => ({
100100
[`${props.referenceJob}-${i[0]}`]: i[1],
101101
}))
102102

client/src/components/tables/DocumentsTable.vue

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131

3232
<template #cell-actions="data: TableData<DocumentMetadata>">
3333
<GForm gap=".25rem">
34-
<DownloadButton @click="downloadRaw(data.item.name)" />
34+
<DownloadButton @click="download(data.item.name)" />
3535

36-
<GButton red title="Delete" @click="deleteDocumentData = data.item">
36+
<GButton red title="Delete" @click="deleteDocument = data.item">
3737
<i class="fa fa-trash"></i>
3838
</GButton>
3939
</GForm>
@@ -45,10 +45,10 @@
4545
</GModal>
4646

4747
<DeleteModal
48-
v-if="deleteDocumentData"
49-
:itemName="`${deleteDocumentData.name} and associated results`"
50-
@hide="deleteDocumentData = undefined"
51-
@delete="deleteDocument(deleteDocumentData.name)"
48+
v-if="deleteDocument"
49+
:itemName="`${deleteDocument.name} and associated results`"
50+
@hide="deleteDocument = undefined"
51+
@delete="remove(deleteDocument.name)"
5252
/>
5353
</template>
5454

@@ -60,7 +60,7 @@ import type { DocumentMetadata } from "@/types/documents"
6060
import { type Column, type TableData, DocsTableType } from "@/types/ui/table"
6161
6262
// Stores
63-
const { deleteDocument, downloadRaw } = stores.useDocuments()
63+
const { remove, download } = stores.useDocuments()
6464
const { canWrite } = storeToRefs(stores.useUser())
6565
6666
// --- props ---
@@ -72,7 +72,7 @@ const { type, corpus, documents, loading } = defineProps<{
7272
}>()
7373
7474
// --- data ---
75-
const deleteDocumentData = ref<DocumentMetadata>()
75+
const deleteDocument = ref<DocumentMetadata>()
7676
const previewDocument = ref<DocumentMetadata>()
7777
7878
// --- computed ---

client/src/stores/corpora.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ const useCorpora = defineStore("corpora", () => {
5353
API.patchCorpus(metadata.uuid, metadata).finally(reload)
5454
}
5555

56-
reload()
57-
5856
return {
5957
corpora,
6058
loading,

client/src/stores/documents.ts

Lines changed: 22 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
// Libraries & stores
21
import { plausible } from "@/ts/plausible"
32
import * as API from "@/api/documents"
4-
import { documentsPath } from "@/api/documents"
53
import * as Utils from "@/api/utils"
64
import stores from "@/stores"
7-
// Types & API
8-
import { type DocumentMetadata, Format } from "@/types/documents"
9-
import { useAxios } from "@/api/useAxios"
5+
import { type DocumentMetadata } from "@/types/documents"
6+
import { addContentTypeHeader, fileExtension } from "@/ts/file"
107

118
// Custom types
129
type FileStatus = { status: "busy" | "success" | "error"; message?: string }
@@ -17,20 +14,12 @@ type FileStatus = { status: "busy" | "success" | "error"; message?: string }
1714
*/
1815
const documents = defineStore("documents", () => {
1916
// Stores
20-
const errors = stores.useErrors()
2117
const { corpusId, corpus } = storeToRefs(stores.useCorpora())
2218
const { reload: reloadCorpora } = stores.useCorpora()
2319

2420
// Fields
25-
const {
26-
data: documents,
27-
loading,
28-
reload,
29-
} = useAxios<DocumentMetadata[]>(
30-
(): string | undefined => (corpusId.value ? documentsPath(corpusId.value) : undefined),
31-
[],
32-
)
33-
21+
const loading = ref<boolean>(false)
22+
const documents = ref<DocumentMetadata[]>([])
3423
const numSourceAnnotations = computed(() => documents.value.filter((i) => i.summary?.annotations.token > 0).length)
3524
const uploading: Record<string, FileStatus> = reactive({})
3625
const uploadBusyCount = computed(() => Object.values(uploading).filter((i) => i.status === "busy").length)
@@ -43,36 +32,34 @@ const documents = defineStore("documents", () => {
4332
})
4433
})
4534

46-
/**
47-
* Delete a document.
48-
* @param name Document name.
49-
*/
50-
function deleteDocument(name: string): void {
35+
/** Reload documents and corpora (number of docs in corpusmetadata can change). */
36+
function reload(): void {
37+
reloadCorpora()
38+
loading.value = true
39+
API.getDocuments(corpusId.value)
40+
.then((res) => (documents.value = res.data))
41+
.finally(() => (loading.value = false))
42+
}
43+
44+
/** Delete a document. */
45+
function remove(name: string): void {
5146
plausible.documentDeleted(corpus.value, getDocument(name))
5247
API.deleteDocument(corpusId.value, name)
53-
.catch((error) => errors.handle(error))
5448
.finally(reload)
5549
}
5650

57-
/**
58-
* Download original source document.
59-
* @param name Document name.
60-
*/
61-
function downloadRaw(name: string): void {
51+
/** Download original source document. */
52+
function download(name: string): void {
6253
plausible.documentDownloaded(corpus.value, getDocument(name))
6354
API.getRawDocument(corpusId.value, name)
6455
.then(Utils.browserDownloadResponseFile)
65-
.catch((res) => Utils.handleBlobError(res, "download raw document", errors))
6656
}
6757

6858
function getDocument(name: string): DocumentMetadata {
6959
return documents.value.find((d: DocumentMetadata) => d.name === name) as DocumentMetadata
7060
}
7161

72-
/**
73-
* Upload all files in filesToUpload.
74-
* Creates timeouts to spread load.
75-
*/
62+
/** Upload all files in filesToUpload. Creates timeouts to spread load. */
7663
function uploadAll(): void {
7764
for (let i = 0; i < filesToUpload.value.length; i++) {
7865
const formData = new FormData()
@@ -86,45 +73,13 @@ const documents = defineStore("documents", () => {
8673
filesToUpload.value = []
8774
}
8875

89-
/**
90-
* Clear errors from not yet uploaded files.
91-
*/
76+
/** Clear errors from not yet uploaded files. */
9277
function clearUploadErrors(): void {
9378
Object.keys(uploading).forEach((key) => {
9479
if (uploading[key].status === "error") delete uploading[key]
9580
})
9681
}
9782

98-
/**
99-
* Add content type header.
100-
* @param fd FormData with file to upload.
101-
* @param contentType Content type header.
102-
* @param exts File extensions to apply the content type header to.
103-
*/
104-
function addContentTypeHeader(fd: FormData): Record<string, string> | null {
105-
const exts_and_headers = {
106-
tsv: "text/tab-separated-values",
107-
conllu: "text/tab-separated-values",
108-
naf: "text/xml",
109-
}
110-
111-
let file = fd.get("file") as File
112-
const extension = fileExtension(file)
113-
let header = null
114-
115-
if (Object.keys(exts_and_headers).includes(extension)) {
116-
const contentType = exts_and_headers[extension]
117-
file = new File([file], file.name, { type: contentType })
118-
header = { "Content-Type": contentType }
119-
fd.set("file", file)
120-
}
121-
return header
122-
}
123-
124-
function fileExtension(file: File): string {
125-
return file.name.split(".").at(-1)
126-
}
127-
12883
/**
12984
* Upload a single file. Takes http content type header into account.
13085
* @param formData FormData with file to upload.
@@ -147,25 +102,10 @@ const documents = defineStore("documents", () => {
147102
.finally(() => {
148103
if (uploadBusyCount.value === 0) {
149104
reload()
150-
reloadCorpora()
151105
}
152106
})
153107
}
154108

155-
/**
156-
* Checks if the documentsStore.documents contains at least one file of the given format.
157-
*/
158-
function containsFormat(format: Format): boolean {
159-
return documents.value.some((i) => {
160-
// Overwrite the format for legacy formats.
161-
let otherFormat = i.format
162-
if (otherFormat === Format.TEI_P5_LEGACY) {
163-
otherFormat = Format.TEI_P5
164-
}
165-
return otherFormat === format
166-
})
167-
}
168-
169109
// Exports
170110
return {
171111
// Fields
@@ -178,11 +118,11 @@ const documents = defineStore("documents", () => {
178118
uploadErrorCount,
179119
numSourceAnnotations,
180120
// Methods
181-
deleteDocument,
182-
downloadRaw,
121+
reload,
122+
remove,
123+
download,
183124
uploadAll,
184125
clearUploadErrors,
185-
containsFormat,
186126
}
187127
})
188128

client/src/stores/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const useErrors = defineStore("errors", () => {
1616
}
1717

1818
setupErrorHandler()
19-
19+
2020
return { errors }
2121
})
2222

client/src/stores/evaluation.ts

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,22 @@
1-
// Libraries & stores
2-
3-
// API & types
1+
import stores from "@/stores"
42
import * as API from "@/api/evaluation"
53
import * as Utils from "@/api/utils"
6-
import stores from "@/stores"
7-
import type { UUID } from "@/types/corpora"
8-
import type { Term, TermComparison } from "@/types/evaluation"
9-
10-
// For some reason the terms are undefined sometimes
11-
// We handle it here
12-
export function literalsForTerm(term: Term): string {
13-
return term.annotations.token
14-
}
15-
16-
export function literalsForTermComparison(termComparison: TermComparison): string {
17-
// the literals could be different for term1 and term2
18-
if (
19-
literalsForTerm(termComparison.hypoTerm) === literalsForTerm(termComparison.refTerm) ||
20-
literalsForTerm(termComparison.refTerm) === ""
21-
) {
22-
return literalsForTerm(termComparison.hypoTerm)
23-
}
24-
if (literalsForTerm(termComparison.hypoTerm) === "") {
25-
return literalsForTerm(termComparison.refTerm)
26-
}
27-
return `MISMATCH: [${literalsForTerm(termComparison.hypoTerm)}${literalsForTerm(termComparison.refTerm)}]`
28-
}
294

30-
/**
31-
* Used to download the evaluation CSV zip.
32-
*/
5+
/** Used to download the evaluation CSV zip. */
336
const useEvaluation = defineStore("evaluation", () => {
34-
// Stores
35-
const errors = stores.useErrors()
36-
const corporaStore = stores.useCorpora()
37-
const jobSelection = stores.useJobSelection()
7+
const { corpusId } = storeToRefs(stores.useCorpora())
8+
const { hypothesisId, referenceId } = storeToRefs(stores.useJobSelection())
389

39-
// Fields
4010
const loading = ref<boolean>()
41-
/** Hypothesis, reference and corpusUUID for which the current evaluations are loaded. */
42-
const hypothesis = ref<string>()
43-
const reference = ref<string>()
44-
const corpusUUID = ref<UUID>()
4511

46-
// Methods
4712
function downloadCSV(): void {
4813
loading.value = true
49-
API.getDownloadEvaluation(corporaStore.corpusId, jobSelection.hypothesisId, jobSelection.referenceId)
14+
API.getDownloadEvaluation(corpusId.value, hypothesisId.value, referenceId.value)
5015
.then(Utils.browserDownloadResponseFile)
51-
.catch((error) => Utils.handleBlobError(error, "download evaluation", errors))
52-
.finally(() => {
53-
loading.value = false
54-
})
16+
.finally(() => loading.value = false)
5517
}
5618

57-
// Exports
58-
return { downloadCSV, loading, hypothesis, reference, corpusUUID }
19+
return { downloadCSV, loading }
5920
})
6021

6122
export default useEvaluation
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import { confusionPath } from "@/api/evaluation"
2-
import { useAxios } from "@/api/useAxios"
31
import stores from "@/stores"
4-
import type { ConfusionWrapper } from "@/types/evaluation"
2+
import type { Confusion } from "@/types/evaluation"
3+
import * as API from "@/api/evaluation"
54

65
/** Stores and fetches the confusion matrix. */
76
const useConfusion = defineStore("confusion", () => {
87
const { hypothesisId, referenceId } = storeToRefs(stores.useJobSelection())
98
const { corpusId } = storeToRefs(stores.useCorpora())
10-
const url = computed<string>(() => {
11-
if (!(hypothesisId.value && referenceId.value)) return undefined
12-
return confusionPath(corpusId.value)
13-
})
14-
const { loading, data: confusion } = useAxios<ConfusionWrapper>(
15-
url,
16-
{},
17-
{ hypothesis: hypothesisId.value, reference: referenceId.value },
18-
)
9+
const loading = ref<boolean>(false)
10+
const confusions = ref<Record<string, Confusion>>()
1911

20-
return { confusion, loading }
12+
function reload(): void {
13+
if (!corpusId.value || !hypothesisId.value || !referenceId.value) return
14+
loading.value = true
15+
API.getConfusion(corpusId.value, hypothesisId.value, referenceId.value)
16+
.then((res) => (confusions.value = res.data))
17+
.finally(() => (loading.value = false))
18+
}
19+
20+
return { reload, confusions, loading }
2121
})
2222

2323
export default useConfusion

0 commit comments

Comments
 (0)