Skip to content

Commit bddfbc6

Browse files
committed
Added JobEntities
1 parent d8d527e commit bddfbc6

15 files changed

Lines changed: 198 additions & 66 deletions

File tree

client/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ declare module 'vue' {
1818
CorpusTable: typeof import('./src/components/tables/CorpusTable.vue')['default']
1919
DeleteModal: typeof import('./src/components/modals/DeleteModal.vue')['default']
2020
DifferentTagsetsHelp: typeof import('./src/components/help/DifferentTagsetsHelp.vue')['default']
21+
DocumentEntitiesTable: typeof import('./src/components/tables/DocumentEntitiesTable.vue')['default']
2122
DocumentSelect: typeof import('./src/components/input/DocumentSelect.vue')['default']
2223
DocumentsHelp: typeof import('./src/components/help/DocumentsHelp.vue')['default']
2324
DocumentsTable: typeof import('./src/components/tables/DocumentsTable.vue')['default']

client/src/api/evaluation.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import type { UUID } from "@/types/corpora"
88
import type {
99
ConfusionWrapper,
1010
DistributionWrapper,
11-
Entity,
1211
Metrics,
1312
TermComparison,
1413
} from "@/types/evaluation"
14+
import type { DocumentEntities, JobEntities } from "@/types/evaluation/entities"
1515

1616
type ConfusionResponse = AxiosResponse<ConfusionWrapper>
1717
type DistributionResponse = AxiosResponse<DistributionWrapper>
1818
type MetricsResponse = AxiosResponse<Metrics>
19-
type EntitiesResponse = AxiosResponse<Entity[]>
19+
type DocumentEntitiesResponse = AxiosResponse<DocumentEntities>
20+
type JobEntitiesResponse = AxiosResponse<JobEntities>
2021

2122
const evaluationPath = (corpus: UUID, hypothesis: string): string =>
2223
`/corpora/${corpus}/jobs/${hypothesis}/evaluation`
@@ -37,9 +38,10 @@ const documentLayerComparisonPath = (
3738
job: string,
3839
document: string,
3940
): string => `/corpora/${corpus}/jobs/${job}/documents/${document}/evaluation`
40-
const entitiesPath = (corpus: UUID, job: string, document: string): string =>
41+
const documentEntitiesPath = (corpus: UUID, job: string, document: string): string =>
4142
`/corpora/${corpus}/jobs/${job}/documents/${document}/entities`
42-
43+
const jobEntitiesPath = (corpus: UUID, job: string): string =>
44+
`${evaluationPath(corpus, job)}/entities`
4345
/**
4446
* Fetch term frequency distribution.
4547
* @param corpus UUID of the corpus.
@@ -166,10 +168,17 @@ export function getDocumentLayerComparison(
166168
})
167169
}
168170

169-
export function getEntities(
171+
export function getDocumentEntities(
170172
corpus: UUID,
171173
job: string,
172174
document: string,
173-
): Promise<EntitiesResponse> {
174-
return axios.get(entitiesPath(corpus, job, document))
175+
): Promise<DocumentEntitiesResponse> {
176+
return axios.get(documentEntitiesPath(corpus, job, document))
177+
}
178+
179+
export function getJobEntities(
180+
corpus: UUID,
181+
job: string,
182+
): Promise<JobEntitiesResponse> {
183+
return axios.get(jobEntitiesPath(corpus, job))
175184
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<template>
2+
<GTable :items :columns sortedByColumn="count" compact>
3+
<template #table-empty-instruction>
4+
No entities found in this document.
5+
</template>
6+
</GTable>
7+
</template>
8+
9+
10+
<script setup lang="ts">
11+
import type { Entity } from '@/types/evaluation/entities'
12+
import type { Field } from '@/types/ui/table'
13+
14+
const {
15+
entities,
16+
filter,
17+
} = defineProps<{
18+
entities: Entity[]
19+
filter: string
20+
}>()
21+
22+
const items = computed(() => entities.filter((i) => i.label === filter || filter === "total"))
23+
const columns = ref<Field[]>([
24+
{ key: "label", sortOn: (i) => i.label },
25+
{ key: "form", sortOn: (i) => i.form },
26+
{ key: "count", sortOn: (i) => i.count }
27+
])
28+
</script>

client/src/components/tables/GTable.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ const {
115115
} = defineProps<{
116116
title?: string
117117
helpLink?: HelpLink | string
118-
loading: boolean
118+
loading?: boolean
119119
displayOnEmpty?: boolean
120120
columns: Field[]
121121
selectable?: boolean

client/src/types/evaluation.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,3 @@ export type EvaluationEntry = {
8989
count: number
9090
samples: TermComparison[]
9191
}
92-
93-
export type Entity = {
94-
first: string
95-
second: Term[]
96-
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export type Entity = {
2+
label: string
3+
form: string
4+
count: number
5+
}
6+
7+
export type DocumentEntities = {
8+
entities: Entity[]
9+
summary: Record<string, number>
10+
total: number
11+
}
12+
13+
export type JobEntities = {
14+
documents: Record<string, DocumentEntities>
15+
summary: Record<string, number>
16+
total: number
17+
}

client/src/views/annotate/subviews/evaluate/subviews/EntitiesView.vue

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,87 @@
22
<GCard title="Entities View">
33
<template #help>
44
<p>
5-
Here you can see all the named entities in the selected document.
5+
Here you can see all the named entities in the selected hypothesis job.
66
</p>
77
</template>
8-
<DocumentSelect v-if="jobSelection.hypothesisJobId" v-model="selectedDoc" />
9-
<p v-else>
10-
Please select a hypothesis job first.
11-
</p>
12-
<GTable v-if="items" :title :loading :items :columns sortedByColumn="count" compact>
8+
9+
<GTable v-if="items" class="table" :title="`Entities in ${jobSelection.hypothesisJobId}`" :loading :items
10+
:columns compact>
1311
<template #table-empty-instruction>
14-
No entities found in this document.
12+
Select a hypothesis layer.
13+
</template>
14+
15+
<template #cell="data: TableData<DocumentEntities>">
16+
<GButton v-if="data.item.document != 'total' && data.field.key != 'document'" class="button"
17+
@click="selectedItem = data">
18+
{{ data.value }}
19+
</GButton>
1520
</template>
1621
</GTable>
22+
23+
<GModal :show="selectedItem" @hide="selectedItem = undefined"
24+
:title="`Entities in ${selectedItem?.item?.document}`">
25+
<template #help>
26+
Here you can view all the entities in the selected document.
27+
</template>
28+
<DocumentEntitiesTable :filter="selectedItem?.field?.key" :entities="selectedItem?.item?.entities">
29+
</DocumentEntitiesTable>
30+
</GModal>
1731
</GCard>
1832
</template>
1933

2034
<script setup lang="ts">
2135
import stores from '@/stores'
2236
import * as API from '@/api/evaluation'
23-
import type { Entity, Term } from '@/types/evaluation'
2437
import type { Field, TableData } from '@/types/ui/table'
38+
import { Entity, type DocumentEntities } from '@/types/evaluation/entities'
2539
2640
// --- stores ---
2741
const jobSelection = stores.useJobSelection()
2842
const corpora = stores.useCorpora()
2943
3044
// --- data ---
31-
const selectedDoc = ref<string>()
3245
const loading = ref<boolean>(false)
33-
const items = ref<Entity[]>()
34-
const columns = ref<Field[]>([
35-
{ key: "label", sortOn: (i) => i.label },
36-
{ key: "form", sortOn: (i) => i.form },
37-
{ key: "count", sortOn: (i) => i.count }
38-
])
46+
const items = ref<DocumentEntities[]>()
47+
const selectedItem = ref<TableData<DocumentEntities>>()
3948
4049
// --- computed ---
41-
const title = computed<string>(() => {
42-
return `Entities in ${selectedDoc.value}`
43-
})
50+
const columns = computed<Field[]>(() => Object.keys(items.value[0]).map((i) => ({ key: i })))
4451
45-
46-
watch(() => selectedDoc.value, () => {
52+
watch(() => jobSelection.hypothesisJobId, () => {
53+
if (!jobSelection.hypothesisJobId) return
4754
loading.value = true
48-
API.getEntities(
55+
API.getJobEntities(
4956
corpora.activeUUID,
5057
jobSelection.hypothesisJobId,
51-
selectedDoc.value
5258
).then(res => {
53-
items.value = res.data
59+
items.value = Object.entries(res.data.documents).map(([key, value]) => ({ document: key, ...value.summary, total: value.total, entities: value.entities }))
60+
items.value.unshift({ document: "total", ...res.data.summary, total: res.data.total })
5461
}).finally(() => {
5562
loading.value = false
5663
})
57-
})
64+
}, { immediate: true })
5865
</script>
66+
67+
<style scoped lang="scss">
68+
.table {
69+
:deep(td) {
70+
padding: 0 !important;
71+
box-sizing: border-box !important;
72+
73+
.button {
74+
width: 100%;
75+
height: 100%;
76+
background-color: transparent;
77+
78+
&:hover {
79+
background-color: var(--int-light-grey) !important;
80+
}
81+
82+
&:focus {
83+
background-color: var(--int-light-grey-hover) !important;
84+
}
85+
}
86+
}
87+
}
88+
</style>

server/src/main/kotlin/org/ivdnt/galahad/app/Galahad.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ const val JOBS_URL: String = "$CORPUS_URL/jobs"
4949
const val JOB_URL: String = "$JOBS_URL/{job}"
5050
const val JOB_DOCUMENT_URL: String = "$JOB_URL/documents/{document}"
5151

52+
// TODO refactor to remove {job} path param, instead using ?hypothesis and ?reference query params
5253
const val EVALUATION_URL: String = "$JOB_URL/evaluation"
54+
const val JOB_ENTITIES_URL: String = "$EVALUATION_URL/entities"
5355
const val DISTRIBUTION_URL: String = "$EVALUATION_URL/distribution"
5456
const val TOKEN_FREQUENCY_URL: String = "$EVALUATION_URL/frequency"
55-
const val ENTITIES_URL: String = "$JOB_DOCUMENT_URL/entities"
57+
const val DOCUMENT_ENTITIES_URL: String = "$JOB_DOCUMENT_URL/entities"
5658
const val METRICS_URL: String = "$EVALUATION_URL/metrics"
5759
const val METRICS_SAMPLES_URL: String = "$METRICS_URL/download"
5860
const val CONFUSION_URL: String = "$EVALUATION_URL/confusion"

server/src/main/kotlin/org/ivdnt/galahad/evaluation/DocumentEvaluation.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ class DocumentEvaluation(
1111
private val corpus: Corpus,
1212
private val jobs: JobPair,
1313
) : GalahadFolder(dir) {
14-
val entities: List<DocumentEntities.Entity> get() = entitiesCache.readOrCreate()
14+
val entities: DocumentEntities get() = entitiesCache.readOrCreate()
1515
private val entitiesFile = dir.resolve(ENTITIES_FILE)
16-
private val entitiesCache = object : ValidatedDiskValue<List<DocumentEntities.Entity>>(entitiesFile) {
16+
private val entitiesCache = object : ValidatedDiskValue<DocumentEntities>(entitiesFile) {
1717
override fun isValid(lastModified: Long) = lastModified >= corpus.lastModified
18-
override fun set(): List<DocumentEntities.Entity> = DocumentEntities.fromLayer(corpus.jobs.readOrThrow(jobs.reference).getLayer(name))
18+
override fun set(): DocumentEntities = DocumentEntities.create(corpus.jobs.readOrThrow(jobs.reference).getLayer(name))
1919
}
2020
companion object {
2121
const val ENTITIES_FILE = "entities.json"

server/src/main/kotlin/org/ivdnt/galahad/evaluation/JobEvaluation.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import org.ivdnt.galahad.annotations.Annotation
44
import org.ivdnt.galahad.corpora.Corpus
55
import org.ivdnt.galahad.evaluation.confusion.CONFUSION_TYPES
66
import org.ivdnt.galahad.evaluation.distribution.CorpusDistribution
7+
import org.ivdnt.galahad.evaluation.entities.DocumentEntities
8+
import org.ivdnt.galahad.evaluation.entities.JobEntities
79
import org.ivdnt.galahad.exceptions.DocumentNotFoundException
810
import org.ivdnt.galahad.files.GalahadFolder
911
import org.ivdnt.galahad.files.GalahadFolderManager
@@ -19,6 +21,13 @@ class JobEvaluation(
1921
) : GalahadFolder(dir) {
2022
val documents: DocumentEvaluations = DocumentEvaluations(dir.resolve(DOCUMENTS_FOLDER), corpus, jobs)
2123

24+
val entities: JobEntities get() = entitiesCache.readOrCreate()
25+
private val entitiesFile = dir.resolve(ENTITIES_FILE)
26+
private val entitiesCache = object : ValidatedDiskValue<JobEntities>(entitiesFile) {
27+
override fun isValid(lastModified: Long) = lastModified >= corpus.lastModified
28+
override fun set(): JobEntities = JobEntities.create(corpus, documents)
29+
}
30+
2231
val distribution: Map<Annotation, CorpusDistribution> get() = distributionCache.readOrCreate()
2332
private val distributionFile = dir.resolve(DISTRIBUTION_FILE)
2433
private val distributionCache = object : ValidatedDiskValue<Map<Annotation, CorpusDistribution>>(distributionFile) {
@@ -42,6 +51,7 @@ class JobEvaluation(
4251

4352
companion object {
4453
private const val DISTRIBUTION_FILE = "distribution.json"
54+
private const val ENTITIES_FILE = "entities.json"
4555
const val DOCUMENTS_FOLDER = "documents"
4656
}
4757
}

0 commit comments

Comments
 (0)