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 +168 -0
- package/dist/components/AnnotationCard.vue.d.ts +10 -0
- package/dist/components/AnnotationsList.vue.d.ts +20 -0
- package/dist/components/ConfirmModal.vue.d.ts +17 -0
- package/dist/components/FilterSelect.vue.d.ts +20 -0
- package/dist/components/LabelChip.vue.d.ts +7 -0
- package/dist/components/MetricsPage.vue.d.ts +21 -0
- package/dist/components/ParametersColumn.vue.d.ts +37 -0
- package/dist/components/ResultsAgreement.vue.d.ts +9 -0
- package/dist/components/ResultsConfidence.vue.d.ts +8 -0
- package/dist/components/ResultsModal.vue.d.ts +17 -0
- package/dist/components/Spinner.vue.d.ts +6 -0
- package/dist/components/StarRating.vue.d.ts +7 -0
- package/dist/components/Toggle.vue.d.ts +10 -0
- package/dist/index.d.ts +6 -0
- package/dist/model/anonymize.d.ts +4 -0
- package/dist/model/format.d.ts +4 -0
- package/dist/model/saveBlob.d.ts +2 -0
- package/dist/model/source.d.ts +34 -0
- package/dist/model/useMetricsPage.d.ts +65 -0
- package/dist/types.d.ts +95 -0
- package/dist/vue-iaa-metrics.css +1 -0
- package/dist/vue-iaa-metrics.js +922 -0
- package/dist/vue-iaa-metrics.umd.cjs +1 -0
- package/package.json +37 -0
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;
|
package/dist/index.d.ts
ADDED
|
@@ -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,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
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -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}
|