vue-iaa-metrics 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # Vue IAA Metrics
2
+
3
+ A **Vue component library, distributed as an npm package**, for browsing
4
+ per-annotator annotations and computing inter-annotator agreement (IAA)
5
+ metrics over a shared document set. Extracted from
6
+ [Lawnotation](https://lawnotation.org)'s task metrics page so the same UI and
7
+ orchestration logic can be reused by other projects, decoupled from
8
+ Lawnotation's own data model (Supabase/tRPC) and UI stack (PrimeVue/Tailwind).
9
+
10
+ ---
11
+
12
+ ## 1. What this package does
13
+
14
+ Given a set of documents that one or more annotators have labelled (spans of
15
+ text, or whole-document tags) plus a per-item confidence rating, this package:
16
+
17
+ - Lets a user filter and browse the raw annotations, grouped by document.
18
+ - Sends the whole task to an external **IAA service** (the Go tool in
19
+ [`lawnotation-iaa`](../lawnotation-iaa)) to compute agreement metrics —
20
+ span-matching precision/recall/F1 and coverage agreement (Krippendorff's α,
21
+ Cohen's κ) — and displays the results.
22
+ - Lets the user download the full report as a ZIP, optionally anonymizing
23
+ annotator identifiers first.
24
+
25
+ It does **not** talk to any backend directly. Everything — fetching filter
26
+ options, fetching annotations, and calling the IAA service — goes through a
27
+ single delegate interface the host implements: `MetricsSource`.
28
+
29
+ ---
30
+
31
+ ## 2. Usage
32
+
33
+ ```vue
34
+ <script setup lang="ts">
35
+ import { MetricsPage, type MetricsSource } from "vue-iaa-metrics";
36
+ import "vue-iaa-metrics/style.css";
37
+
38
+ const source: MetricsSource = {
39
+ /* see contract below */
40
+ };
41
+ </script>
42
+
43
+ <template>
44
+ <MetricsPage
45
+ :source="source"
46
+ report-filename="my-task.zip"
47
+ @error="(message) => toast.error(message)"
48
+ @open-document="(doc) => router.push(`/documents/${doc.id}`)"
49
+ />
50
+ </template>
51
+ ```
52
+
53
+ ### Props
54
+
55
+ | Prop | Type | Notes |
56
+ |---|---|---|
57
+ | `source` | `MetricsSource` | Required. The data/network delegate — see below. |
58
+ | `reportFilename` | `string` | Optional. Filename used when saving a downloaded report. Defaults to `iaa_report.zip`. |
59
+
60
+ ### Events
61
+
62
+ | Event | Payload | Notes |
63
+ |---|---|---|
64
+ | `error` | `message: string` | Fired whenever a `MetricsSource` call rejects. The package has no built-in toast — hosts surface this however they like. |
65
+ | `open-document` | `{ id: string; name: string }` | Fired when the user clicks a document header in the annotation list. Not a real `<a href>` (the package can't assume `vue-router`/Nuxt) — the host decides how to navigate. |
66
+
67
+ ---
68
+
69
+ ## 3. The `MetricsSource` contract
70
+
71
+ ```ts
72
+ interface MetricsSource {
73
+ getLabels(): Promise<LabelOption[]> | LabelOption[];
74
+ getAnnotators(): Promise<string[]> | string[];
75
+ getDocuments(): Promise<DocumentOption[]> | DocumentOption[];
76
+ getAnnotations(filters: AnnotationFilters): Promise<RichAnnotation[]>;
77
+ getIaaInputData(): Promise<IaaInputData>;
78
+ computeMetrics(input: IaaInputData, params: IaaParams): Promise<IaaMetricsResponse>;
79
+ downloadReport(input: IaaInputData, params: IaaParams): Promise<Blob>;
80
+ }
81
+ ```
82
+
83
+ - **`getLabels` / `getAnnotators` / `getDocuments`** — populate the filter
84
+ dropdowns. Called once, on mount.
85
+ - **`getAnnotations(filters)`** — the browsable, filtered annotation list.
86
+ `filters` arrays are empty when nothing's selected for that facet (empty
87
+ means "all", not "none"). Called on mount and again whenever a filter
88
+ changes.
89
+ - **`getIaaInputData()`** — the *whole* task (unfiltered), in the shape the
90
+ IAA service expects. Called once on mount and cached for the rest of the
91
+ session; also where `annotation_level` is read to decide whether to show
92
+ the span-matching criterion/granularity toggles (hidden for document-level
93
+ tasks).
94
+ - **`computeMetrics` / `downloadReport`** — **host-implemented on purpose.**
95
+ The IAA Go service has no CORS or auth handling (see its own README), so
96
+ it's never meant to be called directly from a browser. Hosts proxy it
97
+ through their own backend — see Lawnotation's `/api/iaa/metrics` and
98
+ `/api/iaa/report-zip` server routes for a reference implementation (a thin
99
+ `fetch` pass-through with error normalization).
100
+
101
+ ### `IaaInputData` (the IAA service's input schema)
102
+
103
+ ```json
104
+ {
105
+ "labelset": { "labels": [{ "name": "Actors" }] },
106
+ "documents": [
107
+ {
108
+ "name": "doc_001.txt",
109
+ "full_text": "The full document text.",
110
+ "assignments": [
111
+ {
112
+ "annotator": "alice@example.com",
113
+ "difficulty_rating": 4,
114
+ "annotations": [{ "start": 4, "end": 26, "label": "Actors", "text": "..." }]
115
+ }
116
+ ]
117
+ }
118
+ ],
119
+ "annotation_level": "document"
120
+ }
121
+ ```
122
+
123
+ `annotation_level` is omitted for span-level tasks; `"document"` for
124
+ whole-document tagging (no `start`/`end`, one annotation per label).
125
+
126
+ ### `IaaMetricsResponse` (the IAA service's output schema)
127
+
128
+ See `src/types.ts` for the full shape (`IaaReport` per label —
129
+ `span_matching` + `coverage_agreement` — plus `confidence_metrics`, a
130
+ `DifficultyRatingSummary`). Matches the Go service's `/metrics` response
131
+ verbatim; the package never transforms it, only renders it.
132
+
133
+ ---
134
+
135
+ ## 4. Architecture
136
+
137
+ - **No backend assumptions.** `computeMetrics`/`downloadReport` are the
138
+ host's problem — the package doesn't know or care whether that's a proxy
139
+ route, a direct fetch, or something else.
140
+ - **Self-contained UI.** Own scoped CSS + CSS custom properties
141
+ (`--iaa-accent`, `--iaa-border`, etc.), no PrimeVue/Tailwind/other UI-kit
142
+ dependency — drop it into any Vue 3 app.
143
+ - **The IAA JSON is the contract.** `IaaInputData`/`IaaMetricsResponse` match
144
+ the Go service's schema exactly; hosts map their own data model into/out of
145
+ it (see `RichAnnotation`, `LabelOption`, `DocumentOption` for the pieces the
146
+ UI itself needs).
147
+
148
+ ---
149
+
150
+ ## 5. Development
151
+
152
+ ```bash
153
+ pnpm install
154
+ pnpm dev # serves src/dev/App.vue against a mock MetricsSource
155
+ ```
156
+
157
+ `src/dev/mockSource.ts` proxies `computeMetrics`/`downloadReport` straight to
158
+ `http://localhost:8080`, so run `lawnotation-iaa` locally to exercise real
159
+ computation:
160
+
161
+ ```bash
162
+ cd ../lawnotation-iaa
163
+ go run main.go iaa.go server.go --serve --port 8080
164
+ ```
165
+
166
+ ```bash
167
+ pnpm build # type-checks (vue-tsc) then builds dist/ in library mode
168
+ ```
@@ -0,0 +1,10 @@
1
+ import { RichAnnotation } from '../types';
2
+ type __VLS_Props = {
3
+ annotation: RichAnnotation;
4
+ labelColor: string;
5
+ /** Document-level tasks have no span text to show/collapse. */
6
+ documentLevel: boolean;
7
+ };
8
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,20 @@
1
+ import { LabelOption, RichAnnotation } from '../types';
2
+ type __VLS_Props = {
3
+ annotations: RichAnnotation[];
4
+ labels: LabelOption[];
5
+ loading: boolean;
6
+ documentLevel: boolean;
7
+ };
8
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
9
+ "open-document": (doc: {
10
+ id: string;
11
+ name: string;
12
+ }) => any;
13
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
14
+ "onOpen-document"?: ((doc: {
15
+ id: string;
16
+ name: string;
17
+ }) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,17 @@
1
+ type __VLS_Props = {
2
+ title: string;
3
+ message: string;
4
+ yesLabel?: string;
5
+ noLabel?: string;
6
+ };
7
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
8
+ cancel: () => any;
9
+ yes: () => any;
10
+ no: () => any;
11
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onCancel?: (() => any) | undefined;
13
+ onYes?: (() => any) | undefined;
14
+ onNo?: (() => any) | undefined;
15
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
16
+ declare const _default: typeof __VLS_export;
17
+ export default _default;
@@ -0,0 +1,20 @@
1
+ export type FilterOption = {
2
+ value: string;
3
+ label: string;
4
+ color?: string;
5
+ };
6
+ type __VLS_Props = {
7
+ options: FilterOption[];
8
+ placeholder?: string;
9
+ };
10
+ type __VLS_ModelProps = {
11
+ modelValue: string[];
12
+ };
13
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
14
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
15
+ "update:modelValue": (value: string[]) => any;
16
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
17
+ "onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import { LabelOption } from '../types';
2
+ type __VLS_Props = {
3
+ label: LabelOption;
4
+ };
5
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import { MetricsSource } from '../model/source';
2
+ type __VLS_Props = {
3
+ source: MetricsSource;
4
+ /** Filename used when saving a downloaded report. */
5
+ reportFilename?: string;
6
+ };
7
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
8
+ error: (message: string) => any;
9
+ "open-document": (doc: {
10
+ id: string;
11
+ name: string;
12
+ }) => any;
13
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
14
+ onError?: ((message: string) => any) | undefined;
15
+ "onOpen-document"?: ((doc: {
16
+ id: string;
17
+ name: string;
18
+ }) => any) | undefined;
19
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
20
+ declare const _default: typeof __VLS_export;
21
+ export default _default;
@@ -0,0 +1,37 @@
1
+ import { DocumentOption, LabelOption } from '../types';
2
+ type __VLS_Props = {
3
+ labelsOptions: LabelOption[];
4
+ documentsOptions: DocumentOption[];
5
+ annotatorsOptions: string[];
6
+ /** Document-level tasks have no span text, so span-matching params don't apply. */
7
+ documentLevel: boolean;
8
+ computingMetrics: boolean;
9
+ downloading: boolean;
10
+ };
11
+ type __VLS_ModelProps = {
12
+ "selectedLabels": string[];
13
+ "selectedDocuments": string[];
14
+ "selectedAnnotators": string[];
15
+ "criterion": "exact" | "contained";
16
+ "granularity": "char" | "word";
17
+ };
18
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
19
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
20
+ "compute-metrics": () => any;
21
+ "download-all": () => any;
22
+ "update:selectedLabels": (value: string[]) => any;
23
+ "update:selectedDocuments": (value: string[]) => any;
24
+ "update:selectedAnnotators": (value: string[]) => any;
25
+ "update:criterion": (value: "exact" | "contained") => any;
26
+ "update:granularity": (value: "char" | "word") => any;
27
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
28
+ "onCompute-metrics"?: (() => any) | undefined;
29
+ "onDownload-all"?: (() => any) | undefined;
30
+ "onUpdate:selectedLabels"?: ((value: string[]) => any) | undefined;
31
+ "onUpdate:selectedDocuments"?: ((value: string[]) => any) | undefined;
32
+ "onUpdate:selectedAnnotators"?: ((value: string[]) => any) | undefined;
33
+ "onUpdate:criterion"?: ((value: "exact" | "contained") => any) | undefined;
34
+ "onUpdate:granularity"?: ((value: "char" | "word") => any) | undefined;
35
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
36
+ declare const _default: typeof __VLS_export;
37
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { IaaReport, LabelOption } from '../types';
2
+ type __VLS_Props = {
3
+ report: IaaReport | undefined;
4
+ labelsOptions: LabelOption[];
5
+ loading: boolean;
6
+ };
7
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import { DifficultyRatingSummary } from '../types';
2
+ type __VLS_Props = {
3
+ results: DifficultyRatingSummary | undefined;
4
+ loading: boolean;
5
+ };
6
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import { IaaMetricsResponse, LabelOption } from '../types';
2
+ type __VLS_Props = {
3
+ metricResults: IaaMetricsResponse | undefined;
4
+ labelsOptions: LabelOption[];
5
+ loading: boolean;
6
+ };
7
+ type __VLS_ModelProps = {
8
+ "visible": boolean;
9
+ };
10
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
11
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
12
+ "update:visible": (value: boolean) => any;
13
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
14
+ "onUpdate:visible"?: ((value: boolean) => any) | undefined;
15
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
16
+ declare const _default: typeof __VLS_export;
17
+ export default _default;
@@ -0,0 +1,6 @@
1
+ type __VLS_Props = {
2
+ size?: number;
3
+ };
4
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
5
+ declare const _default: typeof __VLS_export;
6
+ export default _default;
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ value: number;
3
+ max?: number;
4
+ };
5
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,10 @@
1
+ type __VLS_ModelProps = {
2
+ modelValue: boolean;
3
+ };
4
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
5
+ "update:modelValue": (value: boolean) => any;
6
+ }, string, import('vue').PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
7
+ "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,6 @@
1
+ export { default as MetricsPage } from './components/MetricsPage.vue';
2
+ export * from './types';
3
+ export * from './model/source';
4
+ export { useMetricsPage } from './model/useMetricsPage';
5
+ export { anonymizeAnnotators } from './model/anonymize';
6
+ export { stripAnnotatorPrefix, formatNullableFloat, sortByDocumentAndRange } from './model/format';
@@ -0,0 +1,4 @@
1
+ import { IaaInputData } from '../types';
2
+ /** Replaces each annotator identifier with a sequential number (first-seen
3
+ * order, starting at 1) so a downloaded report doesn't contain real names. */
4
+ export declare function anonymizeAnnotators(input: IaaInputData): IaaInputData;
@@ -0,0 +1,4 @@
1
+ import { NullableFloat, RichAnnotation } from '../types';
2
+ export declare function stripAnnotatorPrefix(annotator: string): string;
3
+ export declare function formatNullableFloat(value: NullableFloat, digits?: number): string;
4
+ export declare function sortByDocumentAndRange(ranges: RichAnnotation[]): void;
@@ -0,0 +1,2 @@
1
+ /** Triggers a browser download for a Blob, without pulling in file-saver. */
2
+ export declare function saveBlob(blob: Blob, filename: string): void;
@@ -0,0 +1,34 @@
1
+ import { DocumentOption, IaaInputData, IaaMetricsResponse, LabelOption, RichAnnotation } from '../types';
2
+ export type AnnotationFilters = {
3
+ labels: string[];
4
+ documents: string[];
5
+ annotators: string[];
6
+ };
7
+ export type IaaParams = {
8
+ criterion: "exact" | "contained";
9
+ granularity: "char" | "word";
10
+ };
11
+ /**
12
+ * The host's data-access contract. The package only ever asks for filter
13
+ * options, a filtered annotation list, the whole-task IAA input, and to
14
+ * compute/download a report — whether those resolve via tRPC + Supabase, a
15
+ * REST API, or an in-memory fixture is entirely up to the implementation.
16
+ *
17
+ * computeMetrics/downloadReport are host-implemented on purpose: the IAA Go
18
+ * service has no CORS or auth handling, so it's never meant to be called
19
+ * directly from the browser — hosts proxy it through their own backend.
20
+ */
21
+ export interface MetricsSource {
22
+ getLabels(): Promise<LabelOption[]> | LabelOption[];
23
+ getAnnotators(): Promise<string[]> | string[];
24
+ getDocuments(): Promise<DocumentOption[]> | DocumentOption[];
25
+ /** The browsable, filtered annotation list for the right-hand panel. */
26
+ getAnnotations(filters: AnnotationFilters): Promise<RichAnnotation[]>;
27
+ /**
28
+ * The whole-task IAA input — always unfiltered, built once and cached by
29
+ * the package for the rest of the session (a refresh drops the cache).
30
+ */
31
+ getIaaInputData(): Promise<IaaInputData>;
32
+ computeMetrics(input: IaaInputData, params: IaaParams): Promise<IaaMetricsResponse>;
33
+ downloadReport(input: IaaInputData, params: IaaParams): Promise<Blob>;
34
+ }
@@ -0,0 +1,65 @@
1
+ import { DocumentOption, IaaMetricsResponse, LabelOption, RichAnnotation } from '../types';
2
+ import { MetricsSource } from './source';
3
+ /**
4
+ * Drives the metrics page's state and IAA orchestration through a
5
+ * MetricsSource. Owns filter/options state, the browsable annotation list,
6
+ * the cached whole-task IAA input, and the Compute Metrics / Download All
7
+ * flows — the view layer only renders what this exposes.
8
+ */
9
+ export declare function useMetricsPage(source: MetricsSource, onError: (message: string) => void): {
10
+ loading: import('vue').ComputedRef<boolean>;
11
+ labelsOptions: import('vue').Ref<{
12
+ name: string;
13
+ color: string;
14
+ }[], LabelOption[] | {
15
+ name: string;
16
+ color: string;
17
+ }[]>;
18
+ documentsOptions: import('vue').Ref<{
19
+ value: string;
20
+ label: string;
21
+ }[], DocumentOption[] | {
22
+ value: string;
23
+ label: string;
24
+ }[]>;
25
+ annotatorsOptions: import('vue').Ref<string[], string[]>;
26
+ selectedLabels: import('vue').Ref<string[], string[]>;
27
+ selectedDocuments: import('vue').Ref<string[], string[]>;
28
+ selectedAnnotators: import('vue').Ref<string[], string[]>;
29
+ criterion: import('vue').Ref<"exact" | "contained", "exact" | "contained">;
30
+ granularity: import('vue').Ref<"char" | "word", "char" | "word">;
31
+ isDocumentLevel: import('vue').ComputedRef<boolean>;
32
+ annotations: import('vue').Ref<{
33
+ start: number;
34
+ end: number;
35
+ text: string;
36
+ label: string;
37
+ annotator: string;
38
+ ann_id: number;
39
+ doc_id: string;
40
+ doc_name?: string | undefined;
41
+ confidence: number;
42
+ metadata?: string | undefined;
43
+ }[], RichAnnotation[] | {
44
+ start: number;
45
+ end: number;
46
+ text: string;
47
+ label: string;
48
+ annotator: string;
49
+ ann_id: number;
50
+ doc_id: string;
51
+ doc_name?: string | undefined;
52
+ confidence: number;
53
+ metadata?: string | undefined;
54
+ }[]>;
55
+ loadingAnnotations: import('vue').Ref<boolean, boolean>;
56
+ metricsModalVisible: import('vue').Ref<boolean, boolean>;
57
+ metricsResult: import('vue').Ref<IaaMetricsResponse | undefined, IaaMetricsResponse | undefined>;
58
+ computingMetrics: import('vue').Ref<boolean, boolean>;
59
+ computeMetrics: () => Promise<void>;
60
+ anonymizeConfirmVisible: import('vue').Ref<boolean, boolean>;
61
+ resolveAnonymizeConfirm: (choice: boolean | undefined) => void;
62
+ downloadReport: (filename?: string) => Promise<void>;
63
+ downloading: import('vue').Ref<boolean, boolean>;
64
+ init: () => Promise<void>;
65
+ };
@@ -0,0 +1,95 @@
1
+ export type IaaAnnotation = {
2
+ start: number;
3
+ end: number;
4
+ label: string;
5
+ text: string;
6
+ };
7
+ export type IaaAssignment = {
8
+ annotator: string;
9
+ difficulty_rating: number;
10
+ annotations: IaaAnnotation[];
11
+ };
12
+ export type IaaDocument = {
13
+ name: string;
14
+ full_text: string;
15
+ assignments: IaaAssignment[];
16
+ };
17
+ export type IaaInputData = {
18
+ labelset: {
19
+ labels: {
20
+ name: string;
21
+ }[];
22
+ };
23
+ documents: IaaDocument[];
24
+ /** Omitted for span-level tasks; "document" for whole-document tagging. */
25
+ annotation_level?: "document";
26
+ };
27
+ export type NullableFloat = number | null;
28
+ export type PairResult = {
29
+ true_positives: number;
30
+ ref_span_count: number;
31
+ sys_span_count: number;
32
+ documents_compared: number;
33
+ precision: NullableFloat;
34
+ recall: NullableFloat;
35
+ f1: NullableFloat;
36
+ };
37
+ export type SpanMatchingSummary = {
38
+ macro_f1: NullableFloat;
39
+ pairs: Record<string, Record<string, PairResult>> | null;
40
+ };
41
+ export type CoverageAgreement = {
42
+ matrix_items: number;
43
+ krippendorff_alpha: NullableFloat;
44
+ cohen_kappa_pairs: Record<string, NullableFloat>;
45
+ };
46
+ export type LabelResult = {
47
+ span_counts_per_annotator: Record<string, number>;
48
+ span_matching: SpanMatchingSummary;
49
+ coverage_agreement: CoverageAgreement;
50
+ };
51
+ export type IaaMeta = {
52
+ input_file: string;
53
+ annotation_level: string;
54
+ criterion: string;
55
+ granularity: string;
56
+ annotators: string[];
57
+ num_documents: number;
58
+ notes: Record<string, string>;
59
+ };
60
+ export type IaaReport = {
61
+ meta: IaaMeta;
62
+ per_label: Record<string, LabelResult>;
63
+ };
64
+ export type DifficultyRatingSummary = {
65
+ total: number;
66
+ rated: number;
67
+ mean: NullableFloat;
68
+ counts: Record<string, number>;
69
+ krippendorff_alpha: NullableFloat;
70
+ krippendorff_alpha_pairs: Record<string, NullableFloat>;
71
+ };
72
+ export type IaaMetricsResponse = {
73
+ annotation_metrics: IaaReport;
74
+ confidence_metrics: DifficultyRatingSummary;
75
+ };
76
+ export type RichAnnotation = {
77
+ start: number;
78
+ end: number;
79
+ text: string;
80
+ label: string;
81
+ annotator: string;
82
+ ann_id: number;
83
+ doc_id: string;
84
+ doc_name?: string;
85
+ confidence: number;
86
+ metadata?: string;
87
+ };
88
+ export type LabelOption = {
89
+ name: string;
90
+ color: string;
91
+ };
92
+ export type DocumentOption = {
93
+ value: string;
94
+ label: string;
95
+ };
@@ -0,0 +1 @@
1
+ .filter-select[data-v-d4862aa0]{position:relative;width:100%}.filter-select__control[data-v-d4862aa0]{display:flex;align-items:center;gap:.4rem;width:100%;font:inherit;font-size:.85rem;padding:.4rem .55rem;border:1px solid var(--iaa-border, rgba(0, 0, 0, .15));border-radius:6px;background:#fff;cursor:pointer;text-align:left}.filter-select__summary[data-v-d4862aa0]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-select__summary--placeholder[data-v-d4862aa0]{color:var(--iaa-muted, #6b7280)}.filter-select__caret[data-v-d4862aa0]{color:var(--iaa-muted, #6b7280);font-size:.7rem}.filter-select__swatch[data-v-d4862aa0]{width:.7rem;height:.7rem;border-radius:3px;flex-shrink:0}.filter-select__panel[data-v-d4862aa0]{position:absolute;top:calc(100% + .25rem);left:0;right:0;z-index:20;background:#fff;border:1px solid var(--iaa-border, rgba(0, 0, 0, .15));border-radius:6px;box-shadow:0 6px 20px #00000026;max-height:16rem;display:flex;flex-direction:column}.filter-select__filter[data-v-d4862aa0]{font:inherit;font-size:.82rem;padding:.4rem .55rem;border:none;border-bottom:1px solid var(--iaa-border, rgba(0, 0, 0, .15))}.filter-select__filter[data-v-d4862aa0]:focus{outline:none}.filter-select__options[data-v-d4862aa0]{list-style:none;margin:0;padding:.25rem;overflow-y:auto}.filter-select__option[data-v-d4862aa0]{display:flex;align-items:center;gap:.4rem;padding:.3rem .4rem;border-radius:4px;cursor:pointer;font-size:.82rem}.filter-select__option[data-v-d4862aa0]:hover{background:#0000000d}.filter-select__option-label[data-v-d4862aa0]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.filter-select__empty[data-v-d4862aa0]{padding:.5rem;font-size:.8rem;color:var(--iaa-muted, #6b7280)}.toggle[data-v-56d850bf]{position:relative;width:2.2rem;height:1.2rem;border-radius:999px;border:none;background:var(--iaa-border, rgba(0, 0, 0, .15));cursor:pointer;padding:0;flex-shrink:0;transition:background .15s ease}.toggle--on[data-v-56d850bf]{background:var(--iaa-accent, #1e4e79)}.toggle__knob[data-v-56d850bf]{position:absolute;top:2px;left:2px;width:1rem;height:1rem;border-radius:50%;background:#fff;box-shadow:0 1px 2px #00000059;transition:transform .15s ease}.toggle--on .toggle__knob[data-v-56d850bf]{transform:translate(1rem)}.params[data-v-72d0b699]{display:flex;flex-direction:column;gap:1rem;font-size:.85rem}.params__title[data-v-72d0b699]{display:flex;align-items:center;gap:.3rem;margin:0 0 .6rem;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--iaa-muted, #6b7280)}.params__info[data-v-72d0b699]{cursor:help;color:var(--iaa-muted, #6b7280);font-size:.8rem}.params__field[data-v-72d0b699]{margin-bottom:.6rem}.params__label[data-v-72d0b699]{display:block;margin-bottom:.3rem;font-size:.82rem;font-weight:600}.params__divider[data-v-72d0b699]{border:none;border-top:1px solid var(--iaa-border, rgba(0, 0, 0, .15));margin:0}.params__toggles[data-v-72d0b699]{margin-bottom:.75rem}.params__toggle-row[data-v-72d0b699]{display:flex;align-items:center;gap:.5rem;margin-bottom:.6rem}.params__toggle-label[data-v-72d0b699]{font-size:.82rem;font-weight:600;min-width:4.2rem}.params__toggle-label--right[data-v-72d0b699]{text-align:right}.params__btn[data-v-72d0b699]{display:block;width:100%;font:inherit;font-size:.85rem;font-weight:600;padding:.5rem;border-radius:6px;border:1px solid var(--iaa-accent, #1e4e79);cursor:pointer;margin-top:.5rem}.params__btn[data-v-72d0b699]:disabled{opacity:.5;cursor:default}.params__btn--primary[data-v-72d0b699]{background:var(--iaa-accent, #1e4e79);color:#fff}.params__btn--outline[data-v-72d0b699]{background:transparent;color:var(--iaa-accent, #1e4e79)}.chip[data-v-a86af93f]{display:inline-block;max-width:100%;padding:.15rem .55rem;border-radius:4px;font-size:.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.stars[data-v-c5894f0c]{display:inline-flex;gap:1px;line-height:1}.star[data-v-c5894f0c]{color:var(--iaa-star-empty, #d2cece);font-size:.9em}.star--on[data-v-c5894f0c]{color:var(--iaa-star-on, #f5b301)}.ann-card[data-v-5bca2986]{border:1px solid var(--iaa-border, rgba(0, 0, 0, .15));border-radius:8px;margin-bottom:.5rem;overflow:hidden}.ann-card__header[data-v-5bca2986]{display:flex;align-items:center;gap:.5rem;width:100%;font:inherit;padding:.5rem .6rem;background:#fff;border:none;text-align:left}.ann-card__header--clickable[data-v-5bca2986]{cursor:pointer}.ann-card__header--clickable[data-v-5bca2986]:hover{background:#00000008}.ann-card__meta[data-v-5bca2986]{display:flex;align-items:center;gap:.4rem;font-size:.75rem;color:var(--iaa-muted, #6b7280);overflow:hidden}.ann-card__metadata[data-v-5bca2986]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ann-card__caret[data-v-5bca2986]{margin-left:auto;color:var(--iaa-muted, #6b7280);font-size:.7rem;flex-shrink:0}.ann-card__not-annotated[data-v-5bca2986]{font-size:.8rem;font-style:italic;color:var(--iaa-muted, #6b7280)}.ann-card__text[data-v-5bca2986]{padding:.1rem .6rem .6rem;white-space:pre-wrap;word-break:break-word;font-size:.85rem}.spinner[data-v-aaa362fe]{display:inline-block;border:3px solid var(--iaa-border, rgba(0, 0, 0, .15));border-top-color:var(--iaa-accent, #1e4e79);border-radius:50%;animation:iaa-spin-aaa362fe .7s linear infinite}@keyframes iaa-spin-aaa362fe{to{transform:rotate(360deg)}}.ann-list[data-v-30603c02]{position:relative;height:100%;overflow-y:auto;padding:.75rem}.ann-list__loading[data-v-30603c02]{display:flex;justify-content:center;padding:3rem 0}.ann-list__count[data-v-30603c02]{font-size:1.1rem;font-weight:700;text-align:center;margin-bottom:.75rem}.ann-list__doc-btn[data-v-30603c02]{display:block;font:inherit;font-size:.85rem;font-weight:600;color:var(--iaa-accent, #1e4e79);background:transparent;border:1px solid var(--iaa-accent, #1e4e79);border-radius:6px;padding:.3rem .6rem;margin:.75rem 0 .4rem;cursor:pointer}.ann-list__doc-btn[data-v-30603c02]:hover{background:color-mix(in srgb,var(--iaa-accent, #1e4e79) 8%,transparent)}.ann-list__empty[data-v-30603c02]{text-align:center;color:var(--iaa-muted, #6b7280);padding:2rem 0}.ann-list__sentinel[data-v-30603c02]{height:1px}.ann-list__up[data-v-30603c02]{position:sticky;bottom:.75rem;left:100%;display:flex;align-items:center;justify-content:center;width:2.2rem;height:2.2rem;border-radius:999px;background:var(--iaa-accent, #1e4e79);color:#fff;text-decoration:none;box-shadow:0 2px 8px #00000040}.results-agreement__empty[data-v-905e9258]{text-align:center;color:var(--iaa-muted, #6b7280);padding:2.5rem 0}.results-agreement__picker[data-v-905e9258]{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.results-agreement__picker-label[data-v-905e9258]{font-size:.85rem;font-weight:600}.results-agreement__select[data-v-905e9258]{font:inherit;font-size:.85rem;padding:.35rem .5rem;border:1px solid var(--iaa-border, rgba(0, 0, 0, .15));border-radius:6px;width:65%}.results-agreement__sections[data-v-905e9258]{display:flex;flex-direction:column;gap:1.5rem}.results-agreement__heading[data-v-905e9258]{font-size:.85rem;font-weight:700;margin:0 0 .5rem}.results-agreement__subtle[data-v-905e9258]{font-weight:400;color:var(--iaa-muted, #6b7280)}.results-agreement__table[data-v-905e9258]{width:100%;border-collapse:collapse;font-size:.82rem;text-align:left}.results-agreement__table th[data-v-905e9258],.results-agreement__table td[data-v-905e9258]{padding:.4rem .6rem;border-bottom:1px solid var(--iaa-border, rgba(0, 0, 0, .15))}.results-agreement__table thead th[data-v-905e9258]{font-size:.72rem;text-transform:uppercase;color:var(--iaa-muted, #6b7280);background:#00000008}.results-agreement__center[data-v-905e9258]{text-align:center}.results-agreement__note[data-v-905e9258]{font-size:.72rem;color:var(--iaa-muted, #6b7280)}.results-agreement__footnote[data-v-905e9258]{font-size:.75rem;font-style:italic;color:var(--iaa-muted, #6b7280);margin:.5rem 0 0}.results-confidence[data-v-b81d6b67]{font-size:.85rem}.results-confidence__summary[data-v-b81d6b67]{display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem}.results-confidence__average[data-v-b81d6b67]{display:flex;align-items:center;gap:.5rem}.results-confidence__count[data-v-b81d6b67]{color:var(--iaa-muted, #6b7280)}.results-confidence__subtle[data-v-b81d6b67]{color:var(--iaa-muted, #6b7280);font-size:.8rem}.results-confidence__bar-row[data-v-b81d6b67]{display:flex;align-items:center;gap:.6rem;margin:.5rem 0}.results-confidence__bar-label[data-v-b81d6b67]{width:2rem;flex-shrink:0}.results-confidence__bar-track[data-v-b81d6b67]{flex:1;height:.9rem;background:var(--iaa-progress-track, #e2e8f0);border-radius:999px;overflow:hidden}.results-confidence__bar-fill[data-v-b81d6b67]{height:100%;background:var(--iaa-star-on, #f5b301)}.results-confidence__alpha[data-v-b81d6b67]{text-align:center;margin-top:1.25rem}.results-confidence__alpha-total[data-v-b81d6b67]{font-weight:700}.results-confidence__table[data-v-b81d6b67]{width:100%;border-collapse:collapse;margin-top:.5rem;text-align:left}.results-confidence__table th[data-v-b81d6b67],.results-confidence__table td[data-v-b81d6b67]{padding:.4rem .6rem;border-bottom:1px solid var(--iaa-border, rgba(0, 0, 0, .15))}.results-confidence__center[data-v-b81d6b67]{text-align:center}.results-modal__overlay[data-v-1ad42970]{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:1000;font-family:system-ui,sans-serif}.results-modal[data-v-1ad42970]{background:#fff;border-radius:10px;width:min(640px,calc(100% - 2rem));max-height:calc(100vh - 4rem);display:flex;flex-direction:column;box-shadow:0 10px 40px #00000040}.results-modal__header[data-v-1ad42970]{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--iaa-border, rgba(0, 0, 0, .15))}.results-modal__title[data-v-1ad42970]{margin:0;font-size:1.05rem}.results-modal__close[data-v-1ad42970]{border:none;background:none;font-size:1.4rem;line-height:1;cursor:pointer;color:var(--iaa-muted, #6b7280)}.results-modal__loading[data-v-1ad42970]{min-height:26rem;display:flex;align-items:center;justify-content:center}.results-modal__tabs[data-v-1ad42970]{display:flex;gap:.5rem;padding:0 1.25rem;border-bottom:1px solid var(--iaa-border, rgba(0, 0, 0, .15))}.results-modal__tab[data-v-1ad42970]{font:inherit;font-size:.85rem;font-weight:600;padding:.7rem .4rem;border:none;border-bottom:2px solid transparent;background:none;cursor:pointer;color:var(--iaa-muted, #6b7280)}.results-modal__tab--active[data-v-1ad42970]{color:var(--iaa-accent, #1e4e79);border-bottom-color:var(--iaa-accent, #1e4e79)}.results-modal__panel[data-v-1ad42970]{padding:1.25rem;overflow-y:auto;min-height:26rem}.confirm-modal__overlay[data-v-b29c6ef7]{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:1000;font-family:system-ui,sans-serif}.confirm-modal[data-v-b29c6ef7]{background:#fff;border-radius:10px;padding:1.25rem 1.5rem;max-width:24rem;width:calc(100% - 2rem);box-shadow:0 10px 40px #00000040}.confirm-modal__title[data-v-b29c6ef7]{margin:0 0 .5rem;font-size:1rem;color:#111}.confirm-modal__message[data-v-b29c6ef7]{margin:0 0 1.25rem;font-size:.88rem;color:#4b5563;line-height:1.4;white-space:pre-line}.confirm-modal__actions[data-v-b29c6ef7]{display:flex;justify-content:flex-end;gap:.5rem}.confirm-modal__btn[data-v-b29c6ef7]{font:inherit;font-size:.85rem;cursor:pointer;border-radius:6px;padding:.4rem .9rem;border:1px solid transparent}.confirm-modal__btn--no[data-v-b29c6ef7]{background:transparent;border-color:#d1d5db;color:#374151}.confirm-modal__btn--yes[data-v-b29c6ef7]{background:var(--iaa-accent, #1e4e79);color:#fff}.metrics-page[data-v-b8704e29]{--iaa-accent: #1e4e79;--iaa-success: #16a34a;--iaa-danger: #dc2626;--iaa-border: rgba(0, 0, 0, .12);--iaa-muted: #6b7280;--iaa-star-empty: #d2cece;--iaa-star-on: #f5b301;--iaa-progress-track: #e2e8f0;display:flex;flex-direction:column;height:100%;min-height:0;font-family:system-ui,sans-serif;color:#111;background:var(--iaa-bg, #fff)}.metrics-page__body[data-v-b8704e29]{display:grid;grid-template-columns:280px 1fr;flex:1;min-height:0}.metrics-page__sidebar[data-v-b8704e29]{border-right:1px solid var(--iaa-border);padding:.75rem;overflow-y:auto}.metrics-page__main[data-v-b8704e29]{position:relative;min-height:0}.metrics-page__dimmer[data-v-b8704e29]{position:absolute;top:0;right:0;bottom:0;left:0;background:#ffffffa6;display:flex;align-items:center;justify-content:center}