From 8af558f91bd6ac57581cb23a0298aa91097982f6 Mon Sep 17 00:00:00 2001 From: Gabrielle Gauthier Melancon Date: Mon, 13 Feb 2023 14:40:50 -0500 Subject: [PATCH] Adapt route to add proposed actions (#429) * Add route to import proposed actions * Adapt front end to new route * Add missing invalidations on data action update * Clean up unnecessary casts * Update tests/test_routers/test_utterances.py Co-authored-by: Joseph Marinier * Adapt based on comments * Fix test --------- Co-authored-by: joseph.marinier --- azimuth/app.py | 2 - azimuth/config.py | 5 +- azimuth/routers/v1/tags.py | 69 ------------- azimuth/routers/v1/utterances.py | 44 ++++++++- azimuth/types/tag.py | 40 +------- azimuth/types/utterance.py | 9 +- tests/test_routers/test_export.py | 11 ++- tests/test_routers/test_tags.py | 52 ---------- tests/test_routers/test_utterances.py | 16 +++ .../components/Analysis/UtterancesTable.tsx | 20 ++-- .../Analysis/UtterancesTableFooter.tsx | 15 +-- .../Utterance/UtteranceDataAction.tsx | 9 +- webapp/src/pages/UtteranceDetail.tsx | 2 +- webapp/src/services/api.ts | 72 +++++--------- webapp/src/types/api.ts | 5 +- webapp/src/types/generated/generatedTypes.ts | 99 ++++++++----------- 16 files changed, 163 insertions(+), 307 deletions(-) delete mode 100644 azimuth/routers/v1/tags.py delete mode 100644 tests/test_routers/test_tags.py diff --git a/azimuth/app.py b/azimuth/app.py index aeac81b8..87ed86ed 100644 --- a/azimuth/app.py +++ b/azimuth/app.py @@ -155,7 +155,6 @@ def create_app() -> FastAPI: from azimuth.routers.v1.model_performance.utterance_count import ( router as utterance_count_router, ) - from azimuth.routers.v1.tags import router as tags_router from azimuth.routers.v1.top_words import router as top_words_router from azimuth.routers.v1.utterances import router as utterances_router from azimuth.utils.routers import require_application_ready, require_available_model @@ -168,7 +167,6 @@ def create_app() -> FastAPI: prefix="/dataset_splits/{dataset_split_name}/class_overlap", dependencies=[Depends(require_application_ready)], ) - api_router.include_router(tags_router, prefix="/tags", dependencies=[]) api_router.include_router( confidence_histogram_router, prefix="/dataset_splits/{dataset_split_name}/confidence_histogram", diff --git a/azimuth/config.py b/azimuth/config.py index 8b763321..36b01b5e 100644 --- a/azimuth/config.py +++ b/azimuth/config.py @@ -154,7 +154,7 @@ def threshold(self) -> Optional[float]: if self.postprocessors is None: return None thresholding = next( - iter([post for post in self.postprocessors if isinstance(post, ThresholdConfig)]), None + (post for post in self.postprocessors if isinstance(post, ThresholdConfig)), None ) if thresholding: return thresholding.threshold @@ -165,8 +165,7 @@ def temperature(self) -> Optional[float]: if self.postprocessors is None: return None temp_scaling = next( - iter([post for post in self.postprocessors if isinstance(post, TemperatureScaling)]), - None, + (post for post in self.postprocessors if isinstance(post, TemperatureScaling)), None ) if temp_scaling: return temp_scaling.temperature diff --git a/azimuth/routers/v1/tags.py b/azimuth/routers/v1/tags.py deleted file mode 100644 index 414716f7..00000000 --- a/azimuth/routers/v1/tags.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright ServiceNow, Inc. 2021 – 2022 -# This source code is licensed under the Apache 2.0 license found in the LICENSE file -# in the root directory of this source tree. - -from typing import Dict, Optional - -from fastapi import APIRouter, Body, Depends, HTTPException -from starlette.status import HTTP_400_BAD_REQUEST - -from azimuth.app import get_dataset_split_manager_mapping, get_task_manager -from azimuth.dataset_split_manager import DatasetSplitManager, PredictionTableKey -from azimuth.task_manager import TaskManager -from azimuth.types import DatasetSplitName -from azimuth.types.tag import ( - ALL_DATA_ACTIONS, - DataAction, - DataActionMapping, - DataActionResponse, - PostDataActionRequest, -) -from azimuth.utils.routers import query_pipeline_index - -router = APIRouter() - -TAGS = ["Tag v1"] - - -@router.post( - "", - summary="Post data_action tags", - description="Post new data_action tags", - tags=TAGS, - response_model=DataActionResponse, -) -def post_data_actions( - request_data: PostDataActionRequest = Body(...), - dataset_split_managers: Dict[DatasetSplitName, Optional[DatasetSplitManager]] = Depends( - get_dataset_split_manager_mapping - ), - task_manager: TaskManager = Depends(get_task_manager), - pipeline_index: Optional[int] = Depends(query_pipeline_index), -) -> DataActionResponse: - # Remove NO_ACTION as it is only used in filtering. - for actions in request_data.data_actions.values(): - actions.pop(DataAction.no_action, None) - - dataset = dataset_split_managers.get(request_data.dataset_split_name) - if dataset is None: - raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="Dataset not found.") - try: - table_key = ( - None - if pipeline_index is None - else PredictionTableKey.from_pipeline_index( - pipeline_index, - task_manager.config, - ) - ) - dataset.add_tags(request_data.data_actions, table_key) - except ValueError as e: - raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=e.args[0]) - task_manager.clear_worker_cache() - updated_tags = dataset.get_tags(list(request_data.data_actions.keys()), table_key=table_key) - return DataActionResponse( - data_actions=[ - DataActionMapping(**{data_action: x[data_action] for data_action in ALL_DATA_ACTIONS}) - for x in updated_tags.values() - ] - ) diff --git a/azimuth/routers/v1/utterances.py b/azimuth/routers/v1/utterances.py index 86ff427c..f75e703d 100644 --- a/azimuth/routers/v1/utterances.py +++ b/azimuth/routers/v1/utterances.py @@ -5,7 +5,7 @@ from enum import Enum from typing import Dict, List, Optional -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Body, Depends, HTTPException, Query from starlette.status import HTTP_404_NOT_FOUND from azimuth.app import ( @@ -46,6 +46,7 @@ ModelPrediction, ModelSaliency, Utterance, + UtterancePatch, ) from azimuth.utils.dataset_operations import filter_dataset_split from azimuth.utils.project import ( @@ -264,6 +265,47 @@ def get_utterances( ) +@router.post( + "", + summary="Patch utterances", + description="Patch utterances, such as updating proposed actions.", + tags=TAGS, + response_model=List[UtterancePatch], +) +def patch_utterances( + utterances: List[UtterancePatch] = Body(...), + dataset_split_manager: DatasetSplitManager = Depends(get_dataset_split_manager), + task_manager: TaskManager = Depends(get_task_manager), +) -> List[UtterancePatch]: + persistent_ids = [utterance.persistent_id for utterance in utterances] + try: + row_indices = dataset_split_manager.get_row_indices_from_persistent_id(persistent_ids) + except ValueError as e: + raise HTTPException(HTTP_404_NOT_FOUND, detail=f"Persistent id not found: {e}.") + + data_actions = {} + for row_idx, utterance in zip(row_indices, utterances): + data_actions[row_idx] = {data_action: False for data_action in ALL_DATA_ACTIONS} + if utterance.data_action != DataAction.no_action: + data_actions[row_idx][utterance.data_action] = True + + dataset_split_manager.add_tags(data_actions) + + task_manager.clear_worker_cache() + updated_tags = dataset_split_manager.get_tags(row_indices) + + return [ + UtterancePatch( + persistent_id=persistent_id, + data_action=next( + (tag for tag, value in tags.items() if tag in ALL_DATA_ACTIONS and value), + DataAction.no_action, + ), + ) + for persistent_id, tags in zip(persistent_ids, updated_tags.values()) + ] + + @router.get( "/{index}/perturbed_utterances", summary="Get a perturbed utterances for a single utterance.", diff --git a/azimuth/types/tag.py b/azimuth/types/tag.py index 527954fa..f49841b6 100644 --- a/azimuth/types/tag.py +++ b/azimuth/types/tag.py @@ -5,19 +5,7 @@ from enum import Enum from typing import Any, Dict, List -from pydantic import Field - -from azimuth.types import AliasModel, DatasetSplitName, ModuleResponse - - -class DataActionMapping(AliasModel): - relabel: bool = Field(..., title="Relabel") - augment_with_similar: bool = Field(..., title="Augment with Similar") - define_new_class: bool = Field(..., title="Define New Class") - merge_classes: bool = Field(..., title="Merge Two Classes") - remove: bool = Field(..., title="Remove") - investigate: bool = Field(..., title="Investigate") - +from azimuth.types import ModuleResponse Tag = str @@ -134,29 +122,3 @@ class SmartTagFamily(str, Enum): class TaggingResponse(ModuleResponse): tags: Dict[Tag, bool] adds: Dict[str, Any] - - -class DataActionResponse(AliasModel): - data_actions: List[DataActionMapping] = Field(..., title="Data action tags") - - -class PostDataActionRequest(AliasModel): - dataset_split_name: DatasetSplitName = Field(..., title="Dataset Split Name") - data_actions: Dict[int, Dict[Tag, bool]] = Field(..., title="Data action tags") - - class Config: - schema_extra = { - "example": { - "dataset_split_name": "eval", - "data_actions": { - 1: { - "relabel": True, - "augment_with_similar": False, - "define_new_class": False, - "merge_classes": False, - "remove": False, - "investigate": False, - } - }, - } - } diff --git a/azimuth/types/utterance.py b/azimuth/types/utterance.py index 15ac9df2..cdfd8846 100644 --- a/azimuth/types/utterance.py +++ b/azimuth/types/utterance.py @@ -37,15 +37,18 @@ class ModelSaliency(AliasModel): saliencies: List[float] = Field(..., title="Saliency") -class Utterance(ValuePerDatasetSmartTag[str], ValuePerPipelineSmartTag[str], AliasModel): - index: int = Field(..., title="Index", description="Row index computed by Azimuth..") +class UtterancePatch(AliasModel): # Union[int, str] in this order so FastAPI tries to cast to int() first, then defaults to str(). persistent_id: Union[int, str] = Field(..., title="Persistent id") + data_action: DataAction = Field(..., title="Data action tag") + + +class Utterance(ValuePerDatasetSmartTag[str], ValuePerPipelineSmartTag[str], UtterancePatch): + index: int = Field(..., title="Index", description="Row index computed by Azimuth..") model_prediction: Optional[ModelPrediction] = Field( ..., title="Model prediction", nullable=True ) model_saliency: Optional[ModelSaliency] = Field(..., title="Model saliency", nullable=True) - data_action: DataAction = Field(..., title="Data action tag") label: str = Field(..., title="Label") utterance: str = Field(..., title="Utterance") diff --git a/tests/test_routers/test_export.py b/tests/test_routers/test_export.py index 35d68ef3..d1c70995 100644 --- a/tests/test_routers/test_export.py +++ b/tests/test_routers/test_export.py @@ -40,11 +40,12 @@ def test_get_utterances(app: FastAPI) -> None: def test_get_proposed_actions(app: FastAPI) -> None: client = TestClient(app) - request = { - "datasetSplitName": "eval", - "data_actions": {0: {"remove": True}, 1: {"relabel": True}}, - } - resp = client.post("/tags", json=request) + + request = [ + {"persistent_id": 0, "data_action": "remove"}, + {"persistent_id": 1, "data_action": "relabel"}, + ] + resp = client.post("/dataset_splits/eval/utterances", json=request) assert resp.status_code == HTTP_200_OK, resp.text resp = client.get("/export/dataset_splits/eval/proposed_actions") diff --git a/tests/test_routers/test_tags.py b/tests/test_routers/test_tags.py deleted file mode 100644 index c8d91b14..00000000 --- a/tests/test_routers/test_tags.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright ServiceNow, Inc. 2021 – 2022 -# This source code is licensed under the Apache 2.0 license found in the LICENSE file -# in the root directory of this source tree. - -from fastapi import FastAPI -from starlette.status import HTTP_200_OK -from starlette.testclient import TestClient - - -def test_post_tags(app: FastAPI) -> None: - client = TestClient(app) - - request = { - "datasetSplitName": "eval", - "data_actions": {0: {"remove": True, "NO_ACTION": False}}, - } - resp = client.post("/tags", json=request) - assert resp.status_code == HTTP_200_OK, resp.text - data = resp.json() - assert data == { - "dataActions": [ - { - "relabel": False, - "defineNewClass": False, - "mergeClasses": False, - "remove": True, - "augmentWithSimilar": False, - "investigate": False, - } - ] - } - - # Reset tag to NO_ACTION - request = { - "datasetSplitName": "eval", - "data_actions": {0: {"remove": False, "NO_ACTION": True}}, - } - resp = client.post("/tags", json=request) - assert resp.status_code == HTTP_200_OK, resp.text - data = resp.json() - assert data == { - "dataActions": [ - { - "relabel": False, - "defineNewClass": False, - "mergeClasses": False, - "remove": False, - "augmentWithSimilar": False, - "investigate": False, - } - ] - } diff --git a/tests/test_routers/test_utterances.py b/tests/test_routers/test_utterances.py index 69fed805..db74326b 100644 --- a/tests/test_routers/test_utterances.py +++ b/tests/test_routers/test_utterances.py @@ -4,6 +4,7 @@ from typing import List from fastapi import FastAPI +from starlette.status import HTTP_200_OK from starlette.testclient import TestClient UTTERANCE_COUNT = 42 @@ -147,3 +148,18 @@ def test_perturbed_utterances(app: FastAPI, monkeypatch): ).json() # Utterance 1 has 11 perturbation tests assert len(resp) == 11 + + +def test_post_utterances(app: FastAPI) -> None: + client = TestClient(app) + + request = [{"persistentId": 0, "dataAction": "remove"}] + resp = client.post("/dataset_splits/eval/utterances", json=request) + assert resp.status_code == HTTP_200_OK, resp.text + assert resp.json() == request + + # Reset tag to NO_ACTION + request = [{"persistentId": 0, "dataAction": "NO_ACTION"}] + resp = client.post("/dataset_splits/eval/utterances", json=request) + assert resp.status_code == HTTP_200_OK, resp.text + assert resp.json() == request diff --git a/webapp/src/components/Analysis/UtterancesTable.tsx b/webapp/src/components/Analysis/UtterancesTable.tsx index 93dd1805..0ecc7f99 100644 --- a/webapp/src/components/Analysis/UtterancesTable.tsx +++ b/webapp/src/components/Analysis/UtterancesTable.tsx @@ -95,7 +95,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -type Row = Utterance & { id: number }; +type Row = Utterance & { id: Utterance["persistentId"] }; type Props = { jobId: string; @@ -138,7 +138,9 @@ const UtterancesTable: React.FC = ({ const { data: utterancesResponse, isFetching } = getUtterancesEndpoint.useQuery(getUtterancesQueryState); - const [selectedIds, setSelectedIds] = React.useState([]); + const [selectedPersistentIds, setSelectedPersistentIds] = React.useState< + number[] + >([]); const handlePageChange = (page: number) => { const q = constructSearchString({ @@ -236,7 +238,7 @@ const UtterancesTable: React.FC = ({ row, }: GridCellParams) => ( = ({ // That's the width of the table when viewed on a MacBook Pro 16 with the filter panel. const columns: Column[] = [ { - field: "id", + field: "index", headerName: "Id", description: ID_TOOLTIP, width: 55, @@ -356,7 +358,7 @@ const UtterancesTable: React.FC = ({ const rows: Row[] = React.useMemo( () => utterancesResponse?.utterances.map((utterance) => ({ - id: utterance.index, + id: utterance.persistentId, ...utterance, })) ?? [], [utterancesResponse] @@ -366,7 +368,7 @@ const UtterancesTable: React.FC = ({ const RowLink = (props: RowProps) => ( @@ -416,9 +418,9 @@ const UtterancesTable: React.FC = ({ checkboxSelection disableColumnMenu onSelectionModelChange={(newSelection) => { - setSelectedIds(newSelection as number[]); + setSelectedPersistentIds(newSelection as number[]); }} - selectionModel={selectedIds} + selectionModel={selectedPersistentIds} components={{ Footer: UtterancesTableFooter, Row: RowLink, @@ -428,7 +430,7 @@ const UtterancesTable: React.FC = ({ onClick: (e: React.MouseEvent) => e.stopPropagation(), }, footer: { - selectedIds, + selectedPersistentIds, allDataActions: datasetInfo?.dataActions || [], getUtterancesQueryState, }, diff --git a/webapp/src/components/Analysis/UtterancesTableFooter.tsx b/webapp/src/components/Analysis/UtterancesTableFooter.tsx index d772efda..c9380404 100644 --- a/webapp/src/components/Analysis/UtterancesTableFooter.tsx +++ b/webapp/src/components/Analysis/UtterancesTableFooter.tsx @@ -1,17 +1,18 @@ -import React from "react"; import { Box, Typography } from "@mui/material"; -import UtteranceDataAction from "components/Utterance/UtteranceDataAction"; import CustomPagination from "components/CustomPagination"; +import UtteranceDataAction from "components/Utterance/UtteranceDataAction"; +import React from "react"; +import { UtterancePatch } from "types/api"; import { GetUtterancesQueryState } from "utils/api"; type Props = { - selectedIds: number[]; + selectedPersistentIds: UtterancePatch["persistentId"][]; allDataActions: string[]; getUtterancesQueryState: GetUtterancesQueryState; }; const UtterancesTableFooter: React.FC = ({ - selectedIds, + selectedPersistentIds, allDataActions, getUtterancesQueryState, }) => ( @@ -26,17 +27,17 @@ const UtterancesTableFooter: React.FC = ({ marginX={2} display="flex" alignItems="center" - visibility={selectedIds.length > 0 ? "visible" : "hidden"} + visibility={selectedPersistentIds.length > 0 ? "visible" : "hidden"} > - Apply proposed action on {selectedIds.length} rows: + Apply proposed action on {selectedPersistentIds.length} rows: = ({ - utteranceIds, + persistentIds, dataAction, confirmationButton, allDataActions, @@ -25,9 +25,8 @@ const UtteranceDataAction: React.FC = ({ const handleDataActionChange = (newValue: DataAction) => { updateDataAction({ - ids: utteranceIds, + persistentIds, newValue, - allDataActions, ...getUtterancesQueryState, }); }; diff --git a/webapp/src/pages/UtteranceDetail.tsx b/webapp/src/pages/UtteranceDetail.tsx index b6140677..d2729709 100644 --- a/webapp/src/pages/UtteranceDetail.tsx +++ b/webapp/src/pages/UtteranceDetail.tsx @@ -292,7 +292,7 @@ export const UtteranceDetail = () => { { - const newTagsMap: { [id: number]: Tags } = {}; - ids.forEach((id) => { - const allFalse = allDataActions.reduce( - (tags: Record, tag) => ({ - ...tags, - [tag]: false, - }), - {} - ); - const newTags: Tags = - newValue === DATA_ACTION_NONE_VALUE - ? allFalse - : { ...allFalse, [newValue]: true }; - newTagsMap[id] = newTags; - }); - - return newTagsMap; -}; - const tagTypes = [ "DatasetInfo", "ConfidenceHistogram", @@ -66,8 +40,6 @@ const tagTypes = [ "ClassOverlap", ] as const; -type Tag = typeof tagTypes[number]; - export const api = createApi({ baseQuery: fakeBaseQuery<{ message: string }>(), tagTypes, @@ -220,39 +192,45 @@ export const api = createApi({ ), }), updateDataActions: build.mutation< - DataActionResponse, + UtterancePatch[], { - ids: number[]; + persistentIds: UtterancePatch["persistentId"][]; newValue: DataAction; - allDataActions: string[]; } & GetUtterancesQueryState >({ - queryFn: async ({ - jobId, - datasetSplitName, - ids, - newValue, - allDataActions, - }) => + queryFn: async ({ jobId, datasetSplitName, persistentIds, newValue }) => responseToData( - fetchApi({ path: "/tags", method: "post" }), + fetchApi({ + path: "/dataset_splits/{dataset_split_name}/utterances", + method: "post", + }), "Something went wrong updating proposed actions" )({ jobId, - body: { - datasetSplitName, - dataActions: getDataActions(ids, newValue, allDataActions), - }, + datasetSplitName, + body: persistentIds.map((persistentId) => ({ + persistentId, + dataAction: newValue, + })), }), - invalidatesTags: () => ["OutcomeCountPerFilter", "Utterances"], + invalidatesTags: () => [ + "ConfidenceHistogram", + "ConfusionMatrix", + "Metrics", + "MetricsPerFilter", + "OutcomeCountPerFilter", + "TopWords", + "UtteranceCountPerFilter", + "Utterances", + ], async onQueryStarted( - { ids, newValue, allDataActions, ...args }, + { persistentIds, newValue, ...args }, { dispatch, queryFulfilled } ) { const patchResult = dispatch( api.util.updateQueryData("getUtterances", args, (draft) => { draft.utterances.forEach((utterance) => { - if (ids.includes(utterance.index)) { + if (persistentIds.includes(utterance.persistentId)) { utterance.dataAction = newValue; } }); diff --git a/webapp/src/types/api.ts b/webapp/src/types/api.ts index 35d64d69..088c700a 100644 --- a/webapp/src/types/api.ts +++ b/webapp/src/types/api.ts @@ -28,8 +28,6 @@ export interface CountPerFilterResponse export type CountPerFilterValue = Partial & UtteranceCountPerFilterValue; export type DataAction = components["schemas"]["DataAction"]; -export type DataActionMapping = components["schemas"]["DataActionMapping"]; -export type DataActionResponse = components["schemas"]["DataActionResponse"]; export type DatasetDistributionComparison = components["schemas"]["DatasetDistributionComparison"]; export type DatasetDistributionComparisonValue = @@ -68,8 +66,6 @@ export type PerturbedUtterance = export type PerturbationType = components["schemas"]["PerturbationType"]; export type PipelineDefinition = components["schemas"]["PipelineDefinition"]; export type PlotSpecification = components["schemas"]["PlotSpecification"]; -export type PostDataActionRequest = - components["schemas"]["PostDataActionRequest"]; export type SimilarUtterance = components["schemas"]["SimilarUtterance"]; export type SimilarUtterancesResponse = components["schemas"]["SimilarUtterancesResponse"]; @@ -78,6 +74,7 @@ export type StatusResponse = components["schemas"]["StatusResponse"]; export type TopWordsResponse = components["schemas"]["TopWordsResponse"]; export type TopWordsResult = components["schemas"]["TopWordsResult"]; export type Utterance = components["schemas"]["Utterance"]; +export type UtterancePatch = components["schemas"]["UtterancePatch"]; export type UtteranceCountPerFilterResponse = components["schemas"]["UtteranceCountPerFilterResponse"]; export type UtteranceCountPerFilterValue = diff --git a/webapp/src/types/generated/generatedTypes.ts b/webapp/src/types/generated/generatedTypes.ts index 7c7aeb18..adfebe17 100644 --- a/webapp/src/types/generated/generatedTypes.ts +++ b/webapp/src/types/generated/generatedTypes.ts @@ -38,10 +38,6 @@ export interface paths { /** Get data for class overlap, confusion, and related utterance counts. */ get: operations["get_class_overlap_dataset_splits__dataset_split_name__class_overlap_get"]; }; - "/tags": { - /** Post new data_action tags */ - post: operations["post_data_actions_tags_post"]; - }; "/dataset_splits/{dataset_split_name}/confidence_histogram": { /** Get all confidence bins with their confidence and the outcome count */ get: operations["get_confidence_histogram_dataset_splits__dataset_split_name__confidence_histogram_get"]; @@ -73,6 +69,8 @@ export interface paths { "/dataset_splits/{dataset_split_name}/utterances": { /** Get a table view of the utterances according to filters. */ get: operations["get_utterances_dataset_splits__dataset_split_name__utterances_get"]; + /** Patch utterances, such as updating proposed actions. */ + post: operations["patch_utterances_dataset_splits__dataset_split_name__utterances_post"]; }; "/dataset_splits/{dataset_split_name}/utterances/{index}/perturbed_utterances": { /** Get a perturbed utterances for a single utterance. */ @@ -256,25 +254,6 @@ export interface components { | "remove" | "investigate" | "NO_ACTION"; - /** - * This model should be used as the base for any model that defines aliases to ensure - * that all fields are represented correctly. - */ - DataActionMapping: { - relabel: boolean; - augmentWithSimilar: boolean; - defineNewClass: boolean; - mergeClasses: boolean; - remove: boolean; - investigate: boolean; - }; - /** - * This model should be used as the base for any model that defines aliases to ensure - * that all fields are represented correctly. - */ - DataActionResponse: { - dataActions: components["schemas"]["DataActionMapping"][]; - }; /** * This model should be used as the base for any model that defines aliases to ensure * that all fields are represented correctly. @@ -640,14 +619,6 @@ export interface components { data: { [key: string]: any }[]; layout: { [key: string]: any }; }; - /** - * This model should be used as the base for any model that defines aliases to ensure - * that all fields are represented correctly. - */ - PostDataActionRequest: { - datasetSplitName: components["schemas"]["DatasetSplitName"]; - dataActions: { [key: string]: { [key: string]: boolean } }; - }; /** Class for saving the results in the dataset and the routes. */ PostprocessingStepAPIResponse: { output: components["schemas"]["PredictionDetails"]; @@ -844,6 +815,8 @@ export interface components { * that all fields are represented correctly. */ Utterance: { + persistentId: number | string; + dataAction: components["schemas"]["DataAction"]; almostCorrect: string[]; behavioralTesting: string[]; pipelineComparison: string[]; @@ -853,10 +826,8 @@ export interface components { dissimilar: string[]; /** Row index computed by Azimuth.. */ index: number; - persistentId: number | string; modelPrediction: components["schemas"]["ModelPrediction"] | null; modelSaliency: components["schemas"]["ModelSaliency"] | null; - dataAction: components["schemas"]["DataAction"]; label: string; utterance: string; }; @@ -887,6 +858,14 @@ export interface components { utteranceCount: number; filterValue: string; }; + /** + * This model should be used as the base for any model that defines aliases to ensure + * that all fields are represented correctly. + */ + UtterancePatch: { + persistentId: number | string; + dataAction: components["schemas"]["DataAction"]; + }; /** An enumeration. */ UtterancesSortableColumn: | "index" @@ -1070,33 +1049,6 @@ export interface operations { }; }; }; - /** Post new data_action tags */ - post_data_actions_tags_post: { - parameters: { - query: { - pipeline_index?: number; - }; - }; - responses: { - /** Successful Response */ - 200: { - content: { - "application/json": components["schemas"]["DataActionResponse"]; - }; - }; - /** Validation Error */ - 422: { - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - requestBody: { - content: { - "application/json": components["schemas"]["PostDataActionRequest"]; - }; - }; - }; /** Get all confidence bins with their confidence and the outcome count */ get_confidence_histogram_dataset_splits__dataset_split_name__confidence_histogram_get: { parameters: { @@ -1361,6 +1313,33 @@ export interface operations { }; }; }; + /** Patch utterances, such as updating proposed actions. */ + patch_utterances_dataset_splits__dataset_split_name__utterances_post: { + parameters: { + path: { + dataset_split_name: components["schemas"]["DatasetSplitName"]; + }; + }; + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["UtterancePatch"][]; + }; + }; + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UtterancePatch"][]; + }; + }; + }; /** Get a perturbed utterances for a single utterance. */ get_perturbed_utterances_dataset_splits__dataset_split_name__utterances__index__perturbed_utterances_get: { parameters: {