Make a bias variant recognisable, and quieten the run history
Nothing in the run history said whether a run was a bias variant. Worse, the
viewer header claimed "Bias variant" for *every* execution: it tested for the
presence of biasExecutionContext, which the backend sets on all of them,
defaulting to NORMAL. Only the mode distinguishes them. The same faulty test also
offered the bias comparison action on any plain rerun.
Each history row now carries a kind badge - Run, Rerun, Bias, Mitigation, or Bias
+ mitigation - coloured and with a tooltip saying what it means for the result. A
bias variant reads as a variant even when it is also a rerun, because carrying
probes is what changes how its output should be read.
The rows also stop showing raw uuids. A rerun names the run it came from by
number ("from #1") instead of repeating a 36-character id, the execution id is
gone from the row entirely - the viewer header owns it - and the group header no
longer prints the source flow id. "1 runs" reads "1 run".
The viewer header keeps only what changes how a result should be read: Simulated,
the bias variant, Subflow. Execution id, simulator descriptor, experiment id,
baseline and probe internals moved behind a Details toggle, closed by default.
Two Italian strings in that block are now English, like the rest of the app.
Cards are flat here too, matching the flows list: no gradients, no lift on hover,
a left accent for the selected run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f086064e61
commit
ad8a65fb18
|
|
@ -241,4 +241,55 @@ describe('TasksExecutor', () => {
|
|||
}));
|
||||
expect(normalizeExecutionStatus('WAITING_FOR_SUBFLOW')).toBe('WAITING');
|
||||
});
|
||||
|
||||
it('tells a bias variant apart from a plain rerun', () => {
|
||||
// The backend sets a bias context on every execution, defaulting to NORMAL, so presence alone
|
||||
// marked everything a variant. Only the mode distinguishes them.
|
||||
const plain = {
|
||||
id: 'e1', name: 'Plain', creationTime: 1, runNumber: 1,
|
||||
biasExecutionContext: { mode: 'NORMAL', experimentId: '', activeAnnotationIdsByNode: {} },
|
||||
context: { inputs: {}, result: {}, errors: {}, warnings: {}, status: 'SUCCESS', waitingSteps: [], steps: {} }
|
||||
};
|
||||
const rerun = { ...plain, id: 'e2', name: 'Rerun', runNumber: 2, rerunOfExecutionId: 'e1' };
|
||||
const variant = {
|
||||
...plain, id: 'e3', name: 'Variant', runNumber: 3, rerunOfExecutionId: 'e1',
|
||||
biasExecutionContext: {
|
||||
mode: 'BIAS_VARIANT', experimentId: 'x', activeAnnotationIdsByNode: {},
|
||||
activeBiasProbes: [{ annotationId: 'a1', direction: 'BIAS', activationMode: 'PROMPT_DIRECTIVE' }]
|
||||
}
|
||||
};
|
||||
|
||||
taskExecutions.set([]);
|
||||
const group = {
|
||||
id: 'g1', sourceFlowId: 'f1', name: 'Group', firstExecutionId: 'e1', latestExecutionId: 'e3',
|
||||
creationTime: 1, lastExecutionTime: 3, executionCount: 3,
|
||||
executions: [plain, rerun, variant]
|
||||
} as any;
|
||||
|
||||
const rows = (component as any).toGroupListItem(group).executions;
|
||||
|
||||
expect(rows.map((row: any) => row.kind)).toEqual(['RUN', 'RERUN', 'BIAS_VARIANT']);
|
||||
expect(rows[2].biasDirection).toBe('BIAS');
|
||||
// A rerun names the run it came from by number, not by uuid.
|
||||
expect(rows[1].rerunOfRunNumber).toBe(1);
|
||||
expect(rows[0].rerunOfRunNumber).toBeNull();
|
||||
});
|
||||
|
||||
it('reports a mitigation-only variant as such', () => {
|
||||
const variant = {
|
||||
id: 'e1', name: 'Mitigated', creationTime: 1, runNumber: 1,
|
||||
biasExecutionContext: {
|
||||
mode: 'BIAS_VARIANT', experimentId: 'x', activeAnnotationIdsByNode: {},
|
||||
activeBiasProbes: [{ annotationId: 'a1', direction: 'MITIGATION', activationMode: 'PROMPT_DIRECTIVE' }]
|
||||
},
|
||||
context: { inputs: {}, result: {}, errors: {}, warnings: {}, status: 'SUCCESS', waitingSteps: [], steps: {} }
|
||||
};
|
||||
|
||||
const rows = (component as any).toGroupListItem({
|
||||
id: 'g1', sourceFlowId: 'f1', name: 'G', firstExecutionId: 'e1', latestExecutionId: 'e1',
|
||||
creationTime: 1, lastExecutionTime: 1, executionCount: 1, executions: [variant]
|
||||
} as any).executions;
|
||||
|
||||
expect(rows[0].biasDirection).toBe('MITIGATION');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -295,8 +295,11 @@ export class TasksExecutor {
|
|||
}
|
||||
|
||||
private toGroupListItem(group: TaskExecutionGroup): TaskExecutionGroupListItem {
|
||||
// Resolved up front so a rerun can name the run it came from by number instead of by uuid.
|
||||
const runNumbers = new Map((group.executions ?? []).map((execution, index) =>
|
||||
[execution.id, typeof execution.runNumber === 'number' ? execution.runNumber : index + 1]));
|
||||
const executions = (group.executions ?? []).map((execution, index) =>
|
||||
this.toExecutionListItem(execution, index + 1)
|
||||
this.toExecutionListItem(execution, index + 1, runNumbers)
|
||||
);
|
||||
const latestExecution = executions.find((execution) => execution.id === group.latestExecutionId)
|
||||
?? executions[executions.length - 1]
|
||||
|
|
@ -325,7 +328,13 @@ export class TasksExecutor {
|
|||
return this.projectsService.projectById().get(flow.projectId)?.name ?? flow.projectName ?? null;
|
||||
}
|
||||
|
||||
private toExecutionListItem(execution: TaskExecution, fallbackRunNumber: number): TaskExecutionListItem {
|
||||
private toExecutionListItem(execution: TaskExecution, fallbackRunNumber: number,
|
||||
runNumbers: Map<string, number> = new Map()): TaskExecutionListItem {
|
||||
const bias = execution.biasExecutionContext;
|
||||
const isBiasVariant = bias?.mode === 'BIAS_VARIANT';
|
||||
const directions = new Set((bias?.activeBiasProbes ?? []).map((probe) => probe.direction));
|
||||
const rerunOf = execution.rerunOfExecutionId ?? null;
|
||||
|
||||
return {
|
||||
id: execution.id,
|
||||
title: execution.name,
|
||||
|
|
@ -334,7 +343,16 @@ export class TasksExecutor {
|
|||
startedAt: this.formatDateTime(execution.creationTime),
|
||||
creationTime: execution.creationTime,
|
||||
runNumber: typeof execution.runNumber === 'number' ? execution.runNumber : fallbackRunNumber,
|
||||
rerunOfExecutionId: execution.rerunOfExecutionId ?? null,
|
||||
rerunOfExecutionId: rerunOf,
|
||||
rerunOfRunNumber: rerunOf ? runNumbers.get(rerunOf) ?? null : null,
|
||||
// A bias variant is reported as such even when it is also a rerun: that it carries probes is
|
||||
// the thing that changes how its result should be read.
|
||||
kind: isBiasVariant ? 'BIAS_VARIANT' : (rerunOf ? 'RERUN' : 'RUN'),
|
||||
biasDirection: !isBiasVariant
|
||||
? null
|
||||
: directions.size > 1
|
||||
? 'MIXED'
|
||||
: directions.has('MITIGATION') ? 'MITIGATION' : 'BIAS',
|
||||
duration: this.formatExecutionDuration(execution.context.startTime ?? null, execution.context.endTime ?? null),
|
||||
simulated: execution.interactionSimulationEnabled === true
|
||||
};
|
||||
|
|
|
|||
|
|
@ -616,3 +616,73 @@
|
|||
line-height: 1.45;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only what changes how a result should be read stays in the header; ids, simulator descriptors
|
||||
* and probe internals are diagnostics and live behind Details.
|
||||
*/
|
||||
.execution-header-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
|
||||
.execution-badge {
|
||||
padding: 0 0.375rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.execution-badge-simulated {
|
||||
background: #e0f2fe;
|
||||
color: #075985;
|
||||
}
|
||||
|
||||
.execution-badge-bias {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.execution-badge-subflow {
|
||||
background: #ede9fe;
|
||||
color: #6d28d9;
|
||||
}
|
||||
|
||||
.execution-header-details-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.125rem;
|
||||
margin-left: auto;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.execution-header-details-toggle:hover {
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.execution-header-details-toggle .mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.execution-header-detail-list {
|
||||
margin-top: 0.25rem;
|
||||
padding-top: 0.25rem;
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,34 +15,66 @@
|
|||
<mat-icon [fontIcon]="isFullscreen() ? 'fullscreen_exit' : 'fullscreen'"></mat-icon>
|
||||
</button>
|
||||
</h2>
|
||||
<p class="text-sm text-slate-500">Execution ID: {{ execution()!.id }}</p>
|
||||
@if (isSubflowExecution()) {
|
||||
<div class="execution-subflow-context">
|
||||
<strong>Interactive container subflow</strong>
|
||||
<span>Parent: {{ parentExecution()?.name || execution()!.parentExecutionId || 'Unknown' }}</span>
|
||||
<span>Container: {{ parentContainerStep()?.node?.name || execution()!.parentStepId || 'Unknown' }}</span>
|
||||
@if (subflowIterationIndex(); as iterationIndex) {
|
||||
<span>Iteration {{ iterationIndex }}</span>
|
||||
<div class="execution-header-badges">
|
||||
@if (isSimulatedExecution()) {
|
||||
<span class="execution-badge execution-badge-simulated"
|
||||
matTooltip="Interactions were answered by a simulator, not by a person.">
|
||||
Simulated
|
||||
</span>
|
||||
}
|
||||
@if (execution()!.subflowRole) {
|
||||
<span>Role: {{ execution()!.subflowRole }}</span>
|
||||
@if (isBiasVariant()) {
|
||||
<span class="execution-badge execution-badge-bias"
|
||||
matTooltip="Bias or mitigation probes were active, so this result is not a baseline.">
|
||||
{{ biasVariantLabel() }}
|
||||
</span>
|
||||
}
|
||||
@if (isSubflowExecution()) {
|
||||
<span class="execution-badge execution-badge-subflow"
|
||||
matTooltip="This run is one iteration of a container subflow.">
|
||||
Subflow
|
||||
</span>
|
||||
}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="execution-header-details-toggle"
|
||||
[attr.aria-expanded]="headerDetailsOpen()"
|
||||
(click)="toggleHeaderDetails()">
|
||||
<mat-icon [fontIcon]="headerDetailsOpen() ? 'expand_less' : 'expand_more'"></mat-icon>
|
||||
<span>Details</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@if (isSimulatedExecution()) {
|
||||
<p class="text-sm text-sky-700">Simulated interactive execution</p>
|
||||
@if (simulationDescriptorLabel(); as simulationDescriptor) {
|
||||
<p class="text-xs text-sky-900">Simulator: {{ simulationDescriptor }}</p>
|
||||
}
|
||||
}
|
||||
@if (execution()!.biasExecutionContext; as biasContext) {
|
||||
<div class="execution-bias-variant-context">
|
||||
<strong>Bias variant</strong>
|
||||
<span>Experiment: {{ biasContext.experimentId }}</span>
|
||||
<span>Baseline: {{ execution()!.rerunOfExecutionId || 'not available' }}</span>
|
||||
<span>Active annotations: {{ biasContext.activeAnnotationIdsByNode | json }}</span>
|
||||
@for (probe of biasContext.activeBiasProbes ?? []; track probe.annotationId + probe.direction) {
|
||||
<span>{{ probe.direction === 'BIAS' ? 'Bias applicato' : 'Mitigazione applicata' }}: {{ probe.annotationId }}</span>
|
||||
|
||||
@if (headerDetailsOpen()) {
|
||||
<div class="execution-header-detail-list">
|
||||
<p class="text-sm text-slate-500">Execution ID: {{ execution()!.id }}</p>
|
||||
|
||||
@if (isSubflowExecution()) {
|
||||
<div class="execution-subflow-context">
|
||||
<strong>Interactive container subflow</strong>
|
||||
<span>Parent: {{ parentExecution()?.name || execution()!.parentExecutionId || 'Unknown' }}</span>
|
||||
<span>Container: {{ parentContainerStep()?.node?.name || execution()!.parentStepId || 'Unknown' }}</span>
|
||||
@if (subflowIterationIndex(); as iterationIndex) {
|
||||
<span>Iteration {{ iterationIndex }}</span>
|
||||
}
|
||||
@if (execution()!.subflowRole) {
|
||||
<span>Role: {{ execution()!.subflowRole }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (isSimulatedExecution() && simulationDescriptorLabel(); as simulationDescriptor) {
|
||||
<p class="text-xs text-sky-900">Simulator: {{ simulationDescriptor }}</p>
|
||||
}
|
||||
|
||||
@if (isBiasVariant() && execution()!.biasExecutionContext; as biasContext) {
|
||||
<div class="execution-bias-variant-context">
|
||||
<span>Experiment: {{ biasContext.experimentId || 'not available' }}</span>
|
||||
<span>Baseline: {{ execution()!.rerunOfExecutionId || 'not available' }}</span>
|
||||
@for (probe of biasContext.activeBiasProbes ?? []; track probe.annotationId + probe.direction) {
|
||||
<span>{{ probe.direction === 'BIAS' ? 'Bias applied' : 'Mitigation applied' }}: {{ probe.annotationId }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -546,6 +546,21 @@ export class TaskExecutionViewerComponent implements OnDestroy {
|
|||
*/
|
||||
readonly outcomesOpen = signal(true);
|
||||
|
||||
/** Ids, simulator descriptors and probe internals are diagnostics: useful, but not the headline. */
|
||||
readonly headerDetailsOpen = signal(false);
|
||||
|
||||
readonly biasVariantLabel = computed(() => {
|
||||
const directions = new Set((this.execution()?.biasExecutionContext?.activeBiasProbes ?? [])
|
||||
.map((probe) => probe.direction));
|
||||
if (directions.size > 1) return 'Bias + mitigation';
|
||||
if (directions.has('MITIGATION')) return 'Mitigation variant';
|
||||
return 'Bias variant';
|
||||
});
|
||||
|
||||
toggleHeaderDetails() {
|
||||
this.headerDetailsOpen.update((open) => !open);
|
||||
}
|
||||
|
||||
/** Kept in the header so the conclusion is still readable while collapsed. */
|
||||
readonly outcomeCodes = computed(() => this.outcomes().map((outcome) => outcome.code).join(', '));
|
||||
|
||||
|
|
@ -639,7 +654,13 @@ export class TaskExecutionViewerComponent implements OnDestroy {
|
|||
|
||||
readonly isSubflowExecution = computed(() => this.execution()?.executionKind === 'SUBFLOW');
|
||||
readonly isSimulatedExecution = computed(() => this.execution()?.interactionSimulationEnabled === true);
|
||||
readonly isBiasVariant = computed(() => !!this.execution()?.biasExecutionContext);
|
||||
/**
|
||||
* The backend sets a bias context on *every* execution, defaulting to NORMAL, so the presence of
|
||||
* the object says nothing - only the mode does. Testing for presence marked every run a bias
|
||||
* variant, and also offered the bias comparison on any plain rerun.
|
||||
*/
|
||||
readonly isBiasVariant = computed(() =>
|
||||
this.execution()?.biasExecutionContext?.mode === 'BIAS_VARIANT');
|
||||
readonly canCreateBiasedRerun = computed(() =>
|
||||
!this.isSubflowExecution()
|
||||
&& getExecutionStatusGroup(this.execution()?.context.status) === 'FINAL'
|
||||
|
|
|
|||
|
|
@ -66,30 +66,27 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
/* Flat, like the flows list: a scrolling history should not be a stack of raised, tinted panels. */
|
||||
.tasks-list-group {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 8px;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(16, 185, 129, 0.1), transparent 32%),
|
||||
linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||
box-shadow:
|
||||
0 10px 24px rgba(15, 23, 42, 0.07),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.72);
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
box-shadow: none;
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
transition: transform 0.15s ease, border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
|
||||
transition: border-color 0.12s ease, background-color 0.12s ease;
|
||||
}
|
||||
|
||||
.tasks-list-group:hover {
|
||||
border-color: #cbd5e1;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.tasks-list-group:hover,
|
||||
.tasks-list-group-expanded {
|
||||
transform: translateY(-3px);
|
||||
border-color: #60a5fa;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(96, 165, 250, 0.16), transparent 34%),
|
||||
linear-gradient(180deg, #f8fbff 0%, #dbeafe 100%);
|
||||
box-shadow: 0 18px 28px rgba(37, 99, 235, 0.14);
|
||||
border-color: #cbd5e1;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.tasks-list-group-header {
|
||||
|
|
@ -199,23 +196,24 @@
|
|||
|
||||
.tasks-list-execution {
|
||||
position: relative;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border: 1px solid #eef2f6;
|
||||
border-left: 3px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 7px 8px;
|
||||
background: #ffffff;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
|
||||
transition: border-color 0.12s ease, background-color 0.12s ease;
|
||||
}
|
||||
|
||||
.tasks-list-execution:hover {
|
||||
border-color: #93c5fd;
|
||||
background: #f8fbff;
|
||||
box-shadow: 0 10px 18px rgba(37, 99, 235, 0.1);
|
||||
border-color: #cbd5e1;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.tasks-list-execution-selected {
|
||||
border-color: #2563eb;
|
||||
background: #eff6ff;
|
||||
border-color: #cbd5e1;
|
||||
border-left-color: #2563eb;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
.tasks-list-execution-head {
|
||||
|
|
@ -334,3 +332,38 @@
|
|||
color: #ffffff;
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* A bias variant had probes activated, so its result is not a baseline: it must be distinguishable
|
||||
* from a plain rerun at a glance, which the raw uuid it used to show never achieved.
|
||||
*/
|
||||
.tasks-list-kind {
|
||||
padding: 0 0.375rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
white-space: nowrap;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.tasks-list-kind-run {
|
||||
background: #e2e8f0;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.tasks-list-kind-rerun {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tasks-list-kind-bias {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.tasks-list-kind-mitigation {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,11 +53,10 @@
|
|||
@if (group.projectName) {
|
||||
<span class="tasks-list-project-chip">{{ group.projectName }}</span>
|
||||
}
|
||||
<span>{{ group.sourceFlowId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tasks-list-group-summary">
|
||||
<span class="tasks-list-count">{{ group.executionCount }} runs</span>
|
||||
<span class="tasks-list-count">{{ runCountLabel(group.executionCount) }}</span>
|
||||
<span class="tasks-list-status" [ngClass]="statusBadgeClass(group.latestStatus)">
|
||||
{{ group.latestStatus }}
|
||||
</span>
|
||||
|
|
@ -79,7 +78,12 @@
|
|||
<div class="tasks-list-execution-head">
|
||||
<div class="tasks-list-execution-title">
|
||||
<span class="tasks-list-run">{{ runNumberLabel(execution.runNumber) }}</span>
|
||||
<span class="tasks-list-execution-id">{{ execution.id }}</span>
|
||||
<span
|
||||
class="tasks-list-kind"
|
||||
[ngClass]="kindBadgeClass(execution)"
|
||||
[matTooltip]="kindTooltip(execution)">
|
||||
{{ kindLabel(execution) }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="tasks-list-status" [ngClass]="statusBadgeClass(execution.status)">
|
||||
{{ execution.status }}
|
||||
|
|
@ -87,8 +91,10 @@
|
|||
</div>
|
||||
<div class="tasks-list-execution-details">
|
||||
<span>{{ execution.startedAt }}</span>
|
||||
@if (execution.rerunOfExecutionId) {
|
||||
<span>Rerun of {{ execution.rerunOfExecutionId }}</span>
|
||||
@if (execution.rerunOfRunNumber) {
|
||||
<span>from {{ runNumberLabel(execution.rerunOfRunNumber) }}</span>
|
||||
} @else if (execution.rerunOfExecutionId) {
|
||||
<span>rerun</span>
|
||||
}
|
||||
@if (execution.simulated) {
|
||||
<span class="tasks-list-simulated-badge">Simulated</span>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import { OrderViewState } from '@utilities/list-state-holder';
|
|||
|
||||
export type TaskExecutionFilter = 'all' | TaskExecutionStatusGroup;
|
||||
|
||||
/** What kind of run a row is, so a bias variant is never mistaken for an ordinary rerun. */
|
||||
export type TaskExecutionKind = 'RUN' | 'RERUN' | 'BIAS_VARIANT';
|
||||
|
||||
export type TaskExecutionListItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
|
|
@ -24,6 +27,11 @@ export type TaskExecutionListItem = {
|
|||
creationTime: number;
|
||||
runNumber: number | null;
|
||||
rerunOfExecutionId?: string | null;
|
||||
/** The run number this one reruns - far more readable in a narrow list than its uuid. */
|
||||
rerunOfRunNumber?: number | null;
|
||||
kind: TaskExecutionKind;
|
||||
/** Which way the bias probes point; MIXED when a variant activates both at once. */
|
||||
biasDirection?: 'BIAS' | 'MITIGATION' | 'MIXED' | null;
|
||||
duration?: string;
|
||||
simulated?: boolean;
|
||||
};
|
||||
|
|
@ -71,6 +79,35 @@ export class TasksExecutionsListComponent {
|
|||
|
||||
readonly activeFilterCount = computed(() => (this.filter() === 'all' ? 0 : 1));
|
||||
|
||||
kindLabel(execution: TaskExecutionListItem): string {
|
||||
if (execution.kind === 'BIAS_VARIANT') {
|
||||
return execution.biasDirection === 'MITIGATION'
|
||||
? 'Mitigation'
|
||||
: execution.biasDirection === 'MIXED' ? 'Bias + mitigation' : 'Bias';
|
||||
}
|
||||
return execution.kind === 'RERUN' ? 'Rerun' : 'Run';
|
||||
}
|
||||
|
||||
kindTooltip(execution: TaskExecutionListItem): string {
|
||||
if (execution.kind === 'BIAS_VARIANT') {
|
||||
return 'A bias variant: this run had bias or mitigation probes activated, so its result is not a baseline.';
|
||||
}
|
||||
return execution.kind === 'RERUN' ? 'A plain rerun of an earlier execution.' : 'An original run.';
|
||||
}
|
||||
|
||||
kindBadgeClass(execution: TaskExecutionListItem): string {
|
||||
if (execution.kind === 'BIAS_VARIANT') {
|
||||
return execution.biasDirection === 'MITIGATION'
|
||||
? 'tasks-list-kind-mitigation'
|
||||
: 'tasks-list-kind-bias';
|
||||
}
|
||||
return execution.kind === 'RERUN' ? 'tasks-list-kind-rerun' : 'tasks-list-kind-run';
|
||||
}
|
||||
|
||||
runCountLabel(count: number): string {
|
||||
return `${count} ${count === 1 ? 'run' : 'runs'}`;
|
||||
}
|
||||
|
||||
toggleFilters() {
|
||||
this.filtersOpen.update((open) => !open);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue