diff --git a/apps/monk-test-app/src/i18n.ts b/apps/monk-test-app/src/i18n.ts
index 5efb68d50..4683460e9 100644
--- a/apps/monk-test-app/src/i18n.ts
+++ b/apps/monk-test-app/src/i18n.ts
@@ -22,7 +22,7 @@ i18n
de: { translation: de },
},
})
- .catch((err) => console.error(err));
+ .catch(console.error);
i18nLinkSDKInstances(i18n, [i18nCamera]);
diff --git a/apps/monk-test-app/src/views/CameraView/components/TestPanelLastPic.tsx b/apps/monk-test-app/src/views/CameraView/components/TestPanelLastPic.tsx
index 8ef651667..59308bbe5 100644
--- a/apps/monk-test-app/src/views/CameraView/components/TestPanelLastPic.tsx
+++ b/apps/monk-test-app/src/views/CameraView/components/TestPanelLastPic.tsx
@@ -53,7 +53,7 @@ export function TestPanelLastPic({ lastPicture }: TestPanelLastPicProps) {
anchorRef.current.download = createFileName(lastPicture);
}
})
- .catch((err) => console.error(err));
+ .catch(console.error);
}
}, [lastPicture]);
diff --git a/apps/monk-test-app/src/views/TestView/TestView.css b/apps/monk-test-app/src/views/TestView/TestView.css
index d797fc204..1d6e023db 100644
--- a/apps/monk-test-app/src/views/TestView/TestView.css
+++ b/apps/monk-test-app/src/views/TestView/TestView.css
@@ -1,8 +1,9 @@
.test-view-container {
height: 100%;
width: 100%;
- background-color: red;
+ background-color: black;
display: flex;
align-items: center;
- justify-content: flex-end;
+ justify-content: center;
+ color: white;
}
diff --git a/apps/monk-test-app/src/views/TestView/TestView.tsx b/apps/monk-test-app/src/views/TestView/TestView.tsx
index e4b354ec3..b93cda0ff 100644
--- a/apps/monk-test-app/src/views/TestView/TestView.tsx
+++ b/apps/monk-test-app/src/views/TestView/TestView.tsx
@@ -1,15 +1,52 @@
-import React from 'react';
-import './TestView.css';
+import React, { useState } from 'react';
import { PhotoCapture } from '@monkvision/inspection-capture-web';
import { sights } from '@monkvision/sights';
+import './TestView.css';
+
+const captureSights = [
+ sights['haccord-8YjMcu0D'],
+ sights['haccord-DUPnw5jj'],
+ sights['haccord-hsCc_Nct'],
+ sights['haccord-GQcZz48C'],
+ sights['haccord-QKfhXU7o'],
+ sights['haccord-mdZ7optI'],
+ sights['haccord-bSAv3Hrj'],
+ sights['haccord-W-Bn3bU1'],
+ sights['haccord-GdWvsqrm'],
+ sights['haccord-ps7cWy6K'],
+ sights['haccord-Jq65fyD4'],
+ sights['haccord-OXYy5gET'],
+ sights['haccord-5LlCuIfL'],
+ sights['haccord-Gtt0JNQl'],
+ sights['haccord-cXSAj2ez'],
+ sights['haccord-KN23XXkX'],
+ sights['haccord-Z84erkMb'],
+];
+
+const inspectionId = 'b072ff42-6244-ef3b-b018-5d3d6562c1bd';
+
+const apiConfig = {
+ apiDomain: 'api.preview.monk.ai/v1',
+ authToken:
+ 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjNLUnpaNm01WDFzOWFBZWRudnBrWSJ9.eyJpc3MiOiJodHRwczovL2lkcC5wcmV2aWV3Lm1vbmsuYWkvIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDY5MzYxMTEwMDU4MDYxODA1NTYiLCJhdWQiOlsiaHR0cHM6Ly9hcGkubW9uay5haS92MS8iLCJodHRwczovL21vbmstcHJldmlldy5ldS5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzA3NDcxNzU5LCJleHAiOjE3MDc0Nzg5NTksImF6cCI6InNvWjdQMmM2YjlJNWphclFvUnJoaDg3eDlUcE9TYUduIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInBlcm1pc3Npb25zIjpbIm1vbmtfY29yZV9hcGk6Y29tcGxpYW5jZXMiLCJtb25rX2NvcmVfYXBpOmRhbWFnZV9kZXRlY3Rpb24iLCJtb25rX2NvcmVfYXBpOmRhc2hib2FyZF9vY3IiLCJtb25rX2NvcmVfYXBpOmltYWdlc19vY3IiLCJtb25rX2NvcmVfYXBpOmluc3BlY3Rpb25zOmNyZWF0ZSIsIm1vbmtfY29yZV9hcGk6aW5zcGVjdGlvbnM6ZGVsZXRlIiwibW9ua19jb3JlX2FwaTppbnNwZWN0aW9uczpkZWxldGVfYWxsIiwibW9ua19jb3JlX2FwaTppbnNwZWN0aW9uczpkZWxldGVfb3JnYW5pemF0aW9uIiwibW9ua19jb3JlX2FwaTppbnNwZWN0aW9uczpyZWFkIiwibW9ua19jb3JlX2FwaTppbnNwZWN0aW9uczpyZWFkX2FsbCIsIm1vbmtfY29yZV9hcGk6aW5zcGVjdGlvbnM6cmVhZF9vcmdhbml6YXRpb24iLCJtb25rX2NvcmVfYXBpOmluc3BlY3Rpb25zOnVwZGF0ZSIsIm1vbmtfY29yZV9hcGk6aW5zcGVjdGlvbnM6dXBkYXRlX2FsbCIsIm1vbmtfY29yZV9hcGk6aW5zcGVjdGlvbnM6dXBkYXRlX29yZ2FuaXphdGlvbiIsIm1vbmtfY29yZV9hcGk6aW5zcGVjdGlvbnM6d3JpdGUiLCJtb25rX2NvcmVfYXBpOmluc3BlY3Rpb25zOndyaXRlX2FsbCIsIm1vbmtfY29yZV9hcGk6aW5zcGVjdGlvbnM6d3JpdGVfb3JnYW5pemF0aW9uIiwibW9ua19jb3JlX2FwaTpyZXBhaXJfZXN0aW1hdGUiLCJtb25rX2NvcmVfYXBpOnVzZXJzOnJlYWQiLCJtb25rX2NvcmVfYXBpOnVzZXJzOnJlYWRfYWxsIiwibW9ua19jb3JlX2FwaTp1c2VyczpyZWFkX29yZ2FuaXphdGlvbiIsIm1vbmtfY29yZV9hcGk6dXNlcnM6dXBkYXRlIiwibW9ua19jb3JlX2FwaTp1c2Vyczp1cGRhdGVfYWxsIiwibW9ua19jb3JlX2FwaTp1c2Vyczp1cGRhdGVfb3JnYW5pemF0aW9uIiwibW9ua19jb3JlX2FwaTp1c2Vyczp3cml0ZSIsIm1vbmtfY29yZV9hcGk6dXNlcnM6d3JpdGVfYWxsIiwibW9ua19jb3JlX2FwaTp1c2Vyczp3cml0ZV9vcmdhbml6YXRpb24iLCJtb25rX2NvcmVfYXBpOndoZWVsX2FuYWx5c2lzIl19.m38G5NaCD_pcGptJdLPa_TVtD08yRY9qdp-5pCELd8Ekzma0kCWMctwHvlrv2OsjynlXyAutIK2uhDMrdLdnPk_6bU4rZheej2s0obXaXqZCUTbUOh8scL-81tbH_ZlKN3oSXfqUVMnwvpa1bnXZHmjeHi2e3bhvjxW-Jg5DrBB9gNfstxK0hugrlxtNL96y6ImEITOxOMEbURYGwOLQQtLkRqFo7AeZCu-_w6UbtFZfLJc5FlsWpTKy7I3_xMynzDGGaADRQeyazL_7DfemCb_VPXkR9aV67tF4pt6jevCetYI5CfN5X2JJrp7XNES_M2d7wGdJl5C6lbBPs3n3SA',
+};
-const sightsArray = Object.values(sights)
- .filter((value) => value.category === 'exterior')
- .slice(0, 5);
export function TestView() {
+ const [complete, setComplete] = useState(false);
+
return (
-
+ {complete ? (
+ 'Inspection Complete!'
+ ) : (
+
setComplete(true)}
+ onClose={() => console.log('coucou')}
+ />
+ )}
);
}
diff --git a/documentation/docs/getting-started.md b/documentation/docs/getting-started.md
index 30161d891..cb89095df 100644
--- a/documentation/docs/getting-started.md
+++ b/documentation/docs/getting-started.md
@@ -3,3 +3,54 @@ sidebar_position: 2
---
# Getting Started
+## PhotoCapture
+If you plan on integrating the PhotoCapture workflow into your app, you can first start by installing the required
+dependencies :
+
+```shell
+yarn add @monkvision/inspection-capture-web @monkvision/sights
+```
+
+You can then create a new page in your application that will implement the PhotoCapture component in the following way :
+
+```tsx
+import { sights } from '@monkvision/sights';
+import { PhotoCapture } from '@monkvision/inspection-capture-web';
+
+const apiConfig = {
+ apiDomain: 'MONK_API_DOMAIN',
+ authToken: 'YOUR_AUTH0_ACCESS_TOKEN',
+};
+
+const sights = [
+ sights['fesc20-H1dfdfvH'],
+ sights['fesc20-WMUaKDp1'],
+ sights['fesc20-LTe3X2bg'],
+ sights['fesc20-hp3Tk53x'],
+];
+
+export function MonkCapturePage() {
+ const handleSuccess = () => {
+ // Redirect to another page once the inspection is complete.
+ };
+
+ return (
+
+ );
+}
+```
+
+In the code snippet above, you can replace :
+- `MONK_API_DOMAIN` by the Monk APi Domain that you plan on using :
+ - `api.monk.ai/v1` for production
+ - `api.preview.monk.ai/v1` for preview
+- `YOUR_AUTH0_ACCESS_TOKEN` by the Auth0 authentication token that you previously generated by logging in
+- `sights` with an array of sights of your choice obtained from the `@monkvision/sights` package.
+
+For additional configuration options for this component, please refer to the
+[InspectionCaptureWeb documentation page](docs/packages/inspection-capture-web.md).
diff --git a/documentation/docs/introduction.md b/documentation/docs/introduction.md
index cce133df5..3586f4fb1 100644
--- a/documentation/docs/introduction.md
+++ b/documentation/docs/introduction.md
@@ -15,7 +15,7 @@ that exposes endpoints to communicate with the AI models. The usual Monk workflo
should be included with every request made to the Monk API.
2. Create an *Inspection* using the `POST /inspection` endpoint, and specify which AI models (which *Tasks*) you want to
run during this inspection.
-3. Take pictures of your vehicle and add them to your inspection using the `POST /inspection/{id}/images` endpiont. For
+3. Take pictures of your vehicle and add them to your inspection using the `POST /inspection/{id}/images` endpoint. For
each picture, specify which *Task* you want to run on it.
4. *(optional)* Ask the API to analyze the quality of the images before running the AI models on them
5. Once all the pictures are uploaded, tell the API to start inspection *Tasks* by using the
@@ -36,8 +36,8 @@ implement this workflow yourself, you can refer to our [API Documentation](https
Among other things, the MonkJs SDK offers the following tools to implement the workflow mentionned above :
- A package called [network](docs/packages/network.md) that offers utility functions to easily communicate with our API.
-- A single-page component called [InspectionCapture](docs/packages/inspection-capture-web.md) that you can place in your
- app to display a camera preview and allow users to take pictures of their vehicle, analyze the quality of the pictures
+- Single-page components like [PhotoCapture](docs/packages/inspection-capture-web.md) that you can place in your app
+ to display a camera preview and allow users to take pictures of their vehicle, analyze the quality of the pictures
taken and upload them to the API.
- A single-page component called [InspectionReport](docs/packages/inspection-report-web.md) that you can place in your app
to display the results of an insepction, update the results if needed, and ask the API to generate the PDF report.
diff --git a/documentation/docs/packages/camera-web.md b/documentation/docs/packages/camera-web.md
index 894123323..1a42d6dfc 100644
--- a/documentation/docs/packages/camera-web.md
+++ b/documentation/docs/packages/camera-web.md
@@ -11,7 +11,8 @@ called Camera, that lets you display a camera preview on your app as well as an
to take pictures, compress images, etc.
Generally speaking, as a developer using our SDK, you won't have to directly use this component, since you probably
-want to use higher level components such as the [inspection-capture-web](docs/packages/inspection-capture-web.md) package.
+want to use higher level components such as the ones available in the
+[inspection-capture-web](docs/packages/inspection-capture-web.md) package.
## Complete Documentation
As every other package in the SDK, please refer to
diff --git a/documentation/docs/packages/inspection-capture-web.md b/documentation/docs/packages/inspection-capture-web.md
index 9c2006079..d45dafaa6 100644
--- a/documentation/docs/packages/inspection-capture-web.md
+++ b/documentation/docs/packages/inspection-capture-web.md
@@ -6,9 +6,9 @@ sidebar_position: 5

## Overview
-This package exports a single page component for React (Web) called InspectionCapture that implements the capture
-process in the Monk SDK. The capture process allows the user to take pictures of its vehicle and will automatically send
-the pictures to our API, start the tasks and check the quality of the images using our compliance features.
+This package exports single page components for React (Web) that implements different capture workflows in the Monk SDK.
+The capture process allows the user to take pictures of its vehicle, automatically send them to our API, start the tasks
+and check the quality of the images using our compliance features.
## Complete Documentation
As every other package in the SDK, please refer to
diff --git a/documentation/docs/upgrading-from-v3.md b/documentation/docs/upgrading-from-v3.md
index 1c3c05549..fbe27974d 100644
--- a/documentation/docs/upgrading-from-v3.md
+++ b/documentation/docs/upgrading-from-v3.md
@@ -3,3 +3,186 @@ sidebar_position: 3
---
# Upgrading From V3
+This documentation page provides guidlines to upgrade from the MonkJs 3.X version to the 4.X.
+
+## Why upgrade?
+MonkJs 4 is a complete revamp of the Monk SDK, rewritten from scratch, aimed at providing :
+- Better performances
+- Better monitoring for bug catching
+- New and improved UX/UI
+- New features (Video capture, live compliance, offline management...)
+- TypeScript typings with TSDoc integrated
+- Better documentation
+- And many more 🚀
+
+Another key thing is that MonkJs used to be developed as a cross-platform React-Native SDK for Web (iOS / Android /
+Web). This basically forced us to use Expo and other dependencies that were hard to maintain with such low-level
+features. MonkJs 4 solves this issue by offering basically two SDKs : one for Web using ReactJs, and one for Native
+using React-Native. Some packages of the SDK work for both web and native, while others are split into two separate
+pacakges every time : one ending with `-web` for Web and one ending with `-native` for Native.
+
+For now, only the Web part of the SDK has been implemented, but the Native one should come soon enough!
+
+## Sights
+The sights data previous exported as the default export of the Sights package is now split into 3 objects :
+- `vehicles` containing the details for each vehicle used as a model for the sights
+- `labels` containing translation details about each Sight label
+- `sights` containing the Sight data, with more or less the same structure as before, except for the vehicle details and
+ sight label that now are simply a key reference to the other data export by the package.
+
+Another major difference is that sights now include additional details such as the list of tasks recommended to be run
+for this sight. This means that you now don't have to specify manually which task to run for each sight when
+implementing a capture workflow.
+
+#### How you did it in MonkJs 3
+```javascript
+import sights from '@monkvision/sights';
+
+const sight = sights['fesc20-H1dfdfvH'];
+console.log(JSON.stringify(sight, null, 2));
+/*
+ * Output :
+ * {
+ * "id": "fesc20-H1dfdfvH",
+ * "category": "exterior"
+ * "overlay": "... "
+ * "label": {
+ * "en":"Front Low",
+ * "fr":"Avant (position basse)"
+ * },
+ * }
+ */
+```
+
+#### How you do it in MonkJs 4
+```typescript
+import { sights, labels } from '@monkvision/sights';
+
+const sight = sights['fesc20-H1dfdfvH'];
+console.log(JSON.stringify(sight, null, 2));
+/*
+ * Output :
+ * {
+ * "id": "fesc20-H1dfdfvH",
+ * "category": "exterior",
+ * "label": "front-low",
+ * "overlay": "... ",
+ * "tasks": [
+ * "damage_detection"
+ * ],
+ * "vehicle": "fesc20"
+ * }
+ */
+
+console.log(JSON.stringify(labels[sight.label], null, 2));
+/*
+ * Output :
+ * {
+ * "key": "front-low",
+ * "en": "Front Low",
+ * "fr": "Avant - vue basse",
+ * "de": "Vorderseite Niedrig"
+ * }
+ */
+```
+
+## Camera and Inspection Capture
+The `@monkvision/camera` package used to provide components such as `Capture` that were used to implement the picture
+taking workflows (capture workflows) for inspections, as well as declaring the Camera app used by these Capture
+components. This package has now been split into two :
+
+- `@monkvision/camera-web` : A package that export a component called `Camera` that implements a camera preview used to
+ take pictures, as well as logic utilities used to handle this camera. Usually, as a developer using the SDK, you won't
+ have to use this package directly.
+- `@monkvision/inspection-capture-web` : A package that export capture components (such as `PhotoCapture`) used to
+ integrate the different Monk inspection capture workflows into your app.
+
+#### How you did it in MonkJs 3
+```jsx
+import monk from '@monkvision/corejs';
+import { Capture, Controls } from '@monkvision/camera';
+
+const { TaskName } = monk.types;
+monk.config.authConfig = { domain: 'api.monk.ai/v1' };
+monk.config.accessToken = 'YOUR_AUTH0_ACCESS_TOKEN';
+
+const inspectionId = '1e11fb94-26fe-4956-90b6-d11ef3c87da4';
+
+const sightIds = [
+ 'fesc20-H1dfdfvH',
+ 'fesc20-WMUaKDp1',
+ 'fesc20-LTe3X2bg',
+ 'fesc20-hp3Tk53x',
+];
+
+const mapTasksToSight = [
+ { id: 'fesc20-H1dfdfvH', tasks: [TaskName.DAMAGE_DETECTION] },
+ { id: 'fesc20-WMUaKDp1', tasks: [TaskName.DAMAGE_DETECTION] },
+ { id: 'fesc20-LTe3X2bg', tasks: [TaskName.DAMAGE_DETECTION] },
+ { id: 'fesc20-hp3Tk53x', tasks: [TaskName.DAMAGE_DETECTION] },
+];
+
+function App() {
+ const [cameraLoading, setCameraLoading] = useState(false);
+
+ const controls = [
+ { disabled: cameraLoading, ...Controls.AddDamageButtonProps },
+ { disabled: cameraLoading, ...Controls.CaptureButtonProps },
+ ];
+
+ const handleSuccess = () => {
+ // Update all the tasks statuses to TODO manually and then redirect to another page.
+ };
+
+ return (
+ setCameraLoading(false)}
+ onStartUploadPicture={() => setCameraLoading(true)}
+ onFinishUploadPicture={() => setCameraLoading(false)}
+ enableComplianceCheck
+ onComplianceCheckFinish={handleSuccess}
+ />
+ );
+}
+```
+
+#### How you do it in MonkJs 4
+```tsx
+import { sights } from '@monkvision/sights';
+import { PhotoCapture } from '@monkvision/inspection-capture-web';
+
+const apiConfig = {
+ apiDomain: 'api.monk.ai/v1',
+ authToken: 'YOUR_AUTH0_ACCESS_TOKEN',
+};
+
+const inspectionId = '1e11fb94-26fe-4956-90b6-d11ef3c87da4';
+
+const sights = [
+ sights['fesc20-H1dfdfvH'],
+ sights['fesc20-WMUaKDp1'],
+ sights['fesc20-LTe3X2bg'],
+ sights['fesc20-hp3Tk53x'],
+];
+
+function App() {
+ const handleSuccess = () => {
+ // Immediately redirect to another page.
+ };
+
+ return (
+
+ );
+}
+```
diff --git a/documentation/docs/v3-docs/guides/upgrading-to-3.5.md b/documentation/docs/v3-docs/guides/upgrading-to-3.5.md
index 0f4f308aa..c23da8315 100644
--- a/documentation/docs/v3-docs/guides/upgrading-to-3.5.md
+++ b/documentation/docs/v3-docs/guides/upgrading-to-3.5.md
@@ -91,4 +91,3 @@ rename those sights accordingly in the list of sight given as the `sightIds` pro
| UHZkpCuK | vwtroc-UHZkpCuK |
| 3a3OheoD | vwtroc-3a3OheoD |
| IVcF1dOP | vwtroc-IVcF1dOP |
-
diff --git a/packages/private/test-utils/src/__mocks__/@monkvision/camera-web.tsx b/packages/private/test-utils/src/__mocks__/@monkvision/camera-web.tsx
index fc3fff67f..62b825870 100644
--- a/packages/private/test-utils/src/__mocks__/@monkvision/camera-web.tsx
+++ b/packages/private/test-utils/src/__mocks__/@monkvision/camera-web.tsx
@@ -12,4 +12,5 @@ export = {
Camera: jest.fn(() => <>>),
SimpleCameraHUD: jest.fn(() => <>>),
i18nCamera: {},
+ getCameraErrorLabel: jest.fn(() => ({ en: '', fr: '', de: '' })),
};
diff --git a/packages/private/test-utils/src/__mocks__/@monkvision/common.tsx b/packages/private/test-utils/src/__mocks__/@monkvision/common.tsx
index b17a54daa..6f625bd6c 100644
--- a/packages/private/test-utils/src/__mocks__/@monkvision/common.tsx
+++ b/packages/private/test-utils/src/__mocks__/@monkvision/common.tsx
@@ -19,6 +19,9 @@ const {
createTheme,
createEmptyMonkState,
MonkActionType,
+ getFileExtensions,
+ uniq,
+ flatMap,
} = jest.requireActual('@monkvision/common');
export = {
@@ -40,6 +43,9 @@ export = {
MonkDefaultPalette,
createTheme,
MonkActionType,
+ getFileExtensions,
+ uniq,
+ flatMap,
/* Mocks */
useMonkTheme: jest.fn(() => createTheme()),
@@ -73,6 +79,16 @@ export = {
})),
useResponsiveStyle: jest.fn(() => ({ responsive: jest.fn(() => null) })),
useWindowDimensions: jest.fn(() => ({ width: 0, height: 0, isPortrait: false })),
- useObjectTranslation: jest.fn(() => ({ tObj: jest.fn(() => {}) })),
- useSightLabel: jest.fn(() => ({ label: jest.fn(() => {}) })),
+ useObjectTranslation: jest.fn(() => ({ tObj: jest.fn(() => '') })),
+ useSightLabel: jest.fn(() => ({ label: jest.fn(() => '') })),
+ useAsyncEffect: jest.fn(),
+ useLoadingState: jest.fn(() => ({
+ isLoading: false,
+ error: null,
+ start: jest.fn(),
+ onSuccess: jest.fn(),
+ onError: jest.fn(),
+ })),
+ useLangProp: jest.fn(),
+ isMobileDevice: jest.fn(() => false),
};
diff --git a/packages/private/test-utils/src/__mocks__/@monkvision/network.ts b/packages/private/test-utils/src/__mocks__/@monkvision/network.ts
index 74c62310b..facbbfe87 100644
--- a/packages/private/test-utils/src/__mocks__/@monkvision/network.ts
+++ b/packages/private/test-utils/src/__mocks__/@monkvision/network.ts
@@ -1,9 +1,19 @@
-const { MonkApiPermission } = jest.requireActual('@monkvision/network');
+const { MonkApiPermission, MonkNetworkError } = jest.requireActual('@monkvision/network');
+
+const MonkApi = {
+ getInspection: jest.fn(() => Promise.resolve()),
+ addImage: jest.fn(() => Promise.resolve()),
+ updateTaskStatus: jest.fn(() => Promise.resolve()),
+ startInspectionTasks: jest.fn(() => Promise.resolve()),
+};
export = {
/* Actual exports */
MonkApiPermission,
+ MonkNetworkError,
/* Mocks */
decodeMonkJwt: jest.fn((str) => str),
+ MonkApi,
+ useMonkApi: jest.fn(() => MonkApi),
};
diff --git a/packages/private/test-utils/src/__mocks__/@monkvision/sights.ts b/packages/private/test-utils/src/__mocks__/@monkvision/sights.ts
index 4fb5d143e..d8fd3359e 100644
--- a/packages/private/test-utils/src/__mocks__/@monkvision/sights.ts
+++ b/packages/private/test-utils/src/__mocks__/@monkvision/sights.ts
@@ -1,8 +1,87 @@
-export = {
- /* Actual exports */
+import { TaskName, VehicleModel, VehicleType } from '@monkvision/types';
+
+const vehicles = {
+ [VehicleModel.FESC20]: {
+ id: VehicleModel.FESC20,
+ make: 'Ford',
+ model: 'Escape SE 2020',
+ type: VehicleType.CROSSOVER,
+ },
+};
+
+const labels = {
+ 'test-label-1': {
+ en: 'Test Label 1 EN',
+ fr: 'Test Label 1 FR',
+ de: 'Test Label 1 DE',
+ },
+ 'test-label-2': {
+ en: 'Test Label 2 EN',
+ fr: 'Test Label 2 FR',
+ de: 'Test Label 2 DE',
+ },
+ 'test-label-3': {
+ en: 'Test Label 3 EN',
+ fr: 'Test Label 3 FR',
+ de: 'Test Label 3 DE',
+ },
+ 'test-label-4': {
+ en: 'Test Label 4 EN',
+ fr: 'Test Label 4 FR',
+ de: 'Test Label 4 DE',
+ },
+ 'test-label-5': {
+ en: 'Test Label 5 EN',
+ fr: 'Test Label 5 FR',
+ de: 'Test Label 5 DE',
+ },
+};
+
+const sights = {
+ 'test-sight-1': {
+ id: 'test-sight-1',
+ category: 'exterior',
+ label: 'test-label-1',
+ overlay: ' ',
+ vehicle: VehicleModel.FESC20,
+ tasks: [TaskName.DAMAGE_DETECTION],
+ },
+ 'test-sight-2': {
+ id: 'test-sight-2',
+ category: 'exterior',
+ label: 'test-label-2',
+ overlay: ' ',
+ vehicle: VehicleModel.FESC20,
+ tasks: [TaskName.DAMAGE_DETECTION, TaskName.WHEEL_ANALYSIS],
+ },
+ 'test-sight-3': {
+ id: 'test-sight-3',
+ category: 'exterior',
+ label: 'test-label-3',
+ overlay: ' ',
+ vehicle: VehicleModel.FESC20,
+ tasks: [TaskName.DAMAGE_DETECTION],
+ },
+ 'test-sight-4': {
+ id: 'test-sight-4',
+ category: 'exterior',
+ label: 'test-label-4',
+ overlay: ' ',
+ vehicle: VehicleModel.FESC20,
+ tasks: [TaskName.DAMAGE_DETECTION, TaskName.WHEEL_ANALYSIS],
+ },
+ 'test-sight-5': {
+ id: 'test-sight-5',
+ category: 'exterior',
+ label: 'test-label-5',
+ overlay: ' ',
+ vehicle: VehicleModel.FESC20,
+ tasks: [TaskName.DAMAGE_DETECTION],
+ },
+};
- /* Mocks */
- labels: {},
- vehicles: {},
- sights: {},
+export = {
+ vehicles,
+ labels,
+ sights,
};
diff --git a/packages/private/test-utils/src/__mocks__/ky.ts b/packages/private/test-utils/src/__mocks__/ky.ts
index 7913868b9..eff491f70 100644
--- a/packages/private/test-utils/src/__mocks__/ky.ts
+++ b/packages/private/test-utils/src/__mocks__/ky.ts
@@ -7,12 +7,18 @@ const i18nextInstanceMock: any = {
};
function createMockKyRequestFn() {
- return jest.fn(() =>
- Promise.resolve({
- status: 200,
+ return jest.fn(() => {
+ const helpers = {
json: jest.fn(() => Promise.resolve({})),
- }),
- );
+ blob: jest.fn(() => Promise.resolve(new Blob())),
+ };
+ const response = Promise.resolve({
+ status: 200,
+ ...helpers,
+ });
+ Object.assign(response, helpers);
+ return response;
+ });
}
export = {
@@ -20,4 +26,6 @@ export = {
/* Mocks */
get: createMockKyRequestFn(),
+ post: createMockKyRequestFn(),
+ patch: createMockKyRequestFn(),
};
diff --git a/packages/private/test-utils/src/__mocks__/react-i18next.tsx b/packages/private/test-utils/src/__mocks__/react-i18next.tsx
index b4ab45fe9..ecc0eb954 100644
--- a/packages/private/test-utils/src/__mocks__/react-i18next.tsx
+++ b/packages/private/test-utils/src/__mocks__/react-i18next.tsx
@@ -4,5 +4,8 @@ export = {
/* Mocks */
initReactI18next: {},
I18nextProvider: jest.fn(({ children }) => <>{children}>),
- useTranslation: jest.fn(() => ({ t: jest.fn((str) => str), i18n: { language: 'en' } })),
+ useTranslation: jest.fn(() => ({
+ t: jest.fn((str) => str),
+ i18n: { language: 'en', changeLanguage: jest.fn(() => Promise.resolve()) },
+ })),
};
diff --git a/packages/private/test-utils/src/async.ts b/packages/private/test-utils/src/async.ts
new file mode 100644
index 000000000..3495fa394
--- /dev/null
+++ b/packages/private/test-utils/src/async.ts
@@ -0,0 +1,18 @@
+export interface FakePromise extends Promise {
+ resolve: (value: T) => void;
+ reject: (err?: any) => void;
+}
+
+export function createFakePromise(): FakePromise {
+ let manualResolve: (value: T) => void = () => {};
+ let manualReject: (err?: any) => void = () => {};
+ const promise = new Promise((resolve, reject) => {
+ manualResolve = resolve;
+ manualReject = reject;
+ });
+ Object.assign(promise, {
+ resolve: manualResolve,
+ reject: manualReject,
+ });
+ return promise as FakePromise;
+}
diff --git a/packages/private/test-utils/src/index.ts b/packages/private/test-utils/src/index.ts
index 55d594f24..58b6d26e6 100644
--- a/packages/private/test-utils/src/index.ts
+++ b/packages/private/test-utils/src/index.ts
@@ -1,3 +1,4 @@
export * from './expects';
export * from './dom';
export * from './styles';
+export * from './async';
diff --git a/packages/public/camera-web/README.md b/packages/public/camera-web/README.md
index ff77d50d3..52772bcd7 100644
--- a/packages/public/camera-web/README.md
+++ b/packages/public/camera-web/README.md
@@ -34,24 +34,18 @@ function MyCameraPreview() {
```
## Camera constraints
-The video stream of the camera that is fetched from the user's device is configurable in the following ways :
-- Resolution quality of the camera
-- Camera facing mode (front camera / rear camera on smartphones)
-- Device ID to choose a specific camera
+The resolution quality of the camera of the Camera video stream that is fetched from the user's device is configurable
+by passing it as a prop to the Camera component. Note that device selection (selecting which Camera will be used when
+the device has many available) is disabled for now. This is because this is instead handled automatically by the
+component in order to prevent unusable cameras (zoomed ones for instance) to be used.
-These values can be configured using props on the camera component in the following ways :
+Example of how to configure the resolution of the Camera :
```tsx
-import { Camera, CameraFacingMode, CameraResolution } from '@monkvision/camera-web';
+import { Camera, CameraResolution } from '@monkvision/camera-web';
function MyCameraPreview() {
- return (
-
- );
+ return ;
}
```
@@ -62,7 +56,9 @@ Notes :
permissions another time
- Only the resolutions available in the `CameraResolution` enum are allowed for better results with our AI models
- The resolutions available in the `CameraResolution` enum are all in the 16:9 format
-- If the `deviceId` prop is specificied, the `facingMode` will be ignored
+- Device selection (selecting which Camera will be used when the device has many available) is disabled for now. This is
+ because this is instead handled automatically by the component in order to prevent unusable cameras (zoomed ones for
+ instance) to be used.
- If no device meets the given requirements, the device with the closest match will be used :
- If the needed resolution is too high, the highest resolution will be used : this means that asking for the
`CameraResolution.UHD_4K` resolution is a good way to get the highest resolution possible
@@ -84,9 +80,10 @@ For more details on the compression options, see the *API* section below.
## Camera HUD
In order to control the Camera (take pictures etc.), an HUD component can be passed as a prop to the Camera component.
-An HUD component is a component that takes a camera handle as a prop (an object used to control the camera) and that
-displays head-up display on top of the camera preview. When a HUD component is passed to the Camera component, the
-Camera will place it on top of its preview, and will pass him down the camera handle.
+An HUD component is a component that takes a camera handle (an object used to control the camera) as well as
+a camera preview element as props and that will display the preview with some additional head-up display on top of it.
+When a HUD component is passed to the Camera component, the Camera will place it in the tree, and will pass him down the
+camera handle and preview already configured.
It is definitely possible to write your own custom Camera HUD component, but if you just need a very basic Camera HUD,
this package exports one already : the `SimpleCameraHUD` component. It will display a button to take the pictures, error
@@ -109,17 +106,13 @@ customize the display language, you have two options :
your `i18n` instance with the one used by the Camera package. To do so, we highly recommend using the `i18n` utility
tools provided by the `@monkvision/common` package. More information on this
[here](https://github.com/monkvision/monkjs/blob/main/packages/common/README/INTERNATIONALIZATION.md).
-- Simply specify the fixed language you want to use by using the `language` prop of the component like this :
+- Simply specify the fixed language you want to use by using the `lang` prop of the component like this :
```tsx
import { Camera, SimpleCameraHUD } from '@monkvision/camera-web';
function MyCameraPreviewWithGermanHUD() {
- return (
- }
- />
- );
+ return ;
}
```
@@ -129,18 +122,34 @@ The currently supported languages are :
- German : `'de'`
## Creating a Custom HUD
-In order to implement custom Camera HUD, you simply need to create a component that will take a camera handle as a
-prop :
+In order to implement custom Camera HUD, you simply need to create a component that will take a camera handle and a
+camera preview as props. Additional props can be passed to this HUD component using the `hudProps` prop of the Camera
+component :
```tsx
import { Camera, CameraHUDProps } from '@monkvision/camera-web';
-function MyCustomCameraHUD({ handle }: CameraHUDProps) {
- return Take Picture ;
+interface MyCustomCameraHUD extends CameraHUDProps {
+ message: string;
+}
+
+function MyCustomCameraHUD({ handle, cameraPreview, message }: MyCustomCameraHUD) {
+ return (
+
+ {cameraPreview}
+
Take Picture
+
{message}
+
+ );
}
function MyCameraPreviewWithCustomHUD() {
- return ;
+ return (
+
+ );
}
```
@@ -148,7 +157,7 @@ The complete specification of the camera handle is available in the *API* sectio
## Camera Events
In order to add side effects when some things happen in the Camera, you can specify event handlers callbacks in the
-Camera component props. For now the events available are :
+Camera component props. For now the supported events available are :
- `onPictureTaken: (picture: MonkPicture) => void` : Callback called when the user takes a picture
@@ -160,12 +169,11 @@ Main component exported by this package, displays a Camera preview and the given
### Props
| Prop | Type | Description | Required | Default Value |
|----------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------|----------|--------------------------------|
-| facingMode | CameraFacingMode | Facing mode (front camera or back camera) to specify which camera to use. Ignored if `deviceId` is specified | | `CameraFacingMode.ENVIRONMENT` |
| resolution | CameraResolution | Resolution of the camera to use. | | `CameraResolution.UHD_4K` |
-| deviceId | string | The ID of the camera to use. | | |
| format | CompressionFormat | The compression format used to compress images taken by the camera. | | `CompressionFormat.JPEG` |
| quality | number | The image quality when using a compression format that supports lossy compression. From 0 (lowest quality) to 1 (best quality). | | `0.8` |
-| HUDComponent | CameraHUDComponent | The camera HUD component to display on top of the camera preview. | | |
+| HUDComponent | CameraHUDComponent | The camera HUD component to display on top of the camera preview. | | |
+| hudProps | T | Additional props passed down to the Camera HUD component. | | |
| onPictureTaken | (picture: MonkPicture) => void | Callback called when a picture has been taken by the user. | | |
| monitoring | CameraMonitoringConfig | Extra options that can be passed to configure how the monitoring is handled in the component. | | |
@@ -180,3 +188,4 @@ Object passed to Camera HUD components that is used to control the camera
| error | UserMediaError | null | The error details if there has been an error when fetching the camera stream. |
| isLoading | boolean | Boolean indicating if the camera preview is loading. |
| retry | () => void | A function to retry the camera stream fetching in case of error. |
+| dimensions | PixelDimensions | The Camera stream dimensions (`null` if there is no stream). |
diff --git a/packages/public/camera-web/src/Camera/Camera.tsx b/packages/public/camera-web/src/Camera/Camera.tsx
index 1a6b1b4e4..8d2f77177 100644
--- a/packages/public/camera-web/src/Camera/Camera.tsx
+++ b/packages/public/camera-web/src/Camera/Camera.tsx
@@ -1,4 +1,5 @@
import React, { useMemo } from 'react';
+import { AllOrNone, RequiredKeys } from '@monkvision/types';
import {
CameraConfig,
CameraFacingMode,
@@ -16,21 +17,45 @@ import { styles } from './Camera.styles';
import { CameraEventHandlers, CameraHUDComponent } from './CameraHUD.types';
/**
- * Props given to the Camera component.
+ * Type definition for the HUD component and its props passed to the Camera component. Monk uses this custom type in
+ * order to enforce typing on the following configurations :
+ *
+ * - If the HUD component does not have any required props, the Camera component will allow developers to pass it either
+ * the HUDComponent or both the component and its props.
+ * - If the HUD component does indeed have required props, the Camera component will only accept either BOTH the
+ * HUDComponent and its props, or none of them.
+ *
+ * This is done in order to ensure that developers do not pass HUD components that need specific props to be rendered to
+ * the Camera without actually passing those props as well.
*/
-export interface CameraProps
- extends Partial>,
- Partial,
- CameraEventHandlers {
- /**
- * Optional HUD component to display above the camera preview.
- */
- HUDComponent?: CameraHUDComponent;
- /**
- * Additional monitoring config that can be provided to the Camera component.
- */
- monitoring?: CameraMonitoringConfig;
-}
+export type HUDConfigProps = RequiredKeys extends never
+ ? {
+ /**
+ * HUD component to display above the camera preview.
+ *
+ * Note: If this component needs custom props to be rendered, don't forget to pass them to the Camera in the
+ * `hudProps` props.
+ */
+ HUDComponent?: CameraHUDComponent;
+ /**
+ * Additional props passed to the HUD component when it will be rendered.
+ */
+ hudProps?: T;
+ }
+ : AllOrNone<{ HUDComponent: CameraHUDComponent; hudProps: T }>;
+
+/**
+ * Props given to the Camera component. The generic T type corresponds to the prop types of the HUD.
+ */
+export type CameraProps = Partial> &
+ Partial &
+ CameraEventHandlers &
+ HUDConfigProps & {
+ /**
+ * Additional monitoring config that can be provided to the Camera component.
+ */
+ monitoring?: CameraMonitoringConfig;
+ };
/**
* Component used in MonkJs project used to :
@@ -42,14 +67,15 @@ export interface CameraProps
* [here](https://github.com/monkvision/monkjs/blob/main/packages/camera-web/README.md)) for more details on how this
* component works.
*/
-export function Camera({
+export function Camera({
resolution = CameraResolution.UHD_4K,
format = CompressionFormat.JPEG,
quality = 0.8,
HUDComponent,
+ hudProps,
monitoring,
onPictureTaken,
-}: CameraProps) {
+}: CameraProps) {
const {
ref: videoRef,
dimensions,
@@ -88,6 +114,7 @@ export function Camera({
) : (
<>{cameraPreview}>
diff --git a/packages/public/camera-web/src/Camera/CameraHUD.types.ts b/packages/public/camera-web/src/Camera/CameraHUD.types.ts
index 3273dcfdd..f36bc7769 100644
--- a/packages/public/camera-web/src/Camera/CameraHUD.types.ts
+++ b/packages/public/camera-web/src/Camera/CameraHUD.types.ts
@@ -53,33 +53,12 @@ export interface CameraHUDProps {
/**
* The handle used to control the camera.
*/
- handle?: Partial;
+ handle: CameraHandle;
}
/**
* Component type definition for a Camera HUD component.
*/
-export type CameraHUDComponent = ComponentType;
-
-/**
- * Element type definition for a Camera HUD component.
- */
-export type CameraHUDElement = ReactElement;
-
-/**
- * Parameters passed to the useCameraHUD hook.
- */
-export interface UseCameraHUDParams {
- /**
- * The camera handle used to control the camera.
- */
- handle: CameraHandle;
- /**
- * The preview of the camera as a React element.
- */
- cameraPreview: ReactElement;
- /**
- * The camera HUD component.
- */
- component?: CameraHUDComponent;
-}
+export type CameraHUDComponent> = ComponentType<
+ CameraHUDProps & T
+>;
diff --git a/packages/public/camera-web/src/Camera/hooks/useCameraPreview.ts b/packages/public/camera-web/src/Camera/hooks/useCameraPreview.ts
index 095a273e1..c06d28531 100644
--- a/packages/public/camera-web/src/Camera/hooks/useCameraPreview.ts
+++ b/packages/public/camera-web/src/Camera/hooks/useCameraPreview.ts
@@ -26,7 +26,7 @@ export function useCameraPreview(config: CameraConfig): CameraPreviewHandle {
if (userMediaResult.stream && ref.current) {
ref.current.srcObject = userMediaResult.stream;
ref.current.onloadedmetadata = () => {
- ref.current?.play().catch((err) => handleError(err));
+ ref.current?.play().catch(handleError);
};
}
}, [userMediaResult.stream]);
diff --git a/packages/public/camera-web/src/Camera/hooks/useUserMedia.ts b/packages/public/camera-web/src/Camera/hooks/useUserMedia.ts
index 8fc4239c9..f6c0fab3d 100644
--- a/packages/public/camera-web/src/Camera/hooks/useUserMedia.ts
+++ b/packages/public/camera-web/src/Camera/hooks/useUserMedia.ts
@@ -231,7 +231,7 @@ export function useUserMedia(constraints: MediaStreamConstraints): UserMediaResu
throw err;
}
};
- getUserMedia().catch((err) => handleError(err));
+ getUserMedia().catch(handleError);
}, [constraints, stream, error, isLoading, lastConstraintsApplied, onStreamInactive]);
useEffect(() => {
diff --git a/packages/public/camera-web/src/SimpleCameraHUD/SimpleCameraHUD.tsx b/packages/public/camera-web/src/SimpleCameraHUD/SimpleCameraHUD.tsx
index 3bfdfc396..cf49757a5 100644
--- a/packages/public/camera-web/src/SimpleCameraHUD/SimpleCameraHUD.tsx
+++ b/packages/public/camera-web/src/SimpleCameraHUD/SimpleCameraHUD.tsx
@@ -1,61 +1,63 @@
import { useTranslation } from 'react-i18next';
-import { i18nWrap, useResponsiveStyle } from '@monkvision/common';
+import {
+ i18nWrap,
+ useLangProp,
+ useObjectTranslation,
+ useResponsiveStyle,
+} from '@monkvision/common';
import { Button, TakePictureButton } from '@monkvision/common-ui-web';
import { i18nCamera } from '../i18n';
-import { CameraHUDProps, UserMediaErrorType } from '../Camera';
+import { CameraHUDProps } from '../Camera';
import { styles } from './SimpleCameraHUD.styles';
+import { getCameraErrorLabel } from '../utils';
-function getErrorTranslationKey(error?: UserMediaErrorType): string {
- switch (error) {
- case UserMediaErrorType.NOT_ALLOWED:
- return 'errors.permission';
- case UserMediaErrorType.STREAM_INACTIVE:
- return 'errors.inactive';
- case UserMediaErrorType.INVALID_STREAM:
- return 'errors.invalid';
- case UserMediaErrorType.OTHER:
- return 'errors.other';
- default:
- return 'errors.other';
- }
-}
+/**
+ * Props accepted by the SimpleCameraHUD component.
+ */
+export type SimpleCameraHUDProps = CameraHUDProps & {
+ /**
+ * This prop can be used to specify the language to be used by the SimpleCameraHUD component.
+ */
+ lang?: string;
+};
/**
* The basic Camera HUD provided by the Monk camera package. It displays a button to take pictures, as well as error
* messages (and a retry button) in case of errors with the Camera stream.
*/
-export const SimpleCameraHUD = i18nWrap(({ cameraPreview, handle }: CameraHUDProps) => {
+export const SimpleCameraHUD = i18nWrap(({ cameraPreview, handle, lang }: SimpleCameraHUDProps) => {
+ useLangProp(lang);
const { t } = useTranslation();
+ const { tObj } = useObjectTranslation();
const { responsive } = useResponsiveStyle();
- const isHUDDisabled = handle?.isLoading || !!handle?.error;
+ const isHUDDisabled = handle.isLoading || !!handle.error;
+ const errorLabel = getCameraErrorLabel(handle.error?.type);
return (
{cameraPreview}
- {!handle?.isLoading && !!handle?.error && (
+ {!handle.isLoading && !!handle.error && errorLabel && (
- {t(getErrorTranslationKey(handle?.error?.type))}
+ {tObj(errorLabel)}
- {handle?.retry && (
-
- {t('retry')}
-
- )}
+
+ {t('retry')}
+
)}
diff --git a/packages/public/camera-web/src/i18n.ts b/packages/public/camera-web/src/i18n.ts
index d087a5139..c32ee79b8 100644
--- a/packages/public/camera-web/src/i18n.ts
+++ b/packages/public/camera-web/src/i18n.ts
@@ -3,6 +3,10 @@ import en from './translations/en.json';
import fr from './translations/fr.json';
import de from './translations/de.json';
+/**
+ * i18n instance of the Camera package. You can use this instance to automatically sync your application current
+ * language with the one used by the components of the package.
+ */
export const i18nCamera = i18nCreateSDKInstance({
resources: {
en: { translation: en },
diff --git a/packages/public/camera-web/src/index.ts b/packages/public/camera-web/src/index.ts
index f019abc41..53fb1316e 100644
--- a/packages/public/camera-web/src/index.ts
+++ b/packages/public/camera-web/src/index.ts
@@ -1,3 +1,4 @@
export * from './Camera';
export * from './SimpleCameraHUD';
+export * from './utils';
export * from './i18n';
diff --git a/packages/public/camera-web/src/translations/de.json b/packages/public/camera-web/src/translations/de.json
index 3f5ba8893..05c267fb0 100644
--- a/packages/public/camera-web/src/translations/de.json
+++ b/packages/public/camera-web/src/translations/de.json
@@ -1,9 +1,3 @@
{
- "retry": "Erneut versuchen",
- "errors": {
- "permission": "Die Kameravorschau ist nicht verfügbar, da für die Seite kein Kamerazugriff gewährt wurde.",
- "inactive": "Der Video-Stream der Kamera wurde unerwartet geschlossen.",
- "invalid": "Der Videostrom der Kamera kann nicht verarbeitet werden.",
- "other": "Beim Abrufen des Kamera-Videostreams ist ein unerwarteter Fehler aufgetreten."
- }
+ "retry": "Erneut versuchen"
}
diff --git a/packages/public/camera-web/src/translations/en.json b/packages/public/camera-web/src/translations/en.json
index 8e6e33a47..fd62eb0de 100644
--- a/packages/public/camera-web/src/translations/en.json
+++ b/packages/public/camera-web/src/translations/en.json
@@ -1,9 +1,3 @@
{
- "retry": "Retry",
- "errors": {
- "permission": "Camera preview unavailable because camera access was not granted to the page.",
- "inactive": "The camera video stream was closed unexpectedly.",
- "invalid": "Unable to process the camera video stream.",
- "other": "An unexpected error occurred when fetching the camera video stream."
- }
+ "retry": "Retry"
}
diff --git a/packages/public/camera-web/src/translations/fr.json b/packages/public/camera-web/src/translations/fr.json
index 0c02861b0..a44b00a4b 100644
--- a/packages/public/camera-web/src/translations/fr.json
+++ b/packages/public/camera-web/src/translations/fr.json
@@ -1,9 +1,3 @@
{
- "retry": "Réessayer",
- "errors": {
- "permission": "L'apperçu de la caméra n'est pas disponible car l'accès à la caméra n'est pas autorisé.",
- "inactive": "Le flux vidéo de la caméra a été coupé de manière inattendue.",
- "invalid": "Impossible de traiter le flux vidéo de la caméra.",
- "other": "Une erreur inattendue est survenue lors de la récupération du flux vidéo de la caméra."
- }
+ "retry": "Réessayer"
}
diff --git a/packages/public/camera-web/src/utils/errors.utils.ts b/packages/public/camera-web/src/utils/errors.utils.ts
new file mode 100644
index 000000000..f41822d9c
--- /dev/null
+++ b/packages/public/camera-web/src/utils/errors.utils.ts
@@ -0,0 +1,35 @@
+import { TranslationObject } from '@monkvision/types';
+import { UserMediaErrorType } from '../Camera';
+
+/**
+ * Get a translation object (that can be translated using the useObjectTranslation hook) that contains the translations
+ * for the error label corresponding to the camera error passed as an argument of this function.
+ */
+export function getCameraErrorLabel(error?: UserMediaErrorType): TranslationObject | null {
+ switch (error) {
+ case UserMediaErrorType.NOT_ALLOWED:
+ return {
+ en: 'Camera preview unavailable because camera access was not granted to the page.',
+ fr: "L'apperçu de la caméra n'est pas disponible car l'accès à la caméra n'est pas autorisé.",
+ de: 'Die Kameravorschau ist nicht verfügbar, da für die Seite kein Kamerazugriff gewährt wurde.',
+ };
+ case UserMediaErrorType.STREAM_INACTIVE:
+ return {
+ en: 'The camera video stream was closed unexpectedly.',
+ fr: 'Le flux vidéo de la caméra a été coupé de manière inattendue.',
+ de: 'Der Video-Stream der Kamera wurde unerwartet geschlossen.',
+ };
+ case UserMediaErrorType.INVALID_STREAM:
+ return {
+ en: 'Unable to process the camera video stream.',
+ fr: 'Impossible de traiter le flux vidéo de la caméra.',
+ de: 'Der Videostrom der Kamera kann nicht verarbeitet werden.',
+ };
+ default:
+ return {
+ en: 'An unexpected error occurred when fetching the camera video stream.',
+ fr: 'Une erreur inattendue est survenue lors de la récupération du flux vidéo de la caméra.',
+ de: 'Beim Abrufen des Kamera-Videostreams ist ein unerwarteter Fehler aufgetreten.',
+ };
+ }
+}
diff --git a/packages/public/camera-web/src/utils/index.ts b/packages/public/camera-web/src/utils/index.ts
new file mode 100644
index 000000000..8219cae95
--- /dev/null
+++ b/packages/public/camera-web/src/utils/index.ts
@@ -0,0 +1 @@
+export * from './errors.utils';
diff --git a/packages/public/camera-web/test/Camera/Camera.test.tsx b/packages/public/camera-web/test/Camera/Camera.test.tsx
index 5024d7062..324c71d9b 100644
--- a/packages/public/camera-web/test/Camera/Camera.test.tsx
+++ b/packages/public/camera-web/test/Camera/Camera.test.tsx
@@ -194,6 +194,15 @@ describe('Camera component', () => {
unmount();
});
+ it('should pass the HUDProps to the HUDComponent', () => {
+ const HUDComponent = jest.fn(() => <>>) as unknown as CameraHUDComponent<{ test: string }>;
+ const props = { test: 'hello' };
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(HUDComponent as jest.Mock, props);
+ unmount();
+ });
+
it('should not be loading when both the preview and the take picture are not loading', () => {
const HUDComponent = jest.fn(() => <>>) as unknown as CameraHUDComponent;
const { unmount } = render( );
diff --git a/packages/public/camera-web/test/Camera/hooks/useCameraPreview.test.tsx b/packages/public/camera-web/test/Camera/hooks/useCameraPreview.test.tsx
index f59532aee..7204682ec 100644
--- a/packages/public/camera-web/test/Camera/hooks/useCameraPreview.test.tsx
+++ b/packages/public/camera-web/test/Camera/hooks/useCameraPreview.test.tsx
@@ -1,4 +1,3 @@
-jest.mock('@monkvision/monitoring');
jest.mock('../../../src/Camera/hooks/utils', () => ({
...jest.requireActual('../../../src/Camera/hooks/utils'),
getMediaConstraints: jest.fn(() => ({ audio: false, video: true })),
diff --git a/packages/public/camera-web/test/Camera/hooks/useCompression.test.ts b/packages/public/camera-web/test/Camera/hooks/useCompression.test.ts
index d37b328a6..232c645c1 100644
--- a/packages/public/camera-web/test/Camera/hooks/useCompression.test.ts
+++ b/packages/public/camera-web/test/Camera/hooks/useCompression.test.ts
@@ -1,5 +1,3 @@
-import { getCanvasHandle } from '../../../src/Camera/hooks/utils';
-
jest.mock('../../../src/Camera/hooks/utils/getCanvasHandle', () => ({
getCanvasHandle: jest.fn(() => ({
canvas: { toDataURL: jest.fn(() => 'picture,test-url') },
@@ -24,6 +22,8 @@ import {
} from '../../../src/Camera/monitoring';
import { createMockInternalMonitoringConfig } from '../../mocks';
+const { getCanvasHandle } = jest.requireActual('../../../src/Camera/hooks/utils');
+
const monitoringMock = createMockInternalMonitoringConfig();
const mockImageData = {
width: 123,
diff --git a/packages/public/camera-web/test/Camera/hooks/useTakePicture.test.ts b/packages/public/camera-web/test/Camera/hooks/useTakePicture.test.ts
index 6f445ad3a..b94b1c6ab 100644
--- a/packages/public/camera-web/test/Camera/hooks/useTakePicture.test.ts
+++ b/packages/public/camera-web/test/Camera/hooks/useTakePicture.test.ts
@@ -1,5 +1,3 @@
-jest.mock('@monkvision/monitoring');
-
import { TransactionStatus, useMonitoring } from '@monkvision/monitoring';
import { act } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
diff --git a/packages/public/camera-web/test/Camera/hooks/useUserMedia.test.ts b/packages/public/camera-web/test/Camera/hooks/useUserMedia.test.ts
index 58e153df5..adc53bd02 100644
--- a/packages/public/camera-web/test/Camera/hooks/useUserMedia.test.ts
+++ b/packages/public/camera-web/test/Camera/hooks/useUserMedia.test.ts
@@ -1,17 +1,10 @@
-import { useMonitoring } from '@monkvision/monitoring';
-
-jest.mock('@monkvision/monitoring');
-jest.mock('@monkvision/common', () => ({
- ...jest.requireActual('@monkvision/common'),
- isMobileDevice: jest.fn(() => false),
-}));
-
import { act, waitFor } from '@testing-library/react';
+import { isMobileDevice } from '@monkvision/common';
import { renderHook } from '@testing-library/react-hooks';
+import { useMonitoring } from '@monkvision/monitoring';
import { UserMediaErrorType } from '../../../src';
import { InvalidStreamErrorName, useUserMedia } from '../../../src/Camera/hooks';
import { GetUserMediaMock, mockGetUserMedia } from '../../mocks';
-import { isMobileDevice } from '@monkvision/common';
describe('useUserMedia hook', () => {
let gumMock: GetUserMediaMock | null = null;
diff --git a/packages/public/camera-web/test/SimpleCameraHUD.test.tsx b/packages/public/camera-web/test/SimpleCameraHUD.test.tsx
index 5bb6ba335..883bc3d14 100644
--- a/packages/public/camera-web/test/SimpleCameraHUD.test.tsx
+++ b/packages/public/camera-web/test/SimpleCameraHUD.test.tsx
@@ -1,17 +1,26 @@
-import { i18nWrap } from '@monkvision/common';
-
-jest.mock('react-i18next');
-jest.mock('@monkvision/common');
-jest.mock('@monkvision/common-ui-web');
jest.mock('../src/i18n', () => ({
i18nCamera: {},
}));
+jest.mock('../src/utils', () => ({
+ getCameraErrorLabel: jest.fn(() => ({
+ en: 'en',
+ fr: 'fr',
+ de: 'de',
+ })),
+}));
+import { i18nWrap, useLangProp, useObjectTranslation } from '@monkvision/common';
import { expectPropsOnChildMock } from '@monkvision/test-utils';
import { render, screen } from '@testing-library/react';
import { Button, TakePictureButton } from '@monkvision/common-ui-web';
import { useTranslation } from 'react-i18next';
-import { i18nCamera, SimpleCameraHUD, UserMediaErrorType } from '../src';
+import {
+ CameraHandle,
+ getCameraErrorLabel,
+ i18nCamera,
+ SimpleCameraHUD,
+ UserMediaErrorType,
+} from '../src';
const ERROR_MESSAGE_TEST_ID = 'error-message';
@@ -21,7 +30,9 @@ describe('SimpleCameraHUD component', () => {
});
it('should wrap the component with the i18nWrap method', () => {
- const { unmount } = render(>} />);
+ const { unmount } = render(
+ >} handle={{} as CameraHandle} />,
+ );
expect(i18nWrap).toHaveBeenCalledWith(expect.any(Function), i18nCamera);
unmount();
@@ -30,7 +41,10 @@ describe('SimpleCameraHUD component', () => {
it('should display the camera preview on the screen', () => {
const cameraPreviewTestId = 'camera-preview-test-id';
const { unmount } = render(
- } />,
+ }
+ handle={{} as CameraHandle}
+ />,
);
expect(screen.queryByTestId(cameraPreviewTestId)).not.toBeNull();
@@ -38,22 +52,35 @@ describe('SimpleCameraHUD component', () => {
});
it('should disable the take picture button if the camera is loading', () => {
- const { unmount, rerender } = render(>} />);
+ const { unmount, rerender } = render(
+ >} handle={{} as CameraHandle} />,
+ );
expectPropsOnChildMock(TakePictureButton, { disabled: false });
- rerender(>} handle={{ isLoading: true }} />);
+ rerender(
+ >}
+ handle={{ isLoading: true } as unknown as CameraHandle}
+ />,
+ );
expectPropsOnChildMock(TakePictureButton, { disabled: true });
unmount();
});
it('should disable the take picture button if the camera is in error', () => {
- const { unmount, rerender } = render(>} />);
+ const { unmount, rerender } = render(
+ >} handle={{} as CameraHandle} />,
+ );
expectPropsOnChildMock(TakePictureButton, { disabled: false });
rerender(
>}
- handle={{ error: { type: UserMediaErrorType.INVALID_STREAM, nativeError: null } }}
+ handle={
+ {
+ error: { type: UserMediaErrorType.INVALID_STREAM, nativeError: null },
+ } as unknown as CameraHandle
+ }
/>,
);
expectPropsOnChildMock(TakePictureButton, { disabled: true });
@@ -61,14 +88,16 @@ describe('SimpleCameraHUD component', () => {
});
it('should display a take picture button', () => {
- const { unmount } = render(>} />);
+ const { unmount } = render(
+ >} handle={{} as CameraHandle} />,
+ );
expect(TakePictureButton).toHaveBeenCalled();
unmount();
});
it('should pass the take picture callback from the handle to the TakePictureButton', () => {
- const handle = { takePicture: jest.fn() };
+ const handle = { takePicture: jest.fn() } as unknown as CameraHandle;
const { unmount } = render(>} handle={handle} />);
expectPropsOnChildMock(TakePictureButton, { onClick: handle.takePicture });
@@ -76,36 +105,38 @@ describe('SimpleCameraHUD component', () => {
});
it('should not pass any onClick event to the TakePictureButton by default', () => {
- const { unmount } = render(>} />);
+ const { unmount } = render(
+ >} handle={{} as CameraHandle} />,
+ );
expectPropsOnChildMock(TakePictureButton, { onClick: undefined });
unmount();
});
it('should set the size of the TakePictureButton to 60', () => {
- const { unmount } = render(>} />);
+ const { unmount } = render(
+ >} handle={{} as CameraHandle} />,
+ );
expectPropsOnChildMock(TakePictureButton, { size: 60 });
unmount();
});
it('should display the proper error message when the camera is in error', () => {
- const expectedTranslations: { [key in UserMediaErrorType]: string } = {
- [UserMediaErrorType.NOT_ALLOWED]: 'errors.permission',
- [UserMediaErrorType.STREAM_INACTIVE]: 'errors.inactive',
- [UserMediaErrorType.INVALID_STREAM]: 'errors.invalid',
- [UserMediaErrorType.OTHER]: 'errors.other',
- };
Object.values(UserMediaErrorType).forEach((type) => {
jest.clearAllMocks();
const { unmount } = render(
- >} handle={{ error: { type, nativeError: null } }} />,
+ >}
+ handle={{ error: { type, nativeError: null } } as unknown as CameraHandle}
+ />,
);
- const tMock = (useTranslation as jest.Mock).mock.results[0].value.t;
+ const tObjMock = (useObjectTranslation as jest.Mock).mock.results[0].value.tObj;
const errorMsg = screen.getByTestId(ERROR_MESSAGE_TEST_ID);
- expect(errorMsg.textContent).toEqual(expectedTranslations[type]);
- expect(tMock).toHaveBeenCalledWith(expectedTranslations[type]);
+ expect(errorMsg.textContent).toEqual(tObjMock());
+ expect(getCameraErrorLabel).toHaveBeenCalledWith(type);
+ expect(tObjMock).toHaveBeenCalledWith(getCameraErrorLabel(type));
unmount();
});
});
@@ -114,7 +145,7 @@ describe('SimpleCameraHUD component', () => {
const handle = {
error: { type: UserMediaErrorType.INVALID_STREAM, nativeError: null },
retry: () => {},
- };
+ } as unknown as CameraHandle;
const { unmount } = render(>} handle={handle} />);
expectPropsOnChildMock(Button, { onClick: handle.retry });
@@ -125,7 +156,7 @@ describe('SimpleCameraHUD component', () => {
const handle = {
error: { type: UserMediaErrorType.INVALID_STREAM, nativeError: null },
retry: () => {},
- };
+ } as unknown as CameraHandle;
const { unmount } = render(>} handle={handle} />);
const tMock = (useTranslation as jest.Mock).mock.results[0].value.t;
@@ -134,13 +165,14 @@ describe('SimpleCameraHUD component', () => {
unmount();
});
- it('should not display the retry button if the retry function is not defined', () => {
- const handle = {
- error: { type: UserMediaErrorType.INVALID_STREAM, nativeError: null },
- };
- const { unmount } = render(>} handle={handle} />);
+ it('should call the useLangProp hook with the lang prop', () => {
+ const lang = 'fr';
+ const { unmount } = render(
+ >} handle={{} as CameraHandle} lang={lang} />,
+ );
+
+ expect(useLangProp).toHaveBeenCalledWith(lang);
- expect(Button).not.toHaveBeenCalled();
unmount();
});
});
diff --git a/packages/public/camera-web/test/utils/errors.utils.test.ts b/packages/public/camera-web/test/utils/errors.utils.test.ts
new file mode 100644
index 000000000..27fc80dcb
--- /dev/null
+++ b/packages/public/camera-web/test/utils/errors.utils.test.ts
@@ -0,0 +1,31 @@
+import { getCameraErrorLabel, UserMediaErrorType } from '../../src';
+
+describe('Camera error utils', () => {
+ describe('getCameraErrorLabel util function', () => {
+ it('should return an error label for each error type', () => {
+ Object.values(UserMediaErrorType).forEach((type) => {
+ expect(getCameraErrorLabel(type)).toEqual({
+ en: expect.any(String),
+ fr: expect.any(String),
+ de: expect.any(String),
+ });
+ });
+ });
+
+ it('should return an error label for unknown error types', () => {
+ expect(getCameraErrorLabel('test' as UserMediaErrorType)).toEqual({
+ en: expect.any(String),
+ fr: expect.any(String),
+ de: expect.any(String),
+ });
+ });
+
+ it('should return an error label even when provided nothing', () => {
+ expect(getCameraErrorLabel()).toEqual({
+ en: expect.any(String),
+ fr: expect.any(String),
+ de: expect.any(String),
+ });
+ });
+ });
+});
diff --git a/packages/public/common-ui-web/README.md b/packages/public/common-ui-web/README.md
index d47da5a76..601e078bc 100644
--- a/packages/public/common-ui-web/README.md
+++ b/packages/public/common-ui-web/README.md
@@ -10,6 +10,52 @@ yarn add @monkvision/common-ui-web
```
# Available components
+## BackdropDialog
+### Description
+This component can be used to display a fixed dialog on the screen, with a backdrop behind it. You can either pass a
+custom component for the dialog modal, or use the default one and simply pass the text content of the dialog.
+
+### Example
+```tsx
+import { BackdropDialog } from '@monkvision/common-ui-web';
+
+function App() {
+ const [showDialog, setShowDialog] = useState(false);
+
+ const onConfirm = () => {
+ console.log('Confirmed.');
+ setShowDialog(false);
+ }
+
+ return (
+ setShowDialog(false)}
+ />
+ );
+}
+```
+
+### Props
+| Prop | Type | Description | Required | Default Value |
+|-----------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------|
+| show | boolean | Boolean indicating if the backdrop dialog is displayed on the screen. | | `false` |
+| backdropOpacity | number | Number between 0 and 1 indicating the opacity of the backdrop behind the dialog. | | `0.7` |
+| message | string | Text content of the dialog. | | `''` |
+| confirmLabel | string | Text label of the confirm button. | | `''` |
+| cancelLabel | string | Text label of the cancel button. | | `''` |
+| confirmIcon | IconName | Icon of the confirm button. | | |
+| cancelIcon | IconName | Icon of the cancel button. | | |
+| onConfirm | `() => void` | Callback called when the user pressed the confirm button. | | |
+| onCancel | `() => void` | Callback called when the user pressed the cancel button. | | |
+| dialog | ReactElement | Dialog element to display instead of the default dialog. If this prop is used, the other props such as labels, icons and callbacks are ignored. | | |
+
+---
+
## Button
### Description
A simple button component. It accepts every common HTML button props, as well as other custom props described below. It
@@ -36,96 +82,112 @@ function App() {
```
### Props
-| Prop | Type | Description | Required | Default Value |
-|------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------------------------------------------------------------------|
-| variant | ButtonVariant | Variant describing the look of the button (outlined, fill...) | | `fill` |
-| size | ButtonSize | Prop describing the size of the button. | | `normal` |
-| primaryColor | ColorProp | The primary color of the button. For filled buttons, it corresponds to the background color, for other buttons, it corresponds to the text color. | | `primary-xlight` for outline buttons and `primary` for other buttons |
-| secondaryColor | ColorProp | The secondary color of the button. For filled buttons, it corresponds to the text color and for outline buttons, it corresponds to the background color. This property is ignored for text and text-link buttons. | | `text-white` for filled buttons and `surface-s1` for outline buttons |
-| icon | IconName | The icon to place on the left of the button text. No icon will be placed if not provided. | | |
-| loading | boolean | Boolean indicating if the button is loading. When the button is loading, it is automatically disabled and its content is replaced by a spinner. | | |
-| preserveWidthOnLoading | boolean | Boolean indicating if the button should keep its original width when loading. | | `false` |
+| Prop | Type | Description | Required | Default Value |
+|------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------------------|
+| variant | ButtonVariant | Variant describing the look of the button (outlined, fill...) | | `'fill'` |
+| size | ButtonSize | Prop describing the size of the button. | | `'normal'` |
+| primaryColor | ColorProp | The primary color of the button. For filled buttons, it corresponds to the background color, for other buttons, it corresponds to the text color. | | `'primary-xlight'` for outline buttons and `'primary'` for other buttons |
+| secondaryColor | ColorProp | The secondary color of the button. For filled buttons, it corresponds to the text color and for outline buttons, it corresponds to the background color. This property is ignored for text and text-link buttons. | | `'text-white'` for filled buttons and `'surface-s1'` for outline buttons |
+| icon | IconName | The icon to place on the left of the button text. No icon will be placed if not provided. | | |
+| loading | boolean | Boolean indicating if the button is loading. When the button is loading, it is automatically disabled and its content is replaced by a spinner. | | |
+| preserveWidthOnLoading | boolean | Boolean indicating if the button should keep its original width when loading. | | `false` |
---
-## Spinner
+## DynamicSVG
### Description
-A simple spinner component that displays a loading spinner.
+A component that lets you display an SVG image based on an XML string, and then apply dynamic style and event handlers
+to the SVG elements inside it.
### Example
```tsx
-import { Spinner } from '@monkvision/common-ui-web';
+import React, { useCallback } from 'react';
+import { DynamicSVG } from '@monkvision/common-ui-web';
+
+const svg = ' ';
+// Applies a red fill and an onClick handler on the element with ID "circle1"
function App() {
- return ;
+ const getAttributes = useCallback((element: Element) => {
+ if (element.getAttribute('id') === 'circle1') {
+ return {
+ style: { fill: 'red' },
+ onClick: () => console.log('hello'),
+ pointerEvents: 'all',
+ };
+ }
+ return null;
+ }, []);
+
+ return
}
```
### Props
-| Prop | Type | Description | Required | Default Value |
-|--------------|-----------|-------------------------------------------------------------------------------------------------------------|----------|---------------|
-| size | number | The size of the spinner (width and height, in pixels). The width of the spinner line is scaled accordingly. | | `50` |
-| primaryColor | ColorProp | The name or hexcode of the spinner's color. | | `text-white` |
+| Prop | Type | Description | Required | Default Value |
+|---------------|--------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------|
+| svg | string | The XML string representing the SVG to display | ✔️ | |
+| getAttributes | (element: Element, groupIds: string[]) => SVGProps | null | A customization function that lets you specify custom HTML attributes to give to the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
+| getInnerText | (element: Element, groupIds: string[]) => string | null | A customization function that lets you specify the innner text of the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
---
-## Icon
+## FullscreenModal
### Description
-An Icon component that displays an icon based on a given name.
+Component used to display a full screen modal on top of the screen. The content of the modal must be passed as children
+to this component.
### Example
+
```tsx
-import { Spinner } from '@monkvision/common-ui-web';
+import { FullscreenModal } from '@monkvision/common-ui-web';
function App() {
- return ;
+ const [showFullscreenModal, setShowFullscreenModal] = useState(true);
+
+ return (
+ setShowFullscreenModal(false)}
+ >
+
+ This is the content of the modal!
+
+
+ );
}
```
### Props
-| Prop | Type | Description | Required | Default Value |
-|--------------|-----------|------------------------------------------------------------|----------|---------------|
-| icon | number | The name of the icon to display. | ✔️ | |
-| size | number | The size (width and height, in pixels) of the icon. | | `50` |
-| primaryColor | ColorProp | The name or the hexcode of the color to apply to the icon. | | `black` |
+| Prop | Type | Description | Required | Default Value |
+|--------------|--------------|-----------------------------------------------------------------------------------------------|----------|---------------|
+| show | boolean | Boolean indicating if the fullscreen modal is displayed on the screen. | | `false` |
+| title | string | Title displayed in the header at the top of the modal. | | `''` |
+| onClose | `() => void` | Callback called when the user presses the close button in the header at the top of the modal. | | |
---
-## DynamicSVG
+## Icon
### Description
-A component that lets you display an SVG image based on an XML string, and then apply dynamic style and event handlers
-to the SVG elements inside it.
+An Icon component that displays an icon based on a given name. The list of icons is available in the official Monk SDK
+documentation.
### Example
```tsx
-import React, { useCallback } from 'react';
-import { DynamicSVG } from '@monkvision/common-ui-web';
-
-const svg = ' ';
-
-// Applies a red fill and an onClick handler on the element with ID "circle1"
-function MyCustomSVG() {
- const getAttributes = useCallback((element: Element) => {
- if (element.getAttribute('id') === 'circle1') {
- return {
- style: { fill: 'red' },
- onClick: () => console.log('hello'),
- pointerEvents: 'all',
- };
- }
- return null;
- }, []);
+import { Icon } from '@monkvision/common-ui-web';
- return
+function App() {
+ return ;
}
```
### Props
-| Prop | Type | Description | Required | Default Value |
-|---------------|--------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------|
-| svg | string | The XML string representing the SVG to display | ✔️ | |
-| getAttributes | (element: Element, groupIds: string[]) => SVGProps | null | A customization function that lets you specify custom HTML attributes to give to the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
-| getInnerText | (element: Element, groupIds: string[]) => string | null | A customization function that lets you specify the innner text of the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
+| Prop | Type | Description | Required | Default Value |
+|--------------|-----------|------------------------------------------------------------|----------|---------------|
+| icon | number | The name of the icon to display. | ✔️ | |
+| size | number | The size (width and height, in pixels) of the icon. | | `50` |
+| primaryColor | ColorProp | The name or the hexcode of the color to apply to the icon. | | `black` |
---
@@ -140,7 +202,7 @@ import React, { useCallback } from 'react';
import { getSightById } from '@monkvision/sights';
import { SightOverlay } from '@monkvision/common-ui-web';
-function MyComponent() {
+function App() {
return (
(element: Element, groupIds: string[]) => SVGProps | null | A customization function that lets you specify custom HTML attributes to give to the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
| getInnerText | (element: Element, groupIds: string[]) => string | null | A customization function that lets you specify the innner text of the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
+---
+
+## Spinner
+### Description
+A simple spinner component that displays a loading spinner.
+
+### Example
+```tsx
+import { Spinner } from '@monkvision/common-ui-web';
+
+function App() {
+ return ;
+}
+```
+
+### Props
+| Prop | Type | Description | Required | Default Value |
+|--------------|-----------|-------------------------------------------------------------------------------------------------------------|----------|----------------|
+| size | number | The size of the spinner (width and height, in pixels). The width of the spinner line is scaled accordingly. | | `50` |
+| primaryColor | ColorProp | The name or hexcode of the spinner's color. | | `'text-white'` |
+
+---
+
## SwitchButton
### Description
Switch button component that can be used to turn ON or OFF a feature.
@@ -166,7 +251,7 @@ Switch button component that can be used to turn ON or OFF a feature.
```tsx
import { SwitchButton } from '@monkvision/common-ui-web';
-export function MyComponent() {
+function App() {
const [checked, setChecked] = useState(false);
return (
@@ -177,15 +262,35 @@ export function MyComponent() {
```
### Props
-| Prop | Type | Description | Required | Default Value |
-|-------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------|
-| size | `'normal' | 'small'` | The size of the button. Normal buttons are bigger and have their icon and labels inside the button. Small buttons are smaller, accept no label and have their icon inside the knob. | | 'normal' |
-| checked | boolean | Boolean used to control the SwitchButton. Set to `true` to make the Button switched on and `false` for off. | | false |
-| onSwitch | `(value: boolean) => void` | Callback called when the SwitchButton is switched. The value passed as the first parameter is the result `checked` value. | | |
-| disabled | boolean | Boolean indicating if the button is disabled or not. | | false |
-| checkedPrimaryColor | ColorProp | Primary color (background and knob overlay color) of the button when it is checked. | | 'primary' |
-| checkedSecondaryColor | ColorProp | Secondary color (knob, labels and icons color) of the button when it is checked. | | 'text-white' |
-| uncheckedPrimaryColor | ColorProp | Primary color (background and knob overlay color) of the button when it is unchecked. | | 'text-tertiary' |
-| uncheckedSecondaryColor | ColorProp | Secondary color (knob, labels and icons color) of the button when it is unchecked. | | 'text-white' |
-| checkedLabel | ColorProp | Custom label that can be displayed instead of the check icon when the button is checked. This prop is ignored for small buttons. | | |
-| uncheckedLabel | ColorProp | Custom label that can be displayed when the button is unchecked. This prop is ignored for small buttons. | | |
+| Prop | Type | Description | Required | Default Value |
+|-------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-------------------|
+| size | `'normal' | 'small'` | The size of the button. Normal buttons are bigger and have their icon and labels inside the button. Small buttons are smaller, accept no label and have their icon inside the knob. | | `'normal'` |
+| checked | boolean | Boolean used to control the SwitchButton. Set to `true` to make the Button switched on and `false` for off. | | `false` |
+| onSwitch | `(value: boolean) => void` | Callback called when the SwitchButton is switched. The value passed as the first parameter is the result `checked` value. | | |
+| disabled | boolean | Boolean indicating if the button is disabled or not. | | `false` |
+| checkedPrimaryColor | ColorProp | Primary color (background and knob overlay color) of the button when it is checked. | | `'primary'` |
+| checkedSecondaryColor | ColorProp | Secondary color (knob, labels and icons color) of the button when it is checked. | | `'text-white'` |
+| uncheckedPrimaryColor | ColorProp | Primary color (background and knob overlay color) of the button when it is unchecked. | | `'text-tertiary'` |
+| uncheckedSecondaryColor | ColorProp | Secondary color (knob, labels and icons color) of the button when it is unchecked. | | `'text-white'` |
+| checkedLabel | ColorProp | Custom label that can be displayed instead of the check icon when the button is checked. This prop is ignored for small buttons. | | |
+| uncheckedLabel | ColorProp | Custom label that can be displayed when the button is unchecked. This prop is ignored for small buttons. | | |
+
+---
+
+## TakePictureButton
+### Description
+A custom button that is used as a take-picture button in camera HUDs throughout the MonkJs SDK.
+
+### Example
+```tsx
+import { TakePictureButton } from '@monkvision/common-ui-web';
+
+function App() {
+ return
console.log('Picture taken!')} />;
+}
+```
+
+### Props
+| Prop | Type | Description | Required | Default Value |
+|------|--------|-----------------------------------|----------|---------------|
+| size | number | The size of the button in pixels. | | `60` |
diff --git a/packages/public/common-ui-web/src/components/BackdropDialog/BackdropDialog.styles.ts b/packages/public/common-ui-web/src/components/BackdropDialog/BackdropDialog.styles.ts
new file mode 100644
index 000000000..bc3dc4fa6
--- /dev/null
+++ b/packages/public/common-ui-web/src/components/BackdropDialog/BackdropDialog.styles.ts
@@ -0,0 +1,51 @@
+import { Styles } from '@monkvision/types';
+
+const DIALOG_BORDER_RADIUS = 15;
+
+export const styles: Styles = {
+ backdrop: {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ zIndex: 10,
+ transition: 'opacity 0.5s ease-out',
+ },
+ dialog: {
+ borderRadius: DIALOG_BORDER_RADIUS,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ message: {
+ padding: 30,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ fontSize: 18,
+ },
+ buttonsContainer: {
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexDirection: 'row',
+ },
+ button: {
+ border: 'none',
+ flex: 1,
+ alignSelf: 'stretch',
+ padding: '15px 24px',
+ },
+ cancelButton: {
+ borderRadius: `0 0 0 ${DIALOG_BORDER_RADIUS}px`,
+ },
+ confirmButton: {
+ borderRadius: `0 0 ${DIALOG_BORDER_RADIUS}px 0`,
+ },
+};
diff --git a/packages/public/common-ui-web/src/components/BackdropDialog/BackdropDialog.tsx b/packages/public/common-ui-web/src/components/BackdropDialog/BackdropDialog.tsx
new file mode 100644
index 000000000..c1b5944fe
--- /dev/null
+++ b/packages/public/common-ui-web/src/components/BackdropDialog/BackdropDialog.tsx
@@ -0,0 +1,59 @@
+import { BackdropDialogProps, useBackdropDialogStyles } from './hooks';
+import { styles } from './BackdropDialog.styles';
+import { Button } from '../Button';
+
+/**
+ * This component can be used to display a fixed dialog on the screen, with a backdrop behind it. You can either pass a
+ * custom component for the dialog modal, or use the default one and simply pass the text content of the dialog.
+ */
+export function BackdropDialog({
+ show = false,
+ backdropOpacity = 0.7,
+ message = '',
+ confirmLabel = '',
+ cancelLabel = '',
+ confirmIcon,
+ cancelIcon,
+ onConfirm,
+ onCancel,
+ dialog,
+}: BackdropDialogProps) {
+ const style = useBackdropDialogStyles({
+ show,
+ backdropOpacity,
+ message,
+ confirmLabel,
+ cancelLabel,
+ });
+ return show ? (
+
+ {dialog ?? (
+
+
{message}
+
+
+ {cancelLabel}
+
+
+ {confirmLabel}
+
+
+
+ )}
+
+ ) : null;
+}
diff --git a/packages/public/common-ui-web/src/components/BackdropDialog/hooks.ts b/packages/public/common-ui-web/src/components/BackdropDialog/hooks.ts
new file mode 100644
index 000000000..d7c83dfb2
--- /dev/null
+++ b/packages/public/common-ui-web/src/components/BackdropDialog/hooks.ts
@@ -0,0 +1,47 @@
+import { useMonkTheme } from '@monkvision/common';
+import { ReactElement } from 'react';
+import { styles } from './BackdropDialog.styles';
+import { IconName } from '../../icons';
+
+export interface BackdropDialogProps {
+ show?: boolean;
+ backdropOpacity?: number;
+ message?: string;
+ confirmLabel?: string;
+ cancelLabel?: string;
+ confirmIcon?: IconName;
+ cancelIcon?: IconName;
+ onConfirm?: () => void;
+ onCancel?: () => void;
+ dialog?: ReactElement;
+}
+
+export function useBackdropDialogStyles(
+ props: Required<
+ Pick<
+ BackdropDialogProps,
+ 'show' | 'backdropOpacity' | 'message' | 'confirmLabel' | 'cancelLabel'
+ >
+ >,
+) {
+ const { palette } = useMonkTheme();
+
+ return {
+ backdrop: {
+ ...styles['backdrop'],
+ backgroundColor: `rgba(0, 0, 0, ${props.backdropOpacity})`,
+ },
+ dialog: {
+ ...styles['dialog'],
+ backgroundColor: palette.surface.s1,
+ },
+ cancelButton: {
+ ...styles['button'],
+ ...styles['cancelButton'],
+ },
+ confirmButton: {
+ ...styles['button'],
+ ...styles['confirmButton'],
+ },
+ };
+}
diff --git a/packages/public/common-ui-web/src/components/BackdropDialog/index.ts b/packages/public/common-ui-web/src/components/BackdropDialog/index.ts
new file mode 100644
index 000000000..559f012b9
--- /dev/null
+++ b/packages/public/common-ui-web/src/components/BackdropDialog/index.ts
@@ -0,0 +1,2 @@
+export { BackdropDialog } from './BackdropDialog';
+export { type BackdropDialogProps } from './hooks';
diff --git a/packages/public/common-ui-web/src/components/FullscreenModal/FullscreenModal.tsx b/packages/public/common-ui-web/src/components/FullscreenModal/FullscreenModal.tsx
index fa94942ae..e1698717c 100644
--- a/packages/public/common-ui-web/src/components/FullscreenModal/FullscreenModal.tsx
+++ b/packages/public/common-ui-web/src/components/FullscreenModal/FullscreenModal.tsx
@@ -1,4 +1,4 @@
-import { JSX } from 'react';
+import { PropsWithChildren } from 'react';
import { Button } from '../Button';
import { styles } from './FullscreenModal.styles';
@@ -6,16 +6,21 @@ import { styles } from './FullscreenModal.styles';
* Props that can be passed to the Fullscreen Modal component.
*/
export interface FullscreenModalProps {
- show: boolean;
+ show?: boolean;
onClose?: () => void;
title?: string;
- children?: string | JSX.Element | JSX.Element[];
}
/**
- * Generic Fullscreen Modal component used to display a full screen modal on top of the screen.
+ * Component used to display a full screen modal on top of the screen. The content of the modal must be passed as
+ * children to this component.
*/
-export function FullscreenModal({ show, onClose, title = '', children }: FullscreenModalProps) {
+export function FullscreenModal({
+ show = false,
+ onClose,
+ title = '',
+ children,
+}: PropsWithChildren) {
return show ? (
{children}
@@ -24,7 +29,7 @@ export function FullscreenModal({ show, onClose, title = '', children }: Fullscr
style={styles['closeButton']}
icon='close'
variant='text'
- primaryColor={'text-white'}
+ primaryColor='text-white'
onClick={onClose}
/>
diff --git a/packages/public/common-ui-web/src/components/index.ts b/packages/public/common-ui-web/src/components/index.ts
index 747e4ac69..57b05ab8e 100644
--- a/packages/public/common-ui-web/src/components/index.ts
+++ b/packages/public/common-ui-web/src/components/index.ts
@@ -5,3 +5,4 @@ export * from './DynamicSVG';
export * from './SightOverlay';
export * from './SwitchButton';
export * from './FullscreenModal';
+export * from './BackdropDialog';
diff --git a/packages/public/common-ui-web/test/components/BackdropDialog.test.tsx b/packages/public/common-ui-web/test/components/BackdropDialog.test.tsx
new file mode 100644
index 000000000..77e2ff411
--- /dev/null
+++ b/packages/public/common-ui-web/test/components/BackdropDialog.test.tsx
@@ -0,0 +1,163 @@
+jest.mock('../../src/components/Button', () => ({
+ Button: jest.fn(() => <>>),
+}));
+
+import '@testing-library/jest-dom';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { render, screen } from '@testing-library/react';
+import { BackdropDialog, Button } from '../../src';
+
+const BACKDROP_TEST_ID = 'backdrop';
+
+describe('BackdropDialog component', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should not be shown by default', () => {
+ const { unmount } = render( );
+ expect(screen.queryByTestId(BACKDROP_TEST_ID)).toBeNull();
+ unmount();
+ });
+
+ it('should show a backdrop on top of the screen when shown', () => {
+ const { unmount } = render( );
+ const backdrop = screen.getByTestId(BACKDROP_TEST_ID);
+ expect(backdrop).toHaveStyle({
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ });
+ unmount();
+ });
+
+ it('should take the backdropOpacity prop into account', () => {
+ const backdropOpacity = 0.2;
+ const { unmount } = render( );
+ const backdrop = screen.getByTestId(BACKDROP_TEST_ID);
+ expect(backdrop).toHaveStyle({
+ backgroundColor: `rgba(0,0,0,${backdropOpacity})`,
+ });
+ unmount();
+ });
+
+ it('should have a backdropOpacity of 0.7 by default', () => {
+ const { unmount } = render( );
+ const backdrop = screen.getByTestId(BACKDROP_TEST_ID);
+ expect(backdrop).toHaveStyle({
+ backgroundColor: 'rgba(0,0,0,0.7)',
+ });
+ unmount();
+ });
+
+ it('should display the given message on the screen', () => {
+ const message = 'Hello world!';
+ const { unmount } = render( );
+ expect(screen.queryByText(message)).not.toBeNull();
+ unmount();
+ });
+
+ it('should display a cancel button with the given label', () => {
+ const label = 'Hola amigo';
+ const { unmount } = render( );
+ expect(Button).toHaveBeenCalled();
+ expectPropsOnChildMock(Button, { children: label });
+ unmount();
+ });
+
+ it('should display a confirm button with the given label', () => {
+ const label = 'Buenos dias';
+ const { unmount } = render( );
+ expect(Button).toHaveBeenCalled();
+ expectPropsOnChildMock(Button, { children: label });
+ unmount();
+ });
+
+ it('should pass the onConfirm callback to the confirm button', () => {
+ const label = 'ok';
+ const handleConfirm = jest.fn();
+ const { unmount } = render(
+ ,
+ );
+ expect(Button).toHaveBeenCalled();
+ const props = (Button as unknown as jest.Mock).mock.calls.find(
+ (args) => args[0]?.children === label,
+ )?.[0];
+ expect(props?.onClick).toBeDefined();
+ props.onClick();
+ expect(handleConfirm).toHaveBeenCalled();
+ unmount();
+ });
+
+ it('should pass the onCancel callback to the cancel button', () => {
+ const label = 'ok';
+ const handleCancel = jest.fn();
+ const { unmount } = render(
+ ,
+ );
+ expect(Button).toHaveBeenCalled();
+ const props = (Button as unknown as jest.Mock).mock.calls.find(
+ (args) => args[0]?.children === label,
+ )?.[0];
+ expect(props?.onClick).toBeDefined();
+ props.onClick();
+ expect(handleCancel).toHaveBeenCalled();
+ unmount();
+ });
+
+ it('should take the confirmIcon prop into account', () => {
+ const label = 'ok';
+ const icon = 'check';
+ const { unmount } = render(
+ ,
+ );
+ expectPropsOnChildMock(Button, {
+ children: label,
+ icon,
+ });
+ unmount();
+ });
+
+ it('should have no confirmIcon by default', () => {
+ const label = 'ok';
+ const { unmount } = render( );
+ expectPropsOnChildMock(Button, {
+ children: label,
+ icon: undefined,
+ });
+ unmount();
+ });
+
+ it('should take the cancelIcon prop into account', () => {
+ const label = 'ok';
+ const icon = 'check';
+ const { unmount } = render(
+ ,
+ );
+ expectPropsOnChildMock(Button, {
+ children: label,
+ icon,
+ });
+ unmount();
+ });
+
+ it('should have no cancelIcon by default', () => {
+ const label = 'ok';
+ const { unmount } = render( );
+ expectPropsOnChildMock(Button, {
+ children: label,
+ icon: undefined,
+ });
+ unmount();
+ });
+
+ it('should display the given dialog if provided', () => {
+ const testId = 'hello-test-world';
+ const customDialog = Hello
;
+ const { unmount } = render( );
+ expect(screen.queryByTestId(testId)).not.toBeNull();
+ unmount();
+ });
+});
diff --git a/packages/public/common-ui-web/test/components/Button/Button.test.tsx b/packages/public/common-ui-web/test/components/Button/Button.test.tsx
index a8a511f38..151b1f8df 100644
--- a/packages/public/common-ui-web/test/components/Button/Button.test.tsx
+++ b/packages/public/common-ui-web/test/components/Button/Button.test.tsx
@@ -153,7 +153,7 @@ describe('Button component', () => {
Test
,
);
- expect(screen.queryByTestId(testId)).toBeDefined();
+ expect(screen.queryByTestId(testId)).not.toBeNull();
unmount();
});
diff --git a/packages/public/common-ui-web/test/components/Button/mocks.tsx b/packages/public/common-ui-web/test/components/Button/mocks.tsx
index 92a38382a..614c9d592 100644
--- a/packages/public/common-ui-web/test/components/Button/mocks.tsx
+++ b/packages/public/common-ui-web/test/components/Button/mocks.tsx
@@ -6,6 +6,4 @@ export function mockButtonDependencies(): void {
jest.mock('../../../src/icons', () => ({
Icon: jest.fn(() => <>>),
}));
-
- jest.mock('@monkvision/common');
}
diff --git a/packages/public/common-ui-web/test/components/DynamicSVG/hooks/useJSXTransformAttributes.test.ts b/packages/public/common-ui-web/test/components/DynamicSVG/hooks/useJSXTransformAttributes.test.ts
index cf2643191..4e2bfa605 100644
--- a/packages/public/common-ui-web/test/components/DynamicSVG/hooks/useJSXTransformAttributes.test.ts
+++ b/packages/public/common-ui-web/test/components/DynamicSVG/hooks/useJSXTransformAttributes.test.ts
@@ -1,5 +1,3 @@
-import { transformInlineCss } from '../../../../src/components/DynamicSVG/hooks/utils';
-
jest.mock('../../../../src/components/DynamicSVG/hooks/utils', () => ({
transformInlineCss: jest.fn(() => ({ height: '34px' })),
}));
@@ -7,6 +5,7 @@ jest.mock('../../../../src/components/DynamicSVG/hooks/utils', () => ({
import { renderHook } from '@testing-library/react-hooks';
import { CSSProperties } from 'react';
import { useJSXTransformAttributes } from '../../../../src/components/DynamicSVG/hooks';
+import { transformInlineCss } from '../../../../src/components/DynamicSVG/hooks/utils';
describe('useJSXTransformAttributes hook', () => {
afterEach(() => {
diff --git a/packages/public/common-ui-web/test/components/Spinner.test.tsx b/packages/public/common-ui-web/test/components/Spinner.test.tsx
index 6785046cd..fb39b8862 100644
--- a/packages/public/common-ui-web/test/components/Spinner.test.tsx
+++ b/packages/public/common-ui-web/test/components/Spinner.test.tsx
@@ -1,5 +1,3 @@
-jest.mock('@monkvision/common');
-
jest.mock('../../src/components/DynamicSVG', () => ({
DynamicSVG: jest.fn(() => <>>),
}));
diff --git a/packages/public/common-ui-web/test/components/SwitchButton.test.tsx b/packages/public/common-ui-web/test/components/SwitchButton.test.tsx
index cbe78f06c..83070f02b 100644
--- a/packages/public/common-ui-web/test/components/SwitchButton.test.tsx
+++ b/packages/public/common-ui-web/test/components/SwitchButton.test.tsx
@@ -1,4 +1,3 @@
-jest.mock('@monkvision/common');
jest.mock('../../src/icons', () => ({
Icon: jest.fn(() => <>>),
}));
diff --git a/packages/public/common-ui-web/test/icons/Icon.test.tsx b/packages/public/common-ui-web/test/icons/Icon.test.tsx
index 9931d2915..36a02ec7f 100644
--- a/packages/public/common-ui-web/test/icons/Icon.test.tsx
+++ b/packages/public/common-ui-web/test/icons/Icon.test.tsx
@@ -1,5 +1,3 @@
-jest.mock('@monkvision/common');
-
jest.mock('../../src/components/DynamicSVG', () => ({
DynamicSVG: jest.fn(() => <>>),
}));
diff --git a/packages/public/common/README/HOOKS.md b/packages/public/common/README/HOOKS.md
index b2994bf13..361dfa50c 100644
--- a/packages/public/common/README/HOOKS.md
+++ b/packages/public/common/README/HOOKS.md
@@ -4,16 +4,104 @@ React hooks. You can refer to [this page](README.md). for more general informati
This package exports various custom hooks used throughout the MonkJs SDK.
-### useWindowDimensions
+### useAsyncEffect
```tsx
-import { useWindowDimensions } from '@monkvision/common';
+import { useAsyncEffect } from '@monkvision/common';
function TestComponent() {
- const { width, height, isPortrait } = useWindowDimensions();
+ useAsyncEffect(
+ async () => {
+ const result = myCustomAsyncFunc();
+ return result.value;
+ },
+ [exampleDependency],
+ {
+ onResolve: (value) => {
+ console.log(value);
+ },
+ onReject: (err) => {
+ console.error(err);
+ },
+ onComplete: () => {
+ console.log('Done.');
+ }
+ },
+ );
}
```
-This hook returns the current window dimensions in pixels, and a boolean indicating if the window is in portrait mode
-(width < height) or not.
+Custom hook that can be used to run asyncrhonous effects. It is similar to `useEffect` but makes sure to not execute the
+effect handlers if the effect's Promise resolves after the current component as been dismounted.
+
+### useInteractiveStatus
+```tsx
+import { useInteractiveStatus } from '@monkvision/common';
+
+function TestComponent() {
+ const { status, events } = useInteractiveStatus();
+ useEffect(() => console.log('Button status :', status), [status]);
+
+ return My Button ;
+}
+```
+This hook allows the tracking of the interactive status (hovered, active, disabled...) of a React element. It returns
+the interactive status of the element, as well as a set of MouseEvent listeners used to update the status accordingly.
+
+### useLangProp
+```tsx
+import { useLangProp } from '@monkvision/common';
+
+function TestComponent(props: { lang?: string }) {
+ useLangProp(lang);
+}
+```
+Custom hook used internally by the Monk SDK components to handle the `lang` prop tha tcan be passed to them to manage
+the current language displayed by the component.
+
+### useLoadingState
+```tsx
+import { useEffect } from 'react';
+import { useLoadingState } from '@monkvision/common';
+
+function useCustomApiCall() {
+ const loading = useLoadingState();
+ useEffect(() => {
+ loading.start();
+ myApiCall().then(() => loading.onSuccess()).catch((err) => loading.onError(err));
+ }, []);
+}
+```
+Custom hook used to create a `LoadingState` object. This object can be used to track the processing of a task in the
+component. For instance, you can use this hook to handle the loading and errors of API calls in your components.
+
+### useObjectTranslation
+```tsx
+import { useObjectTranslation } from '@monkvision/common';
+
+const translationObject = { en: 'Hello', fr: 'Salut', de: 'Hallo' };
+
+function TestComponent() {
+ const { tObj } = useObjectTranslation();
+ return {tObj(translationObject)}
;
+}
+```
+Custom hook used to get a translation function tObj that translates TranslationObjects.
+
+### useQueue
+```tsx
+import { useQueue } from '@monkvision/common';
+
+function TestComponent() {
+ const queue = useQueue((item) => {
+ console.log(item);
+ return Promise.resolve();
+ });
+ ...
+}
+```
+
+This hook is used to create a processing queue. The `process` function passed as a parameter is an async function
+that is used to process items in the queue. You can find more details on how the queue works by taking a look at the
+TSDoc of the `Queue` interface.
### useResponsiveStyle
```tsx
@@ -40,33 +128,25 @@ This hook returns takes a `ResponsiveStyleProperties` declarations object (see t
`@monkvision/types` package for more details) containing a media query and returns either the CSSProperties contained in
the type, or `null` if the query conditions are not met. Note that if there are no query, the style will be applied.
-### useInteractiveStatus
+### useSightLabel
```tsx
-import { useInteractiveStatus } from '@monkvision/common';
+import { sights } from '@monkvision/sights';
+import { useSightLabel } from '@monkvision/common';
function TestComponent() {
- const { status, events } = useInteractiveStatus();
- useEffect(() => console.log('Button status :', status), [status]);
-
- return My Button ;
+ const { label } = useSightLabel();
+ return {label(sights['fesc20-0mJeXBDf'])}
;
}
```
-This hook allows the tracking of the interactive status (hovered, active, disabled...) of a React element. It returns
-the interactive status of the element, as well as a set of MouseEvent listeners used to update the status accordingly.
+Custom hook used to get the label of a sight with the currently selected language.
-### useQueue
+### useWindowDimensions
```tsx
-import { useQueue } from '@monkvision/common';
+import { useWindowDimensions } from '@monkvision/common';
function TestComponent() {
- const queue = useQueue((item) => {
- console.log(item);
- return Promise.resolve();
- });
- ...
+ const { width, height, isPortrait } = useWindowDimensions();
}
```
-
-This hook is used to create a processing queue. The `process` function passed as a parameter is an async function
-that is used to process items in the queue. You can find more details on how the queue works by taking a look at the
-TSDoc of the `Queue` interface.
+This hook returns the current window dimensions in pixels, and a boolean indicating if the window is in portrait mode
+(width < height) or not.
diff --git a/packages/public/common/README/INTERNATIONALIZATION.md b/packages/public/common/README/INTERNATIONALIZATION.md
index ff3444571..277f20039 100644
--- a/packages/public/common/README/INTERNATIONALIZATION.md
+++ b/packages/public/common/README/INTERNATIONALIZATION.md
@@ -46,6 +46,7 @@ i18n.use(initReactI18next).init(...);
// Use the function right after initializing your i18n instance.
i18nLinkSDKInstances(i18n, [i18nInspectionCapture, i18nInspectionReport]);
+
export default i18n;
```
diff --git a/packages/public/common/README/STATE_MANAGEMENT.md b/packages/public/common/README/STATE_MANAGEMENT.md
index 31ad6e8ac..a06338b2f 100644
--- a/packages/public/common/README/STATE_MANAGEMENT.md
+++ b/packages/public/common/README/STATE_MANAGEMENT.md
@@ -57,85 +57,66 @@ The list of actions that can be dispatched is described in the *Monk state actio
> previously by a MonkProvider
# Monk state actions
-There are 4 types of actions that can be dispatched in the Monk state. Every action must at least contain the following
-properties :
+This section enumerates the available actions that can be dispatched in the MonkState.
-- `type` : The type of action dispatched (described by the `MonkActionType` enum).
-- `entityType` : The type of entity affected by this action (described by the `MonkEntityType` enum).
-
-## GOT_ONE_ENTITY
-This action is designed to be dispatched when an entity has just been fetched. When this action is dispatched, if an
-entity with the same ID is already present in the state, it will be updated, if not, it will be created.
+## ResetState Action
+This action can be dispatched in order to completely reset the MonkState and clear all stored entities. This action does
+not need any payload.
```typescript
-import { Inspection, MonkEntityType } from '@monkvision/types';
-import { MonkGotOneAction, MonkActionType } from '@monkvision/common';
+import { MonkResetStateAction, MonkActionType } from '@monkvision/common';
-const action: MonkGotOneAction = {
- type: MonkActionType.GOT_ONE_ENTITY,
- entityType: MonkEntityType.INSPECTION,
- entity: inspection,
+const action: MonkResetStateAction = {
+ type: MonkActionType.RESET_STATE,
}
```
-In addition to the common Monk action fields, this action contains a field called `entity` which should contain the
-entity that has been fetched. Note : In TypeScript, the `MonkGotOneAction` interface is generic, and you must specify
-the type of the entity related to the action. The TypeScript action and entity types HAVE to match with their enum
-values specified in the action fields.
-
-## GOT_MANY_ENTITIES
-This action is designed to be dispatched when multiple entities have just been fetched. When this action is dispatched,
-for each entity, if an entity with the same ID is already present in the state, it will be updated, if not, it will be
-created.
+## GotOneInspection Action
+This action can be dispatched when details about an inspection have been fetched from the API. The payload of this
+action is actually a partial MonkState containing details about every entity fetched with this inspection.
```typescript
-import { Image, MonkEntityType } from '@monkvision/types';
-import { MonkGotManyAction, MonkActionType } from '@monkvision/common';
-
-const action: MonkGotManyAction = {
- type: MonkActionType.GOT_ONE_ENTITY,
- entityType: MonkEntityType.IMAGE,
- entities: images,
+import { MonkResetStateAction, MonkActionType } from '@monkvision/common';
+
+const action: Monk = {
+ type: MonkActionType.GOT_ONE_INSPECTION,
+ payload: {
+ inspections: [...],
+ images: [...],
+ ...
+ }
}
```
-In addition to the common Monk action fields, this action contains a field called `entities` which should contain an
-array of the entities that have been fetched. Note : In TypeScript, the `MonkGotManyAction` interface is generic, and
-you must specify the type of the entity related to the action. The TypeScript action and entity types HAVE to match
-with their enum values specified in the action fields.
-
-## DELETED_ONE_ENTITY
-This action is designed to be dispatched when an entity has just been deleted. When this action is dispatched, if an
-entity with the specified ID exists, it will be deleted.
+## CreatedOneImage Action
+This action can be dispatched after an image has beencreated and uploaded to the API. The payload of this action should
+contain the details about the image that has been created, as well as the ID of the inspection.
```typescript
-import { Image, MonkEntityType } from '@monkvision/types';
-import { MonkDeletedOneAction, MonkActionType } from '@monkvision/common';
-
-const action: MonkDeletedOneAction = {
- type: MonkActionType.DELETED_ONE_ENTITY,
- entityType: MonkEntityType.IMAGE,
- id: '...',
+import { MonkResetStateAction, MonkActionType } from '@monkvision/common';
+
+const action: Monk = {
+ type: MonkActionType.CREATED_ONE_IMAGE,
+ payload: {
+ inspectionId: 'e1cb2852-77f3-4fb5-a851-e700cf31a7d1',
+ image: {...},
+ }
}
```
-In addition to the common Monk action fields, this action contains a field called `id` which should contain the ID of
-the entity that has been deleted.
-
-## DELETED_MANY_ENTITIES
-This action is designed to be dispatched when multiple entities have just been deleted. When this action is dispatched,
-for each entity ID, if an entity with this ID exists, it will be deleted.
+## UpdatedManyTasks Action
+This action can be dispatched after one or multiple tasks' statuses have been updated. The payload of this action should
+be an array containing the details of the tasks that have been updated.
```typescript
-import { Image, MonkEntityType } from '@monkvision/types';
-import { MonkDeletedManyAction, MonkActionType } from '@monkvision/common';
-
-const action: MonkDeletedManyAction = {
- type: MonkActionType.DELETED_MANY_ENTITIES,
- entityType: MonkEntityType.IMAGE,
- ids: ['...', '...'],
+import { MonkResetStateAction, MonkActionType } from '@monkvision/common';
+import { ProgressStatus } from '@monkvision/types';
+
+const action: Monk = {
+ type: MonkActionType.CREATED_ONE_IMAGE,
+ payload: [
+ { id: '2b2ac131-c613-41a9-ac04-d00b942e2290', status: ProgressStatus.IN_PROGRESS },
+ { id: '4097bc0e-02d0-4ed8-9411-b18f0cb922f2', status: ProgressStatus.IN_PROGRESS },
+ ]
}
```
-
-In addition to the common Monk action fields, this action contains a field called `ids` which should contain the list o
-IDs of the deleted entities.
diff --git a/packages/public/common/README/UTILITIES.md b/packages/public/common/README/UTILITIES.md
index 75dabc8f9..e9a90cd53 100644
--- a/packages/public/common/README/UTILITIES.md
+++ b/packages/public/common/README/UTILITIES.md
@@ -4,63 +4,46 @@ utility functions. You can refer to [this page](README.md). for more general inf
This package exports various utility functions used throughout the MonkJs SDK.
-# String Utils
-### suffix
-```typescript
-import { suffix } from '@monkvision/common';
-
-console.log(suffix('my-str', { suffix1: true, suffix2: false }));
-// Output : 'my-str suffix1'
-```
-This function suffixes a string with the given suffixes, only if their value is `true` in the suffixes object param.
-
-*Note : The order of the suffixes is not guaranteed.*
-
-### words
+# Array Utils
+### permutations
```typescript
-import { words } from '@monkvision/common';
+import { permutations } from '@monkvision/common';
-console.log(words('my-str-test'));
-// Output : 'my str test'
+console.log(permutations([1, 2, 3]));
+// Output : [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
```
-Split the given string into its composing words.
+Returns an array containing all the possible permutations of the given array.
-### capitalize
+### uniq
```typescript
-import { capitalize } from '@monkvision/common';
+import { uniq } from '@monkvision/common';
-console.log(capitalize('my-str-test'));
-// Output : 'My-str-test'
+console.log(uniq([1, 1, 1, 2, 3, 3]));
+// Output : [1, 2, 3]
```
-Capitalizes (transforms the first character to upper case) the given string.
+Return a copy of the given array in which all duplicates have been removed.
-### uncapitalize
+### flatten
```typescript
-import { uncapitalize } from '@monkvision/common';
+import { flatten } from '@monkvision/common';
-console.log(uncapitalize('My-str-test'));
-// Output : 'my-str-test'
+console.log(flatten([ 1, [2, 3], [[4], [5, 6]]]));
+// Output : [1, 2, 3, 4, 5, 6]
```
-Uncapitalizes (transforms the first character to lower case) the given string.
+Flatten the given array.
-### toCamelCase
+### flatten
```typescript
-import { toCamelCase } from '@monkvision/common';
+import { flatMap } from '@monkvision/common';
-console.log(toCamelCase('My-str-test'));
-// Output : 'myStrTest'
+console.log(flatMap([1, 2, 3], (item: number) => [item, item + 1]));
+// Output : [1, 2, 2, 3, 3, 4]
```
-Converts a string to camel case.
-
-# Array Utils
-### permutations
-```typescript
-import { permutations } from '@monkvision/common';
+JS implementation of the
+[Array.prototype.flatMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap)
+method, available on all versions of JavaScript.
-console.log(permutations([1, 2, 3]));
-// Output : [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
-```
-Returns an array containing all the possible permutations of the given array.
+---
# Color Utils
### getRGBAFromString
@@ -100,6 +83,15 @@ Apply a shade of black or white over the given color. The amount of shade to app
- use positive values like 0.08 to lighten the color by 8%
- use negative values like -0.08 to darken the color by 8%
+### changeAlpha
+```typescript
+import { changeAlpha } from '@monkvision/common';
+
+console.log(changeAlpha('#FF1234FF', 0.5));
+// Output : #FF123480
+```
+Returns a new color equal to the given color but with a different alpha value.
+
### getInteractiveVariants
```typescript
import { getInteractiveVariants } from '@monkvision/common';
@@ -116,3 +108,118 @@ const variants = getInteractiveVariants('#FC72A7');
```
Create interactive variants (hovered, active...) for the given color. You can specify as an additional parameter the
type of variation to use for the interactive colors (lighten or darken the color, default = lighten).
+
+---
+
+# Mimetype Utils
+### MIMETYPE_FILE_EXTENSIONS
+```typescript
+import { MIMETYPE_FILE_EXTENSIONS } from '@monkvision/common';
+
+console.log(MIMETYPE_FILE_EXTENSIONS['text/plain']);
+// Output : ['txt']
+```
+Datamap that associates mimetypes to known file extensions corresponding to this mimetype.
+
+### getFileExtensions
+```typescript
+import { getFileExtensions } from '@monkvision/common';
+
+console.log(getFileExtensions('image/jpeg'));
+// Output : ['jpeg', 'jpg']
+```
+Returns a list of file extensions known to be corresponding to the given mimetype. If no file extension is known for
+this mimetype, this function will throw an error.
+
+### getMimetype
+```typescript
+import { getMimetype } from '@monkvision/common';
+
+console.log(getMimetype('jpg'));
+// Output : 'image/jpeg'
+```
+Returns the mimetype associated with the given file extension. If the file extension is unknown, this function will
+throw an error.
+
+---
+
+# Promise Utils
+### timeoutPromise
+```typescript
+import { timeoutPromise } from '@monkvision/common';
+
+timeoutPromise(5000).then(() => console.log('Hello!'));
+// Output after 5 seconds : 'Hello!'
+```
+This function creates and returns a new Promise that will resolve to void after the given amount of milliseconds.
+
+---
+
+# String Utils
+### suffix
+```typescript
+import { suffix } from '@monkvision/common';
+
+console.log(suffix('my-str', { suffix1: true, suffix2: false }));
+// Output : 'my-str suffix1'
+```
+This function suffixes a string with the given suffixes, only if their value is `true` in the suffixes object param.
+
+*Note : The order of the suffixes is not guaranteed.*
+
+### words
+```typescript
+import { words } from '@monkvision/common';
+
+console.log(words('my-str-test'));
+// Output : 'my str test'
+```
+Split the given string into its composing words.
+
+### capitalize
+```typescript
+import { capitalize } from '@monkvision/common';
+
+console.log(capitalize('my-str-test'));
+// Output : 'My-str-test'
+```
+Capitalizes (transforms the first character to upper case) the given string.
+
+### uncapitalize
+```typescript
+import { uncapitalize } from '@monkvision/common';
+
+console.log(uncapitalize('My-str-test'));
+// Output : 'my-str-test'
+```
+Uncapitalizes (transforms the first character to lower case) the given string.
+
+### toCamelCase
+```typescript
+import { toCamelCase } from '@monkvision/common';
+
+console.log(toCamelCase('My-str-test'));
+// Output : 'myStrTest'
+```
+Converts a string to camel case.
+
+---
+
+# Zlib Utils
+### zlibCompress
+```typescript
+import { zlibCompress } from '@monkvision/common';
+
+console.log(zlibCompress('Hello World!'))
+// Output : 'eJzzSM3JyVcIzy/KSVEEABxJBD4='
+```
+Compresses and encodes a string in base64 using the ZLib algorithm.
+
+### zlibDecompress
+```typescript
+import { zlibDecompress } from '@monkvision/common';
+
+console.log(zlibDecompress('eJzzSM3JyVcIzy/KSVEEABxJBD4='))
+// Output : 'Hello World!'
+```
+Decompresses a string that has been encoded in base64 and compressed using the Zlib algorithm.
diff --git a/packages/public/common/src/hooks/index.ts b/packages/public/common/src/hooks/index.ts
index 594b0613b..fbf6c2ee7 100644
--- a/packages/public/common/src/hooks/index.ts
+++ b/packages/public/common/src/hooks/index.ts
@@ -4,3 +4,6 @@ export * from './useInteractiveStatus';
export * from './useQueue';
export * from './useObjectTranslation';
export * from './useSightLabel';
+export * from './useLoadingState';
+export * from './useAsyncEffect';
+export * from './useLangProp';
diff --git a/packages/public/common/src/hooks/useAsyncEffect.ts b/packages/public/common/src/hooks/useAsyncEffect.ts
new file mode 100644
index 000000000..d2f25d77a
--- /dev/null
+++ b/packages/public/common/src/hooks/useAsyncEffect.ts
@@ -0,0 +1,35 @@
+import { DependencyList, useEffect } from 'react';
+import { PromiseHandlers } from '@monkvision/types';
+
+/**
+ * Custom hook that can be used to run asyncrhonous effects. It is similar to `useEffect` but makes sure to not execute
+ * the effect handlers if the effect's Promise resolves after the current component as been dismounted.
+ */
+export function useAsyncEffect(
+ effect: () => Promise,
+ deps: DependencyList | undefined,
+ handlers?: Partial>,
+): void {
+ useEffect(() => {
+ let isActive = true;
+ effect()
+ .then((res) => {
+ if (isActive) {
+ handlers?.onResolve?.(res);
+ }
+ })
+ .catch((err) => {
+ if (isActive) {
+ handlers?.onReject?.(err);
+ }
+ })
+ .finally(() => {
+ if (isActive) {
+ handlers?.onComplete?.();
+ }
+ });
+ return () => {
+ isActive = false;
+ };
+ }, deps);
+}
diff --git a/packages/public/common/src/hooks/useLangProp.ts b/packages/public/common/src/hooks/useLangProp.ts
new file mode 100644
index 000000000..c0f4f444d
--- /dev/null
+++ b/packages/public/common/src/hooks/useLangProp.ts
@@ -0,0 +1,24 @@
+import { useTranslation } from 'react-i18next';
+import { useEffect } from 'react';
+import { useMonitoring } from '@monkvision/monitoring';
+import { MonkLanguage, monkLanguages } from '@monkvision/types';
+
+/**
+ * Custom hook used internally by the Monk SDK components to handle the `lang` prop tha tcan be passed to them to
+ * manage the current language displayed by the component.
+ */
+export function useLangProp(lang?: string): void {
+ if (lang && !monkLanguages.includes(lang as MonkLanguage)) {
+ throw new Error(
+ `Unsupported language : "${lang}". Languages supported by the Monk SDK are : ${monkLanguages}.`,
+ );
+ }
+ const { i18n } = useTranslation();
+ const { handleError } = useMonitoring();
+
+ useEffect(() => {
+ if (lang) {
+ i18n.changeLanguage(lang).catch(handleError);
+ }
+ }, [i18n, lang]);
+}
diff --git a/packages/public/common/src/hooks/useLoadingState.ts b/packages/public/common/src/hooks/useLoadingState.ts
new file mode 100644
index 000000000..16f7a8f97
--- /dev/null
+++ b/packages/public/common/src/hooks/useLoadingState.ts
@@ -0,0 +1,60 @@
+import { useState } from 'react';
+
+/**
+ * An object containing data about a task.
+ */
+export interface LoadingState {
+ /**
+ * Boolean indicating if the task is running.
+ */
+ isLoading: boolean;
+ /**
+ * This field will contain the error that occurred during the task. If no error has occurred, or if the task has not
+ * completed yet, this field is `null`.
+ */
+ error: unknown | null;
+ /**
+ * Callback used to indicate that the task has started.
+ */
+ start: () => void;
+ /**
+ * Callback used to indicate that the task has completed successfully.
+ */
+ onSuccess: () => void;
+ /**
+ * Callback used to indicate that an error occurred during the task.
+ */
+ onError: (err?: unknown) => void;
+}
+
+/**
+ * Custom hook used to create a `LoadingState` object. This object can be used to track the processing of a task in
+ * the component. For instance, you can use this hook to handle the loading and errors of API calls in your components.
+ */
+export function useLoadingState(): LoadingState {
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const start = () => {
+ setError(null);
+ setIsLoading(true);
+ };
+
+ const onSuccess = () => {
+ setError(null);
+ setIsLoading(false);
+ };
+
+ const onError = (err?: unknown) => {
+ setError(err);
+ setIsLoading(false);
+ };
+
+ return {
+ isLoading,
+ error,
+ start,
+ onSuccess,
+ onError,
+ };
+}
diff --git a/packages/public/common/src/hooks/useObjectTranslation.ts b/packages/public/common/src/hooks/useObjectTranslation.ts
index 1fd1e7e78..f6bc524f2 100644
--- a/packages/public/common/src/hooks/useObjectTranslation.ts
+++ b/packages/public/common/src/hooks/useObjectTranslation.ts
@@ -13,7 +13,7 @@ export interface UseObjectTranslationResult {
}
/**
- * Custom hook used to get a translate function tObj that translates TranslationObjects.
+ * Custom hook used to get a translation function tObj that translates TranslationObjects.
*/
export function useObjectTranslation(): UseObjectTranslationResult {
const { i18n } = useTranslation();
diff --git a/packages/public/common/src/hooks/useQueue.ts b/packages/public/common/src/hooks/useQueue.ts
index 11db03559..61658ff50 100644
--- a/packages/public/common/src/hooks/useQueue.ts
+++ b/packages/public/common/src/hooks/useQueue.ts
@@ -3,7 +3,7 @@ import { useMemo, useRef, useState } from 'react';
/**
* Type definition for the processing function of a queue.
*/
-export type ProcessingFunction = (item: T) => Promise;
+export type ProcessingFunction = (item: T) => Promise;
/**
* The options that can be used to configure a processing queue using the `useQueue` hook.
diff --git a/packages/public/common/src/hooks/useSightLabel.ts b/packages/public/common/src/hooks/useSightLabel.ts
index 6a9da9da4..38379e017 100644
--- a/packages/public/common/src/hooks/useSightLabel.ts
+++ b/packages/public/common/src/hooks/useSightLabel.ts
@@ -20,7 +20,7 @@ export interface UseSightLabelParams {
}
/**
- * Custom hook used to get the label of a sight with the actual selected language.
+ * Custom hook used to get the label of a sight with the currently selected language.
*/
export function useSightLabel({ labels }: UseSightLabelParams): UseSightLabelResult {
const { tObj } = useObjectTranslation();
diff --git a/packages/public/common/src/i18n/utils.tsx b/packages/public/common/src/i18n/utils.tsx
index 225eff385..3f3e6826d 100644
--- a/packages/public/common/src/i18n/utils.tsx
+++ b/packages/public/common/src/i18n/utils.tsx
@@ -31,9 +31,7 @@ import { I18nextProvider, initReactI18next } from 'react-i18next';
*/
export function i18nLinkSDKInstances(instance: i18n, sdkInstances: i18n[]): void {
instance.on('languageChanged', (lng: string) => {
- sdkInstances.forEach((sdkInstance) =>
- sdkInstance.changeLanguage(lng).catch((err) => console.error(err)),
- );
+ sdkInstances.forEach((sdkInstance) => sdkInstance.changeLanguage(lng).catch(console.error));
});
}
@@ -65,7 +63,7 @@ export function useI18nLink(instance: i18n, sdkInstances: i18n[]): void {
useEffect(() => {
sdkInstances.forEach((sdkInstance) =>
- sdkInstance.changeLanguage(instance.language).catch((err) => handleError(err)),
+ sdkInstance.changeLanguage(instance.language).catch(handleError),
);
}, [instance.language]);
}
@@ -95,10 +93,7 @@ export function i18nCreateSDKInstance({ resources }: I18NSDKOptions): i18n {
interpolation: { escapeValue: false },
resources,
});
- instance
- .use(initReactI18next)
- .init()
- .catch((err) => console.error(err));
+ instance.use(initReactI18next).init().catch(console.error);
return instance;
}
diff --git a/packages/public/common/src/state/actions.ts b/packages/public/common/src/state/actions.ts
deleted file mode 100644
index bcd8fabe1..000000000
--- a/packages/public/common/src/state/actions.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { MonkState } from './state';
-
-/**
- * Enumeration of the types of action that can be dispatched in the Monk state.
- */
-export enum MonkActionType {
- /**
- * Update the state.
- */
- UPDATE_STATE = 'update_state',
- /**
- * Reset the state.
- */
- RESET_STATE = 'reset_state',
-}
-
-/**
- * Type definition for a generic action dispatched in the Monk state.
- */
-export interface MonkAction {
- /**
- * The type of the action.
- */
- type: MonkActionType;
-}
-
-/**
- * The payload of a MonkUpdateStateAction.
- */
-export interface MonkUpdateStatePayload {
- /**
- * The new entities to add to the state. For each entity, if an entity with the same ID already exist in the state,
- * overwrite it.
- */
- entities?: Partial;
- /**
- * A list of entity IDs to delete from the state.
- */
- deleted?: string[];
-}
-
-/**
- * Action dispatched when an update to the state needs to be made.
- */
-export interface MonkUpdateStateAction extends MonkAction {
- /**
- * The type of the action : `MonkActionType.UPDATE_STATE`.
- */
- type: MonkActionType.UPDATE_STATE;
- /**
- * The payload of the action containing the new entities or the IDs of the deleted ones.
- */
- payload: MonkUpdateStatePayload;
-}
-
-/**
- * Matcher function that matches a MonkGotOneAction while also inferring its type using TypeScript's type predicate
- * feature.
- */
-export function isUpdateStateAction(action: MonkAction): action is MonkUpdateStateAction {
- return action.type === MonkActionType.UPDATE_STATE;
-}
-
-/**
- * Action dispatched when the state needs to be reset.
- */
-export interface MonkResetStateAction extends MonkAction {
- /**
- * The type of the action : `MonkActionType.UPDATE_STATE`.
- */
- type: MonkActionType.RESET_STATE;
-}
-
-/**
- * Matcher function that matches a MonkGotOneAction while also inferring its type using TypeScript's type predicate
- * feature.
- */
-export function isResetStateAction(action: MonkAction): action is MonkResetStateAction {
- return action.type === MonkActionType.RESET_STATE;
-}
diff --git a/packages/public/common/src/state/actions/createdOneImage.ts b/packages/public/common/src/state/actions/createdOneImage.ts
new file mode 100644
index 000000000..6fba43e22
--- /dev/null
+++ b/packages/public/common/src/state/actions/createdOneImage.ts
@@ -0,0 +1,55 @@
+import { Image } from '@monkvision/types';
+import { MonkAction, MonkActionType } from './monkAction';
+import { MonkState } from '../state';
+
+/**
+ * The payload of a MonkCreatedOneImageAction.
+ */
+export interface MonkCreatedOneImagePayload {
+ inspectionId: string;
+ image: Image;
+}
+
+/**
+ * Action dispatched when an image has been uploaded to the API.
+ */
+export interface MonkCreatedOneImageAction extends MonkAction {
+ /**
+ * The type of the action : `MonkActionType.CREATED_ONE_IMAGE`.
+ */
+ type: MonkActionType.CREATED_ONE_IMAGE;
+ /**
+ * The payload of the action.
+ */
+ payload: MonkCreatedOneImagePayload;
+}
+
+/**
+ * Matcher function that matches a CreatedOneImage while also inferring its type using TypeScript's type predicate
+ * feature.
+ */
+export function isCreatedOneImageAction(action: MonkAction): action is MonkCreatedOneImageAction {
+ return action.type === MonkActionType.CREATED_ONE_IMAGE;
+}
+
+/**
+ * Reducer function for a CreatedOneImage action.
+ */
+export function createdOneImage(state: MonkState, action: MonkCreatedOneImageAction): MonkState {
+ const { inspections } = state;
+ const images = state.images.filter((image) => image.id !== action.payload.image.id);
+ images.push(action.payload.image);
+ inspections.forEach((inspection) => {
+ if (
+ inspection.id === action.payload.inspectionId &&
+ !inspection.images.includes(action.payload.image.id)
+ ) {
+ inspection.images.push(action.payload.image.id);
+ }
+ });
+ return {
+ ...state,
+ inspections,
+ images,
+ };
+}
diff --git a/packages/public/common/src/state/actions/gotOneInspection.ts b/packages/public/common/src/state/actions/gotOneInspection.ts
new file mode 100644
index 000000000..68abd5239
--- /dev/null
+++ b/packages/public/common/src/state/actions/gotOneInspection.ts
@@ -0,0 +1,46 @@
+import { MonkEntity } from '@monkvision/types';
+import { MonkState } from '../state';
+import { MonkAction, MonkActionType } from './monkAction';
+
+/**
+ * Action dispatched when an inspection has been fetched from the API.
+ */
+export interface MonkGotOneInspectionAction extends MonkAction {
+ /**
+ * The type of the action : `MonkActionType.GOT_ONE_INSPECTION`.
+ */
+ type: MonkActionType.GOT_ONE_INSPECTION;
+ /**
+ * The payload of the action containing the fetched entities.
+ */
+ payload: Partial;
+}
+
+/**
+ * Matcher function that matches a GotOneInspection while also inferring its type using TypeScript's type predicate
+ * feature.
+ */
+export function isGotOneInspectionAction(action: MonkAction): action is MonkGotOneInspectionAction {
+ return action.type === MonkActionType.GOT_ONE_INSPECTION;
+}
+
+/**
+ * Reducer function for a GotOneInspection action.
+ */
+export function gotOneInspection(state: MonkState, action: MonkGotOneInspectionAction): MonkState {
+ const newState = { ...state };
+ Object.keys(state).forEach((key: string) => {
+ const entityKey = key as keyof MonkState;
+ action.payload[entityKey]?.forEach((payloadEntity) => {
+ const newEntityIndex = state[entityKey].findIndex(
+ (newEntity) => newEntity.id === payloadEntity.id,
+ );
+ if (newEntityIndex !== -1) {
+ (newState[entityKey] as MonkEntity[])[newEntityIndex] = payloadEntity;
+ } else {
+ (newState[entityKey] as MonkEntity[]).push(payloadEntity);
+ }
+ });
+ });
+ return newState;
+}
diff --git a/packages/public/common/src/state/actions/index.ts b/packages/public/common/src/state/actions/index.ts
new file mode 100644
index 000000000..62cf1da33
--- /dev/null
+++ b/packages/public/common/src/state/actions/index.ts
@@ -0,0 +1,5 @@
+export * from './monkAction';
+export * from './resetState';
+export * from './gotOneInspection';
+export * from './createdOneImage';
+export * from './updatedManyTasks';
diff --git a/packages/public/common/src/state/actions/monkAction.ts b/packages/public/common/src/state/actions/monkAction.ts
new file mode 100644
index 000000000..c3e2c1b15
--- /dev/null
+++ b/packages/public/common/src/state/actions/monkAction.ts
@@ -0,0 +1,31 @@
+/**
+ * Enumeration of the types of action that can be dispatched in the Monk state.
+ */
+export enum MonkActionType {
+ /**
+ * An inspection has been fetched from the API.
+ */
+ GOT_ONE_INSPECTION = 'got_one_inspection',
+ /**
+ * An image has been uploaded to the API.
+ */
+ CREATED_ONE_IMAGE = 'created_one_image',
+ /**
+ * One or more tasks have been updated.
+ */
+ UPDATED_MANY_TASKS = 'updated_many_tasks',
+ /**
+ * Clear and reset the state.
+ */
+ RESET_STATE = 'reset_state',
+}
+
+/**
+ * Type definition for a generic action dispatched in the Monk state.
+ */
+export interface MonkAction {
+ /**
+ * The type of the action.
+ */
+ type: MonkActionType;
+}
diff --git a/packages/public/common/src/state/actions/resetState.ts b/packages/public/common/src/state/actions/resetState.ts
new file mode 100644
index 000000000..88cabb7ee
--- /dev/null
+++ b/packages/public/common/src/state/actions/resetState.ts
@@ -0,0 +1,27 @@
+import { MonkAction, MonkActionType } from './monkAction';
+import { createEmptyMonkState, MonkState } from '../state';
+
+/**
+ * Action dispatched when the state needs to be reset.
+ */
+export interface MonkResetStateAction extends MonkAction {
+ /**
+ * The type of the action : `MonkActionType.RESET_STATE`.
+ */
+ type: MonkActionType.RESET_STATE;
+}
+
+/**
+ * Matcher function that matches a MonkGotOneAction while also inferring its type using TypeScript's type predicate
+ * feature.
+ */
+export function isResetStateAction(action: MonkAction): action is MonkResetStateAction {
+ return action.type === MonkActionType.RESET_STATE;
+}
+
+/**
+ * Reducer function for a ResetState action.
+ */
+export function resetState(): MonkState {
+ return createEmptyMonkState();
+}
diff --git a/packages/public/common/src/state/actions/updatedManyTasks.ts b/packages/public/common/src/state/actions/updatedManyTasks.ts
new file mode 100644
index 000000000..623ff6e99
--- /dev/null
+++ b/packages/public/common/src/state/actions/updatedManyTasks.ts
@@ -0,0 +1,50 @@
+import { ProgressStatus } from '@monkvision/types';
+import { MonkAction, MonkActionType } from './monkAction';
+import { MonkState } from '../state';
+
+/**
+ * Details about a task which status has been updated.
+ */
+export interface UpdatedTask {
+ id: string;
+ status: ProgressStatus;
+}
+
+/**
+ * Action dispatched when the status of multiple tasks have been updated.
+ */
+export interface MonkUpdatedManyTasksAction extends MonkAction {
+ /**
+ * The type of the action : `MonkActionType.UPDATED_ONE_TASK`.
+ */
+ type: MonkActionType.UPDATED_MANY_TASKS;
+ /**
+ * The payload of the action.
+ */
+ payload: UpdatedTask[];
+}
+
+/**
+ * Matcher function that matches a UpdatedManyTasksAction while also inferring its type using TypeScript's type
+ * predicate feature.
+ */
+export function isUpdatedManyTasksAction(action: MonkAction): action is MonkUpdatedManyTasksAction {
+ return action.type === MonkActionType.UPDATED_MANY_TASKS;
+}
+
+/**
+ * Reducer function for an UpdatedManyTasks action.
+ */
+export function updatedManyTasks(state: MonkState, action: MonkUpdatedManyTasksAction): MonkState {
+ const { tasks } = state;
+ action.payload.forEach((task) => {
+ const taskToUpdate = tasks.find((t) => t.id === task.id);
+ if (taskToUpdate) {
+ taskToUpdate.status = task.status;
+ }
+ });
+ return {
+ ...state,
+ tasks,
+ };
+}
diff --git a/packages/public/common/src/state/reducer.ts b/packages/public/common/src/state/reducer.ts
index 61224bb5e..d8ad50b23 100644
--- a/packages/public/common/src/state/reducer.ts
+++ b/packages/public/common/src/state/reducer.ts
@@ -1,49 +1,31 @@
-import { MonkEntity } from '@monkvision/types';
import {
+ createdOneImage,
+ gotOneInspection,
+ isCreatedOneImageAction,
+ isGotOneInspectionAction,
isResetStateAction,
- isUpdateStateAction,
+ isUpdatedManyTasksAction,
MonkAction,
- MonkUpdateStatePayload,
+ resetState,
+ updatedManyTasks,
} from './actions';
-import { createEmptyMonkState, MonkState } from './state';
-
-function updateState(state: MonkState, payload: MonkUpdateStatePayload): MonkState {
- const newState = createEmptyMonkState();
- Object.keys(state).forEach((key: string) => {
- const entityKey = key as keyof MonkState;
- state[entityKey].forEach((stateEntity) => {
- if (!payload.deleted || !payload.deleted.includes(stateEntity.id)) {
- (newState[entityKey] as MonkEntity[]).push(stateEntity);
- }
- });
- if (payload.entities) {
- payload.entities[entityKey]?.forEach((payloadEntity) => {
- if (payload.deleted && payload.deleted.includes(payloadEntity.id)) {
- return;
- }
- const newEntityIndex = newState[entityKey].findIndex(
- (newEntity) => newEntity.id === payloadEntity.id,
- );
- if (newEntityIndex !== -1) {
- (newState[entityKey] as MonkEntity[])[newEntityIndex] = payloadEntity;
- } else {
- (newState[entityKey] as MonkEntity[]).push(payloadEntity);
- }
- });
- }
- });
- return newState;
-}
+import { MonkState } from './state';
/**
* Main reducer function for the Monk state.
*/
export function monkReducer(state: MonkState, action: MonkAction): MonkState {
if (isResetStateAction(action)) {
- return createEmptyMonkState();
+ return resetState();
+ }
+ if (isGotOneInspectionAction(action)) {
+ return gotOneInspection(state, action);
+ }
+ if (isCreatedOneImageAction(action)) {
+ return createdOneImage(state, action);
}
- if (isUpdateStateAction(action)) {
- return updateState(state, action.payload);
+ if (isUpdatedManyTasksAction(action)) {
+ return updatedManyTasks(state, action);
}
return state;
}
diff --git a/packages/public/common/src/utils/array.utils.ts b/packages/public/common/src/utils/array.utils.ts
index 691e55e55..cf3eaf906 100644
--- a/packages/public/common/src/utils/array.utils.ts
+++ b/packages/public/common/src/utils/array.utils.ts
@@ -18,3 +18,65 @@ export function permutations(array: T[]): T[][] {
return result;
}
+
+/**
+ * Return a copy of the given array in which all duplicates have been removed.
+ */
+export function uniq(array: T[]): T[] {
+ const indexablePrimitives: Record<'number' | 'string', Record> = {
+ number: {},
+ string: {},
+ };
+ const objects: T[] = [];
+
+ return array.filter((item) => {
+ const type = typeof item;
+ if (type in indexablePrimitives) {
+ const primitiveType = type as 'number' | 'string';
+ const primitiveItem = item as number | string;
+ if (Object.hasOwn(indexablePrimitives[primitiveType], primitiveItem)) {
+ return false;
+ }
+ indexablePrimitives[primitiveType][primitiveItem] = true;
+ return true;
+ }
+ return objects.indexOf(item) >= 0 ? false : objects.push(item);
+ });
+}
+
+/**
+ * Type definition for an array of either elements of type T, or another recursive array of type T.
+ */
+export type RecursiveArray = (T | RecursiveArray)[];
+
+function flattenRecursive(array: RecursiveArray, result: T[]): void {
+ array.forEach((item) => {
+ if (Array.isArray(item)) {
+ flattenRecursive(item, result);
+ } else {
+ result.push(item);
+ }
+ });
+}
+
+/**
+ * Flatten the given array.
+ *
+ * @example
+ * console.log(flatten([ 1, [2, 3], [[4], [5, 6]]]));
+ * // Output : 1,2,3,4,5,6
+ */
+export function flatten(array: RecursiveArray): T[] {
+ const result: T[] = [];
+ flattenRecursive(array, result);
+ return result;
+}
+
+/**
+ * JS implementation of the
+ * [Array.prototype.flatMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap)
+ * method, available on all versions of JavaScript.
+ */
+export function flatMap(array: T[], map: (item: T) => RecursiveArray): K[] {
+ return flatten(array.map(map));
+}
diff --git a/packages/public/common/src/utils/index.ts b/packages/public/common/src/utils/index.ts
index 365c37496..d1527e3ad 100644
--- a/packages/public/common/src/utils/index.ts
+++ b/packages/public/common/src/utils/index.ts
@@ -1,5 +1,7 @@
export * from './string.utils';
export * from './array.utils';
export * from './color.utils';
+export * from './mimetype.utils';
+export * from './promise.utils';
export * from './zlib.utils';
export * from './browser.utils';
diff --git a/packages/public/common/src/utils/mimetype.utils.ts b/packages/public/common/src/utils/mimetype.utils.ts
new file mode 100644
index 000000000..04fbee242
--- /dev/null
+++ b/packages/public/common/src/utils/mimetype.utils.ts
@@ -0,0 +1,67 @@
+/**
+ * Datamap that associates mimetypes to known file extensions corresponding to this mimetype.
+ */
+export const MIMETYPE_FILE_EXTENSIONS: Record = {
+ 'text/plain': ['txt'],
+ 'text/html': ['html', 'htm', 'shtml'],
+ 'text/css': ['css'],
+ 'text/xml': ['xml'],
+
+ 'image/gif': ['gif'],
+ 'image/jpeg': ['jpeg', 'jpg'],
+ 'image/png': ['png'],
+ 'image/tiff': ['tif', 'tiff'],
+ 'image/vnd.wap.wbmp': ['wbmp'],
+ 'image/x-icon': ['ico'],
+ 'image/x-jng': ['jng'],
+ 'image/x-ms-bmp': ['bmp'],
+ 'image/svg+xml': ['svg'],
+ 'image/webp': ['webp'],
+
+ 'application/octet-stream': ['bin', 'exe', 'dll', 'eot', 'iso', 'img', 'msi', 'msp', 'msm'],
+ 'application/x-javascript': ['js'],
+ 'application/pdf': ['pdf'],
+ 'application/xhtml+xml': ['xhtml'],
+ 'application/zip': ['zip'],
+
+ 'audio/midi': ['mid', 'midi', 'kar'],
+ 'audio/mpeg': ['mp3'],
+ 'audio/ogg': ['ogg'],
+ 'audio/x-realaudio': ['ra'],
+
+ 'video/3gpp': ['3gpp', '3gp'],
+ 'video/mpeg': ['mpeg', 'mpg'],
+ 'video/quicktime': ['mov'],
+ 'video/x-flv': ['flv'],
+ 'video/x-mng': ['mng'],
+ 'video/x-ms-asf': ['asx', 'asf'],
+ 'video/x-ms-wmv': ['wmv'],
+ 'video/x-msvideo': ['avi'],
+ 'video/mp4': ['m4v', 'mp4'],
+};
+
+/**
+ * Returns a list of file extensions known to be corresponding to the given mimetype. If no file extension is known for
+ * this mimetype, this function will throw an error.
+ */
+export function getFileExtensions(mimetype: string): string[] {
+ const extensions = MIMETYPE_FILE_EXTENSIONS[mimetype];
+ if (!extensions) {
+ throw new Error(`Unknown mimetype : ${mimetype}`);
+ }
+ return extensions;
+}
+
+/**
+ * Returns the mimetype associated with the given file extension. If the file extension is unknown, this function will
+ * throw an error.
+ */
+export function getMimetype(fileExtension: string): string {
+ const mimetype = Object.entries(MIMETYPE_FILE_EXTENSIONS).find(([, extensions]) =>
+ extensions.includes(fileExtension),
+ )?.[0];
+ if (!mimetype) {
+ throw new Error(`Unknown file extension : ${fileExtension}`);
+ }
+ return mimetype;
+}
diff --git a/packages/public/common/src/utils/promise.utils.ts b/packages/public/common/src/utils/promise.utils.ts
new file mode 100644
index 000000000..af028d971
--- /dev/null
+++ b/packages/public/common/src/utils/promise.utils.ts
@@ -0,0 +1,8 @@
+/**
+ * This function creates and returns a new Promise that will resolve to void after the given amount of milliseconds.
+ */
+export function timeoutPromise(delayMs: number): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(), delayMs);
+ });
+}
diff --git a/packages/public/common/test/hooks/useAsyncEffect.test.ts b/packages/public/common/test/hooks/useAsyncEffect.test.ts
new file mode 100644
index 000000000..3f730a08a
--- /dev/null
+++ b/packages/public/common/test/hooks/useAsyncEffect.test.ts
@@ -0,0 +1,133 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { useAsyncEffect } from '../../src';
+import { createFakePromise } from '@monkvision/test-utils';
+import { waitFor } from '@testing-library/react';
+
+describe('useAsyncEffect hook', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should call the effect on the first render', () => {
+ const effect = jest.fn(() => Promise.resolve());
+ const { unmount } = renderHook((props) => useAsyncEffect(props.effect, props.deps), {
+ initialProps: { effect, deps: [] },
+ });
+ expect(effect).toHaveBeenCalled();
+ unmount();
+ });
+
+ it('should call the effect again only if the dependencies change', () => {
+ const effect = jest.fn(() => Promise.resolve());
+ const dep = 1;
+ const { unmount, rerender } = renderHook((props) => useAsyncEffect(props.effect, props.deps), {
+ initialProps: { effect, deps: [dep] },
+ });
+ expect(effect).toHaveBeenCalledTimes(1);
+ rerender({ effect, deps: [dep] });
+ expect(effect).toHaveBeenCalledTimes(1);
+ rerender({ effect, deps: [dep + 1] });
+ expect(effect).toHaveBeenCalledTimes(2);
+ unmount();
+ });
+
+ it('should call the onResolve handler when provided', async () => {
+ const result = 35;
+ const effect = jest.fn(() => Promise.resolve(result));
+ const onResolve = jest.fn();
+ const { unmount } = renderHook(
+ (props) => useAsyncEffect(props.effect, props.deps, props.handlers),
+ {
+ initialProps: { effect, deps: [], handlers: { onResolve } },
+ },
+ );
+ await waitFor(() => {
+ expect(onResolve).toHaveBeenCalledWith(result);
+ });
+ unmount();
+ });
+
+ it('should call the onReject handler when provided', async () => {
+ const err = 22;
+ const effect = jest.fn(() => Promise.reject(err));
+ const onReject = jest.fn();
+ const { unmount } = renderHook(
+ (props) => useAsyncEffect(props.effect, props.deps, props.handlers),
+ {
+ initialProps: { effect, deps: [], handlers: { onReject } },
+ },
+ );
+ await waitFor(() => {
+ expect(onReject).toHaveBeenCalledWith(err);
+ });
+ unmount();
+ });
+
+ it('should call the onComplete handler after the promise resolves', async () => {
+ const effect = jest.fn(() => Promise.resolve());
+ const onComplete = jest.fn();
+ const { unmount } = renderHook(
+ (props) => useAsyncEffect(props.effect, props.deps, props.handlers),
+ {
+ initialProps: { effect, deps: [], handlers: { onComplete } },
+ },
+ );
+ await waitFor(() => {
+ expect(onComplete).toHaveBeenCalled();
+ });
+ unmount();
+ });
+
+ it('should call the onComplete handler after the promise rejects', async () => {
+ const effect = jest.fn(() => Promise.reject());
+ const onComplete = jest.fn();
+ const { unmount } = renderHook(
+ (props) => useAsyncEffect(props.effect, props.deps, props.handlers),
+ {
+ initialProps: { effect, deps: [], handlers: { onComplete } },
+ },
+ );
+ await waitFor(() => {
+ expect(onComplete).toHaveBeenCalled();
+ });
+ unmount();
+ });
+
+ it('should not call handlers if the promise completes after the unmount', async () => {
+ const fakePromise = createFakePromise();
+ const effect = jest.fn(() => fakePromise);
+ const onResolve = jest.fn();
+ const onReject = jest.fn();
+ const onComplete = jest.fn();
+ const { unmount } = renderHook(
+ (props) => useAsyncEffect(props.effect, props.deps, props.handlers),
+ {
+ initialProps: { effect, deps: [], handlers: { onResolve, onReject, onComplete } },
+ },
+ );
+ unmount();
+ fakePromise.resolve({});
+ expect(onResolve).not.toHaveBeenCalled();
+ expect(onReject).not.toHaveBeenCalled();
+ expect(onComplete).not.toHaveBeenCalled();
+ });
+
+ it('should not call handlers if the promise rejects after the unmount', async () => {
+ const fakePromise = createFakePromise();
+ const effect = jest.fn(() => fakePromise);
+ const onResolve = jest.fn();
+ const onReject = jest.fn();
+ const onComplete = jest.fn();
+ const { unmount } = renderHook(
+ (props) => useAsyncEffect(props.effect, props.deps, props.handlers),
+ {
+ initialProps: { effect, deps: [], handlers: { onResolve, onReject, onComplete } },
+ },
+ );
+ unmount();
+ fakePromise.reject({});
+ expect(onResolve).not.toHaveBeenCalled();
+ expect(onReject).not.toHaveBeenCalled();
+ expect(onComplete).not.toHaveBeenCalled();
+ });
+});
diff --git a/packages/public/common/test/hooks/useLangProp.test.ts b/packages/public/common/test/hooks/useLangProp.test.ts
new file mode 100644
index 000000000..76b30f271
--- /dev/null
+++ b/packages/public/common/test/hooks/useLangProp.test.ts
@@ -0,0 +1,75 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { useTranslation } from 'react-i18next';
+import { useLangProp } from '../../src/hooks/useLangProp';
+import { useMonitoring } from '@monkvision/monitoring';
+import { waitFor } from '@testing-library/react';
+
+function getChangeLanguage(): jest.Mock {
+ expect(useTranslation).toHaveBeenCalled();
+ const lastCall = (useTranslation as jest.Mock).mock.results.length - 1;
+ return (useTranslation as jest.Mock).mock.results[lastCall].value.i18n
+ .changeLanguage as jest.Mock;
+}
+
+describe('useLangProp hook', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should update the language on the first render', () => {
+ const lang = 'fr';
+ const { unmount } = renderHook(useLangProp, { initialProps: lang });
+
+ expect(getChangeLanguage()).toHaveBeenCalledWith(lang);
+
+ unmount();
+ });
+
+ it('should change the language everytime the lang param changes', () => {
+ let lang = 'fr';
+ const { rerender, unmount } = renderHook(useLangProp, { initialProps: lang });
+
+ expect(getChangeLanguage()).toHaveBeenCalledWith(lang);
+
+ lang = 'en';
+ expect(getChangeLanguage()).not.toHaveBeenCalledWith(lang);
+ rerender(lang);
+ expect(getChangeLanguage()).toHaveBeenCalledWith(lang);
+
+ unmount();
+ });
+
+ it('should not change the language if the lang parameter is not provided', () => {
+ const { unmount } = renderHook(useLangProp);
+
+ expect(getChangeLanguage()).not.toHaveBeenCalled();
+
+ unmount();
+ });
+
+ it('should call handleError if the changeLanguage call fails', async () => {
+ const err = 'test-salut';
+ (useTranslation as jest.Mock).mockImplementationOnce(() => ({
+ i18n: { changeLanguage: jest.fn(() => Promise.reject(err)) },
+ }));
+ const { unmount } = renderHook(useLangProp, { initialProps: 'fr' });
+
+ expect(useMonitoring).toHaveBeenCalled();
+ const { handleError } = (useMonitoring as jest.Mock).mock.results[0].value;
+ await waitFor(() => {
+ expect(handleError).toHaveBeenCalledWith(err);
+ });
+
+ unmount();
+ });
+
+ it('should throw an error if the given language is not supported', () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ const { result, unmount } = renderHook(useLangProp, { initialProps: 'unknown-lang' });
+
+ expect(result.error).toBeDefined();
+
+ unmount();
+ jest.spyOn(console, 'error').mockRestore();
+ });
+});
diff --git a/packages/public/common/test/hooks/useLoadingState.test.ts b/packages/public/common/test/hooks/useLoadingState.test.ts
new file mode 100644
index 000000000..75ac85efb
--- /dev/null
+++ b/packages/public/common/test/hooks/useLoadingState.test.ts
@@ -0,0 +1,76 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { useLoadingState } from '../../src';
+import { act } from '@testing-library/react';
+
+describe('useLoadingState hook', () => {
+ it('should not be loading initially', () => {
+ const { result, unmount } = renderHook(useLoadingState);
+ expect(result.current.isLoading).toBe(false);
+ unmount();
+ });
+
+ it('should not be in error initially', () => {
+ const { result, unmount } = renderHook(useLoadingState);
+ expect(result.current.error).toBe(null);
+ unmount();
+ });
+
+ it('should start loading when the start function is called', () => {
+ const { result, unmount } = renderHook(useLoadingState);
+ expect(result.current.isLoading).toBe(false);
+ act(() => {
+ result.current.start();
+ });
+ expect(result.current.isLoading).toBe(true);
+ unmount();
+ });
+
+ it('should stop loading with no error when the onSuccess function is called', () => {
+ const { result, unmount } = renderHook(useLoadingState);
+ expect(result.current.isLoading).toBe(false);
+ expect(result.current.error).toBe(null);
+ act(() => {
+ result.current.start();
+ });
+ expect(result.current.isLoading).toBe(true);
+ expect(result.current.error).toBe(null);
+ act(() => {
+ result.current.onSuccess();
+ });
+ expect(result.current.isLoading).toBe(false);
+ expect(result.current.error).toBe(null);
+ unmount();
+ });
+
+ it('should stop loading with an error when the onError function is called', () => {
+ const { result, unmount } = renderHook(useLoadingState);
+ expect(result.current.isLoading).toBe(false);
+ expect(result.current.error).toBe(null);
+ act(() => {
+ result.current.start();
+ });
+ expect(result.current.isLoading).toBe(true);
+ expect(result.current.error).toBe(null);
+ const error = 23;
+ act(() => {
+ result.current.onError(error);
+ });
+ expect(result.current.isLoading).toBe(false);
+ expect(result.current.error).toBe(error);
+ unmount();
+ });
+
+ it('should reset the errror status when the start function is called', () => {
+ const { result, unmount } = renderHook(useLoadingState);
+ expect(result.current.error).toBe(null);
+ act(() => {
+ result.current.onError();
+ });
+ expect(result.current.error).not.toBe(null);
+ act(() => {
+ result.current.start();
+ });
+ expect(result.current.error).toBe(null);
+ unmount();
+ });
+});
diff --git a/packages/public/common/test/hooks/useObjectTranslation.test.ts b/packages/public/common/test/hooks/useObjectTranslation.test.ts
index 58364e191..52f481715 100644
--- a/packages/public/common/test/hooks/useObjectTranslation.test.ts
+++ b/packages/public/common/test/hooks/useObjectTranslation.test.ts
@@ -1,6 +1,3 @@
-jest.mock('i18next');
-jest.mock('react-i18next');
-
import { monkLanguages, TranslationObject } from '@monkvision/types';
import { useTranslation } from 'react-i18next';
import { renderHook } from '@testing-library/react-hooks';
diff --git a/packages/public/common/test/hooks/useSightLabel.test.ts b/packages/public/common/test/hooks/useSightLabel.test.ts
index 40d1e6822..83067d5c6 100644
--- a/packages/public/common/test/hooks/useSightLabel.test.ts
+++ b/packages/public/common/test/hooks/useSightLabel.test.ts
@@ -1,6 +1,3 @@
-jest.mock('@monkvision/common');
-jest.mock('react-i18next');
-
import { renderHook } from '@testing-library/react-hooks';
import { LabelDictionary, Sight, TranslationObject } from '@monkvision/types';
import { useObjectTranslation, useSightLabel } from '../../src';
diff --git a/packages/public/common/test/i18n/utils.test.tsx b/packages/public/common/test/i18n/utils.test.tsx
index df555c4e8..d3599f275 100644
--- a/packages/public/common/test/i18n/utils.test.tsx
+++ b/packages/public/common/test/i18n/utils.test.tsx
@@ -1,7 +1,3 @@
-jest.mock('@monkvision/monitoring');
-jest.mock('i18next');
-jest.mock('react-i18next');
-
import {
expectComponentToPassDownRefToHTMLElement,
expectPropsOnChildMock,
@@ -120,7 +116,7 @@ describe('Monkvision i18n utils', () => {
expectPropsOnChildMock(I18nextProvider, { i18n: instance });
- expect(screen.queryByTestId(TEST_COMPONENT_TEST_ID)).toBeDefined();
+ expect(screen.queryByTestId(TEST_COMPONENT_TEST_ID)).not.toBeNull();
unmount();
});
diff --git a/packages/public/common/test/state/actions/createdOneImage.test.ts b/packages/public/common/test/state/actions/createdOneImage.test.ts
new file mode 100644
index 000000000..e789b4797
--- /dev/null
+++ b/packages/public/common/test/state/actions/createdOneImage.test.ts
@@ -0,0 +1,64 @@
+import {
+ createdOneImage,
+ createEmptyMonkState,
+ isCreatedOneImageAction,
+ MonkActionType,
+ MonkCreatedOneImageAction,
+} from '../../../src';
+import { Image, ImageType, Inspection } from '@monkvision/types';
+
+const action: MonkCreatedOneImageAction = {
+ type: MonkActionType.CREATED_ONE_IMAGE,
+ payload: {
+ inspectionId: 'inspectionId',
+ image: {
+ id: 'imageId',
+ type: ImageType.BEAUTY_SHOT,
+ } as Image,
+ },
+};
+
+describe('CreatedOneImage action handlers', () => {
+ describe('Action matcher', () => {
+ it('should return true if the action has the proper type', () => {
+ expect(isCreatedOneImageAction({ type: MonkActionType.CREATED_ONE_IMAGE })).toBe(true);
+ });
+
+ it('should return false if the action does not have the proper type', () => {
+ expect(isCreatedOneImageAction({ type: MonkActionType.RESET_STATE })).toBe(false);
+ });
+ });
+
+ describe('Action handler', () => {
+ it('should return a new state', () => {
+ const state = createEmptyMonkState();
+ expect(Object.is(createdOneImage(state, action), state)).toBe(false);
+ });
+
+ it('should add the image to the state if it does not exist already', () => {
+ const state = createEmptyMonkState();
+ state.images.push({ id: 'coucou' } as Image);
+ const newState = createdOneImage(state, action);
+ expect(newState.images.length).toBe(2);
+ expect(newState.images).toContainEqual(action.payload.image);
+ });
+
+ it('should modify the image in the state if it exists already', () => {
+ const state = createEmptyMonkState();
+ state.images.push({ id: action.payload.image.id, siblingKey: 'test-wow-wow' } as Image);
+ const newState = createdOneImage(state, action);
+ expect(newState.images[0]).toEqual(action.payload.image);
+ });
+
+ it('should add the image to the inspection', () => {
+ const state = createEmptyMonkState();
+ state.inspections.push({
+ id: action.payload.inspectionId,
+ images: ['coucou'],
+ } as unknown as Inspection);
+ const newState = createdOneImage(state, action);
+ expect(newState.inspections[0].images.length).toBe(2);
+ expect(newState.inspections[0].images).toContainEqual(action.payload.image.id);
+ });
+ });
+});
diff --git a/packages/public/common/test/state/actions/gotOneInspection.test.ts b/packages/public/common/test/state/actions/gotOneInspection.test.ts
new file mode 100644
index 000000000..83d67fe31
--- /dev/null
+++ b/packages/public/common/test/state/actions/gotOneInspection.test.ts
@@ -0,0 +1,99 @@
+import {
+ createEmptyMonkState,
+ gotOneInspection,
+ isGotOneInspectionAction,
+ MonkActionType,
+ MonkGotOneInspectionAction,
+ MonkState,
+} from '../../../src';
+import {
+ Damage,
+ DamageType,
+ Image,
+ Inspection,
+ Part,
+ PartOperation,
+ RenderedOutput,
+ SeverityResult,
+ Task,
+ Vehicle,
+ VehiclePart,
+ View,
+} from '@monkvision/types';
+
+const action: MonkGotOneInspectionAction = {
+ type: MonkActionType.GOT_ONE_INSPECTION,
+ payload: {
+ damages: [{ id: 'damages-test' } as Damage],
+ images: [{ id: 'images-test' } as Image],
+ inspections: [{ id: 'inspections-test' } as Inspection],
+ parts: [{ id: 'parts-test' } as Part],
+ partOperations: [{ id: 'partOperations-test' } as PartOperation],
+ renderedOutputs: [{ id: 'renderedOutputs-test' } as RenderedOutput],
+ severityResults: [{ id: 'severityResults-test' } as SeverityResult],
+ tasks: [{ id: 'tasks-test' } as Task],
+ vehicles: [{ id: 'vehicles-test' } as Vehicle],
+ views: [{ id: 'views-test' } as View],
+ },
+};
+
+describe('GotOneInspection action handlers', () => {
+ describe('Action matcher', () => {
+ it('should return true if the action has the proper type', () => {
+ expect(isGotOneInspectionAction({ type: MonkActionType.GOT_ONE_INSPECTION })).toBe(true);
+ });
+
+ it('should return false if the action does not have the proper type', () => {
+ expect(isGotOneInspectionAction({ type: MonkActionType.RESET_STATE })).toBe(false);
+ });
+ });
+
+ describe('Action handler', () => {
+ it('should return a new state', () => {
+ const state = createEmptyMonkState();
+ expect(Object.is(gotOneInspection(state, action), state)).toBe(false);
+ });
+
+ it('should add new entities to the state', () => {
+ const state = {
+ damages: [{ id: 'damages-test-111111' } as Damage],
+ images: [{ id: 'images-test-111111' } as Image],
+ inspections: [{ id: 'inspections-test-111111' } as Inspection],
+ parts: [{ id: 'parts-test-111111' } as Part],
+ partOperations: [{ id: 'partOperations-test-111111' } as PartOperation],
+ renderedOutputs: [{ id: 'renderedOutputs-test-111111' } as RenderedOutput],
+ severityResults: [{ id: 'severityResults-test-111111' } as SeverityResult],
+ tasks: [{ id: 'tasks-test-111111' } as Task],
+ vehicles: [{ id: 'vehicles-test-111111' } as Vehicle],
+ views: [{ id: 'views-test-111111' } as View],
+ };
+ const newState = gotOneInspection(state, action);
+ Object.keys(createEmptyMonkState()).forEach((key) => {
+ const entityKey = key as keyof MonkState;
+ expect(newState[entityKey].length).toEqual(2);
+ expect(newState[entityKey]).toContainEqual((action.payload as MonkState)[entityKey][0]);
+ });
+ });
+
+ it('should update existing entities in the state', () => {
+ const state = {
+ damages: [{ id: 'damages-test', type: DamageType.BODY_CRACK } as Damage],
+ images: [{ id: 'images-test', siblingKey: 'siblingKey' } as Image],
+ inspections: [{ id: 'inspections-test', images: ['test'] } as Inspection],
+ parts: [{ id: 'parts-test', type: VehiclePart.WHEEL } as Part],
+ partOperations: [{ id: 'partOperations-test', name: 'te' } as PartOperation],
+ renderedOutputs: [{ id: 'renderedOutputs-test', path: 'ew' } as RenderedOutput],
+ severityResults: [{ id: 'severityResults-test', inspectionId: 'tes' } as SeverityResult],
+ tasks: [{ id: 'tasks-test', images: ['ok'] } as Task],
+ vehicles: [{ id: 'vehicles-test', type: 'nice' } as Vehicle],
+ views: [{ id: 'views-test', elementId: 'ww' } as View],
+ };
+ const newState = gotOneInspection(state, action);
+ Object.keys(createEmptyMonkState()).forEach((key) => {
+ const entityKey = key as keyof MonkState;
+ expect(newState[entityKey].length).toEqual(1);
+ expect(newState[entityKey][0]).toEqual((action.payload as MonkState)[entityKey][0]);
+ });
+ });
+ });
+});
diff --git a/packages/public/common/test/state/actions/resetState.test.ts b/packages/public/common/test/state/actions/resetState.test.ts
new file mode 100644
index 000000000..6ce25b487
--- /dev/null
+++ b/packages/public/common/test/state/actions/resetState.test.ts
@@ -0,0 +1,19 @@
+import { createEmptyMonkState, isResetStateAction, MonkActionType, resetState } from '../../../src';
+
+describe('ResetState action handlers', () => {
+ describe('Action matcher', () => {
+ it('should return true if the action has the proper type', () => {
+ expect(isResetStateAction({ type: MonkActionType.RESET_STATE })).toBe(true);
+ });
+
+ it('should return false if the action does not have the proper type', () => {
+ expect(isResetStateAction({ type: MonkActionType.GOT_ONE_INSPECTION })).toBe(false);
+ });
+ });
+
+ describe('Action handler', () => {
+ it('should return a new empty state', () => {
+ expect(resetState()).toEqual(createEmptyMonkState());
+ });
+ });
+});
diff --git a/packages/public/common/test/state/actions/updatedManyTasks.test.ts b/packages/public/common/test/state/actions/updatedManyTasks.test.ts
new file mode 100644
index 000000000..86f2c0073
--- /dev/null
+++ b/packages/public/common/test/state/actions/updatedManyTasks.test.ts
@@ -0,0 +1,66 @@
+import {
+ createEmptyMonkState,
+ isUpdatedManyTasksAction,
+ MonkActionType,
+ MonkUpdatedManyTasksAction,
+ updatedManyTasks,
+} from '../../../src';
+import { ProgressStatus, Task, TaskName } from '@monkvision/types';
+
+const action: MonkUpdatedManyTasksAction = {
+ type: MonkActionType.UPDATED_MANY_TASKS,
+ payload: [
+ { id: 'test-1', status: ProgressStatus.TODO },
+ { id: 'test-2', status: ProgressStatus.IN_PROGRESS },
+ ],
+};
+
+describe('UpdatedManyTasks action handlers', () => {
+ describe('Action matcher', () => {
+ it('should return true if the action has the proper type', () => {
+ expect(isUpdatedManyTasksAction({ type: MonkActionType.UPDATED_MANY_TASKS })).toBe(true);
+ });
+
+ it('should return false if the action does not have the proper type', () => {
+ expect(isUpdatedManyTasksAction({ type: MonkActionType.RESET_STATE })).toBe(false);
+ });
+ });
+
+ describe('Action handler', () => {
+ it('should return a new state', () => {
+ const state = createEmptyMonkState();
+ expect(Object.is(updatedManyTasks(state, action), state)).toBe(false);
+ });
+
+ it('should update tasks in the state', () => {
+ const state = createEmptyMonkState();
+ const task1 = {
+ id: action.payload[0].id,
+ name: TaskName.DAMAGE_DETECTION,
+ status: ProgressStatus.ERROR,
+ } as Task;
+ const task2 = {
+ id: action.payload[1].id,
+ name: TaskName.DASHBOARD_OCR,
+ status: ProgressStatus.ERROR,
+ } as Task;
+ state.tasks.push({ ...task1 }, { ...task2 });
+ const newState = updatedManyTasks(state, action);
+ expect(newState.tasks.length).toBe(2);
+ expect(newState.tasks).toContainEqual({
+ ...task1,
+ status: action.payload[0].status,
+ });
+ expect(newState.tasks).toContainEqual({
+ ...task2,
+ status: action.payload[1].status,
+ });
+ });
+
+ it('should not o anything if the tasks do not exist', () => {
+ const state = createEmptyMonkState();
+ const newState = updatedManyTasks(state, action);
+ expect(newState.tasks.length).toBe(0);
+ });
+ });
+});
diff --git a/packages/public/common/test/state/hooks.test.ts b/packages/public/common/test/state/hooks.test.ts
index 0561af196..5666d20de 100644
--- a/packages/public/common/test/state/hooks.test.ts
+++ b/packages/public/common/test/state/hooks.test.ts
@@ -1,8 +1,8 @@
+jest.mock('react');
+
import React from 'react';
import { MonkContext, createEmptyMonkState, MonkStateWithDispatch, useMonkState } from '../../src';
-jest.mock('react');
-
describe('useMonkState hook', () => {
it('should return the MonkContext', () => {
const context: MonkStateWithDispatch = {
diff --git a/packages/public/common/test/state/reducer.test.ts b/packages/public/common/test/state/reducer.test.ts
index 5c5b05b85..1441c8190 100644
--- a/packages/public/common/test/state/reducer.test.ts
+++ b/packages/public/common/test/state/reducer.test.ts
@@ -1,107 +1,52 @@
-import { Inspection, View } from '@monkvision/types';
+jest.mock('../../src/state/actions', () => ({
+ isCreatedOneImageAction: jest.fn(() => false),
+ isGotOneInspectionAction: jest.fn(() => false),
+ isResetStateAction: jest.fn(() => false),
+ isUpdatedManyTasksAction: jest.fn(() => false),
+ createdOneImage: jest.fn(() => null),
+ gotOneInspection: jest.fn(() => null),
+ resetState: jest.fn(() => null),
+ updatedManyTasks: jest.fn(() => null),
+}));
+
import {
- createEmptyMonkState,
+ createdOneImage,
+ gotOneInspection,
+ isCreatedOneImageAction,
+ isGotOneInspectionAction,
+ isResetStateAction,
+ isUpdatedManyTasksAction,
MonkAction,
- MonkActionType,
monkReducer,
- MonkResetStateAction,
- MonkUpdateStateAction,
+ MonkState,
+ resetState,
+ updatedManyTasks,
} from '../../src';
-describe('Monk state reducer', () => {
- describe('Unknown action', () => {
- it('should return the same state', () => {
- const state = createEmptyMonkState();
- state.views.push({ id: 'test' } as unknown as View);
- const action = { type: 'unknown' } as unknown as MonkAction;
- const result = monkReducer(state, action);
-
- expect(result).toEqual(state);
- });
- });
+const actions = [
+ { matcher: isResetStateAction, handler: resetState, noParams: true },
+ { matcher: isGotOneInspectionAction, handler: gotOneInspection },
+ { matcher: isCreatedOneImageAction, handler: createdOneImage },
+ { matcher: isUpdatedManyTasksAction, handler: updatedManyTasks },
+] as unknown as { matcher: jest.Mock; handler: jest.Mock; noParams?: boolean }[];
- describe('Reset state action', () => {
- it('should returns an empty new state for MonkResetStateActions', () => {
- const state = createEmptyMonkState();
- state.views.push({ id: 'test' } as unknown as View);
- const action: MonkResetStateAction = { type: MonkActionType.RESET_STATE };
- const result = monkReducer(state, action);
-
- expect(result).toEqual(createEmptyMonkState());
- });
+describe('Monk state reducer', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
});
- describe('Update state action', () => {
- it('should properly add new entities', () => {
- const exisingInspection = { id: 'test' } as unknown as Inspection;
- const newInspections = [{ id: 'ok' }, { id: 'lol' }] as unknown as Inspection[];
- const newViews = [{ id: 'yes' }] as unknown as View[];
- const state = createEmptyMonkState();
- state.inspections.push(exisingInspection);
- const action: MonkUpdateStateAction = {
- type: MonkActionType.UPDATE_STATE,
- payload: {
- entities: { inspections: newInspections, views: newViews },
- },
- };
- const result = monkReducer(state, action);
-
- expect(result).toEqual({
- ...createEmptyMonkState(),
- inspections: [exisingInspection, ...newInspections],
- views: newViews,
- });
- });
-
- it('should properly udpate existing entities', () => {
- const id = 'okayyyy';
- const exisingInspection = { id, val: 'nope' } as unknown as Inspection;
- const updatedInspection = { id, val: 'hello' } as unknown as Inspection;
- const state = createEmptyMonkState();
- state.inspections.push(exisingInspection);
- const action: MonkUpdateStateAction = {
- type: MonkActionType.UPDATE_STATE,
- payload: {
- entities: { inspections: [updatedInspection] },
- },
- };
+ it('should properly call the action matchers and helpers for each action type', () => {
+ actions.forEach((availableAction) => {
+ const newState = { test: 'hello' };
+ availableAction.matcher.mockImplementationOnce(() => true);
+ availableAction.handler.mockImplementationOnce(() => newState);
+ const state = { mock: 'state' } as unknown as MonkState;
+ const action = { fake: 'action' } as unknown as MonkAction;
const result = monkReducer(state, action);
-
- expect(result).toEqual({
- ...createEmptyMonkState(),
- inspections: [updatedInspection],
- });
- });
-
- it('should delete existing entities', () => {
- const id = 'okayyyy';
- const exisingInspection = { id } as unknown as Inspection;
- const state = createEmptyMonkState();
- state.inspections.push(exisingInspection);
- const action: MonkUpdateStateAction = {
- type: MonkActionType.UPDATE_STATE,
- payload: {
- deleted: [id],
- },
- };
- const result = monkReducer(state, action);
-
- expect(result).toEqual(createEmptyMonkState());
- });
-
- it('should not add deleted entities', () => {
- const id = 'okayyyy';
- const newInspection = { id } as unknown as Inspection;
- const action: MonkUpdateStateAction = {
- type: MonkActionType.UPDATE_STATE,
- payload: {
- entities: { inspections: [newInspection] },
- deleted: [id],
- },
- };
- const result = monkReducer(createEmptyMonkState(), action);
-
- expect(result).toEqual(createEmptyMonkState());
+ expect(availableAction.matcher).toHaveBeenCalledWith(action);
+ const params = availableAction.noParams ? [] : [state, action];
+ expect(availableAction.handler).toHaveBeenCalledWith(...params);
+ expect(result).toEqual(newState);
});
});
});
diff --git a/packages/public/common/test/theme/hooks.test.ts b/packages/public/common/test/theme/hooks.test.ts
index 1e5ab3c15..bfd9ade22 100644
--- a/packages/public/common/test/theme/hooks.test.ts
+++ b/packages/public/common/test/theme/hooks.test.ts
@@ -1,9 +1,9 @@
+jest.mock('react');
+
import { MonkTheme } from '@monkvision/types';
import React from 'react';
import { MonkThemeContext, useMonkTheme } from '../../src';
-jest.mock('react');
-
describe('useMonkTheme hook', () => {
it('should return the MonkThemeContext', () => {
const context = {
diff --git a/packages/public/common/test/utils/mimetype.utils.test.ts b/packages/public/common/test/utils/mimetype.utils.test.ts
new file mode 100644
index 000000000..e54ec9475
--- /dev/null
+++ b/packages/public/common/test/utils/mimetype.utils.test.ts
@@ -0,0 +1,41 @@
+import { getFileExtensions, getMimetype, MIMETYPE_FILE_EXTENSIONS } from '../../src';
+
+describe('Mimetype utils', () => {
+ describe('Mimetype file extension map', () => {
+ it('should be an object mapping mimetypes to arrays of file extensions', () => {
+ Object.keys(MIMETYPE_FILE_EXTENSIONS).forEach((mimetype) => {
+ expect(Array.isArray(MIMETYPE_FILE_EXTENSIONS[mimetype])).toBe(true);
+ expect(MIMETYPE_FILE_EXTENSIONS[mimetype].length).toBeGreaterThanOrEqual(1);
+ MIMETYPE_FILE_EXTENSIONS[mimetype].forEach((extension) => {
+ expect(typeof extension).toBe('string');
+ });
+ });
+ });
+ });
+
+ describe('getFileExtensions util function', () => {
+ it('should return the file extensions from the file extension map', () => {
+ Object.keys(MIMETYPE_FILE_EXTENSIONS).forEach((mimetype) => {
+ expect(getFileExtensions(mimetype)).toEqual(MIMETYPE_FILE_EXTENSIONS[mimetype]);
+ });
+ });
+
+ it('should throw if the file extension is unknown', () => {
+ expect(() => getFileExtensions('test')).toThrowError();
+ });
+ });
+
+ describe('getMimetype util function', () => {
+ it('should return the mimetype from the file extension map', () => {
+ Object.keys(MIMETYPE_FILE_EXTENSIONS).forEach((mimetype) => {
+ MIMETYPE_FILE_EXTENSIONS[mimetype].forEach((extension) => {
+ expect(getMimetype(extension)).toEqual(mimetype);
+ });
+ });
+ });
+
+ it('should throw if the mimetype is unknown', () => {
+ expect(() => getMimetype('test')).toThrowError();
+ });
+ });
+});
diff --git a/packages/public/common/test/utils/promise.utils.test.ts b/packages/public/common/test/utils/promise.utils.test.ts
new file mode 100644
index 000000000..918c72b91
--- /dev/null
+++ b/packages/public/common/test/utils/promise.utils.test.ts
@@ -0,0 +1,14 @@
+import { timeoutPromise } from '../../src';
+
+describe('Promise utils', () => {
+ describe('timeoutPromise util function', () => {
+ it('should create a promise that resolves after the specified delay', async () => {
+ const delay = 1000;
+ const startTime = Date.now();
+ await timeoutPromise(delay);
+ const actualDelay = Date.now() - startTime;
+ expect(actualDelay).toBeGreaterThanOrEqual(delay);
+ expect(actualDelay).toBeLessThan(delay + 10);
+ });
+ });
+});
diff --git a/packages/public/inspection-capture-web/README.md b/packages/public/inspection-capture-web/README.md
index bc76be6e4..22b4ee9fb 100644
--- a/packages/public/inspection-capture-web/README.md
+++ b/packages/public/inspection-capture-web/README.md
@@ -1,4 +1,10 @@
# @monkvision/inspection-capture-web
+This package provides utils, tools and ready-to-use components used to capture pictures needed to run Monk inspections.
+There are two main workflows for capturing pictures of a vehicle for a Monk inspection :
+- The **PhotoCapture** workflow : the user is shown a certain set of car wireframes, called *Sights*, and they are asked
+ to take pictures of the vehicle by aligning the vehicle with the Sight overlays.
+- The **VideoCapture** workflow : the user is asked to record a quick video of their vehicle by filming it and rotating
+ in a full circle around it.
# Installing
To install the package, you can run the following command :
@@ -9,3 +15,68 @@ yarn add @monkvision/inspection-capture-web
If you are using TypeScript, this package comes with its type definitions integrated, so you don't need to install
anything else!
+
+# PhotoCapture
+The PhotoCapture wofklow is aimed at guiding users in taking pictures of their vehicle in order to add them to a Monk
+inspection. The user is shown a set of car wireframes, which we call *Sights* and that are available in the
+`@monkvision/sights` package. These Sights act as guides, and the user is asked to take pictures of their vehicle by
+aligning it with the Sights.
+
+## Add Damage
+In this capture workflow, the user also has the option to manually take close-up pictures
+of a damage, in order to increase the detection rate. This feature is called `Add Damage`, and there two workflows
+available for it :
+- **Part Selection** : The user is first asked to select where the damage is located on the car (by clicking on the
+ corresponding car part on a 2D car model), and then they are asked to take a close-up picture of the damage.
+- **2-Shot** : The user is first asked to take a picture centered on the damage, but containing the entire car (in order
+ to allow our AI models to automatically detect where the damage is located on the car), and then they are asked to
+ take a close-up picture of the damage.
+
+For now, only the 2-shot workflow is implemented in the PhotoCapture workflow.
+
+## PhotoCapture component
+This package exports a ready-to-use single-page component called `PhotoCapture` that implements the PhotoCapture
+workflow. In order to use it, simply create a new page in your application containing just this component. You will then
+need to generate a Monk authentication token (using Auth0), and create a new inspection, in which all tasks statuses are
+set to `NOT_STARTED` (so that we can add images to them later). You can then pass the api config (with the auth token),
+the inspection ID, as well as a list of Sights (to display to the user) to the `PhotoCapture` component. Once the user
+has completed the capture workflow, the `onComplete` callback will be called, and you will then be able to navigate to
+another page. The complete list of configuration props for this component is available at the end of this section.
+
+```tsx
+import { sights } from '@monkvision/sights';
+import { PhotoCapture } from '@monkvision/inspection-capture-web';
+
+const PHOTO_CAPTURE_SIGHTS = [
+ sights['fesc20-0mJeXBDf'],
+ sights['fesc20-26n47kaO'],
+ sights['fesc20-2bLRuhEQ'],
+ sights['fesc20-4Wqx52oU'],
+ sights['fesc20-Tlu3sz8A'],
+ sights['fesc20-5Ts1UkPT'],
+ sights['fesc20-raHPDUNm'],
+];
+
+export function MonkPhotoCapturePage() {
+ return (
+ { /* Navigate to another page */ }}
+ />
+ );
+}
+```
+
+| Prop | Type | Description | Required | Default Value |
+|----------------------|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------|
+| sights | Sight[] | The list of Sights to take pictures of. The values in this array should be retreived from the `@monkvision/sights` package. | ✔️ | |
+| inspectionId | string | The ID of the inspection to add images to. Make sure that the user that created the inspection if the same one as the one described in the auth token in the `apiConfig` prop. | ✔️ | |
+| apiConfig | ApiConfig | The api config used to communicate with the API. Make sure that the user described in the auth token is the same one as the one that created the inspection provided in the `inspectionId` prop. | ✔️ | |
+| compliances | ComplianceOptions | Options used to specify compliance checks to be run on the pictures taken by the user. | | |
+| tasksBySight | `Record` | Record associating each sight with a list of tasks to execute for it. If not provided, the default tasks of the sight will be used. | | |
+| startTasksOnComplete | boolean | TaskName[] | Value indicating if tasks should be started at the end of the inspection : If not provided or if value is set to `false`, no tasks will be started. If set to `true`, the tasks described by the `tasksBySight` param (or, if not provided, the default tasks of each sight) will be started. If an array of tasks is provided, the tasks started will be the ones contained in the array. | | |
+| onClose | `() => void` | Callback called when the user clicks on the Close button. If this callback is not provided, the button will not be displayed on the screen. | | |
+| onComplete | `() => void` | Callback called when inspection capture is complete. | | |
diff --git a/packages/public/inspection-capture-web/package.json b/packages/public/inspection-capture-web/package.json
index afa2fe1a7..3593a0849 100644
--- a/packages/public/inspection-capture-web/package.json
+++ b/packages/public/inspection-capture-web/package.json
@@ -28,6 +28,7 @@
"@monkvision/camera-web": "4.0.0",
"@monkvision/common": "4.0.0",
"@monkvision/common-ui-web": "4.0.0",
+ "@monkvision/sights": "4.0.0",
"i18next": "^23.4.5",
"react-i18next": "^13.2.0"
},
@@ -40,7 +41,9 @@
"@monkvision/eslint-config-typescript": "4.0.0",
"@monkvision/eslint-config-typescript-react": "4.0.0",
"@monkvision/jest-config": "4.0.0",
+ "@monkvision/network": "4.0.0",
"@monkvision/prettier-config": "4.0.0",
+ "@monkvision/sights": "4.0.0",
"@monkvision/test-utils": "4.0.0",
"@monkvision/typescript-config": "4.0.0",
"@testing-library/react": "^12.1.5",
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx
index b06850a78..14e81720a 100644
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx
@@ -1,44 +1,141 @@
-import {
- Camera,
- CameraFacingMode,
- CameraHUDProps,
- CameraResolution,
- CompressionFormat,
- MonkPicture,
-} from '@monkvision/camera-web';
-import { Sight } from '@monkvision/types';
-import { i18nWrap } from '@monkvision/common';
-import { useState } from 'react';
-import { PhotoCaptureHUD } from './PhotoCaptureHUD';
+import { Camera, CameraConfig, CameraHUDProps, CompressionOptions } from '@monkvision/camera-web';
+import { Sight, TaskName } from '@monkvision/types';
+import { useLoadingState } from '@monkvision/common';
+import { ComplianceOptions, MonkAPIConfig } from '@monkvision/network';
+import { useMonitoring } from '@monkvision/monitoring';
+import { PhotoCaptureHUD, PhotoCaptureHUDProps } from './PhotoCaptureHUD';
import { styles } from './PhotoCapture.styles';
-import { i18nPhotoCaptureHUD } from './i18n';
+import {
+ useAddDamageMode,
+ usePhotoCaptureSightState,
+ usePictureTaken,
+ useStartTasksOnComplete,
+ useUploadQueue,
+} from './hooks';
-export interface PhotoCaptureProps {
+/**
+ * Props of the PhotoCapture component.
+ */
+export interface PhotoCaptureProps extends Partial, Partial {
+ /**
+ * The list of sights to take pictures of. The values in this array should be retreived from the `@monkvision/sights`
+ * package.
+ */
sights: Sight[];
+ /**
+ * The ID of the inspection to add images to. Make sure that the user that created the inspection if the same one as
+ * the one described in the auth token in the `apiConfig` prop.
+ */
+ inspectionId: string;
+ /**
+ * The api config used to communicate with the API. Make sure that the user described in the auth token is the same
+ * one as the one that created the inspection provided in the `inspectionId` prop.
+ */
+ apiConfig: MonkAPIConfig;
+ /**
+ * Options used to specify compliance checks to be run on the pictures taken by the user.
+ */
+ compliances?: ComplianceOptions;
+ /**
+ * Record associating each sight with a list of tasks to execute for it. If not provided, the default tasks of the
+ * sight will be used.
+ */
+ tasksBySight?: Record;
+ /**
+ * Value indicating if tasks should be started at the end of the inspection :
+ * - If not provided or if value is set to `false`, no tasks will be started.
+ * - If set to `true`, the tasks described by the `tasksBySight` param (or, if not provided, the default tasks of each
+ * sight) will be started.
+ * - If an array of tasks is provided, the tasks started will be the ones contained in the array.
+ */
+ startTasksOnComplete?: boolean | TaskName[];
+ /**
+ * Callback called when the user clicks on the Close button. If this callback is not provided, the button will not be
+ * displayed on the screen.
+ */
+ onClose?: () => void;
+ /**
+ * Callback called when inspection capture is complete.
+ */
+ onComplete?: () => void;
}
-export const PhotoCapture = i18nWrap(({ sights }: PhotoCaptureProps) => {
- const [cameraState] = useState({
- facingMode: CameraFacingMode.ENVIRONMENT,
- resolution: CameraResolution.UHD_4K,
- compressionFormat: CompressionFormat.JPEG,
- quality: '0.8',
+// No ts-doc for this component : the component exported is PhotoCaptureHOC
+export function PhotoCapture({
+ sights,
+ inspectionId,
+ apiConfig,
+ tasksBySight,
+ startTasksOnComplete = true,
+ onClose,
+ onComplete,
+ compliances,
+ ...cameraConfig
+}: PhotoCaptureProps) {
+ const { handleError } = useMonitoring();
+ const loading = useLoadingState();
+ const addDamageHandle = useAddDamageMode();
+ const { startTasks } = useStartTasksOnComplete({
+ inspectionId,
+ apiConfig,
+ sights,
+ tasksBySight,
+ startTasksOnComplete,
+ loading,
+ });
+ const onLastSightTaken = () => {
+ startTasks()
+ .then(() => {
+ onComplete?.();
+ })
+ .catch((err) => {
+ loading.onError(err);
+ handleError(err);
+ });
+ };
+ const sightState = usePhotoCaptureSightState({
+ inspectionId,
+ captureSights: sights,
+ apiConfig,
+ loading,
+ onLastSightTaken,
+ });
+ const uploadQueue = useUploadQueue({
+ inspectionId,
+ apiConfig,
+ compliances,
+ loading,
+ });
+ const { handlePictureTaken } = usePictureTaken({
+ sightState,
+ addDamageHandle,
+ uploadQueue,
+ tasksBySight,
});
- const hud = (props: CameraHUDProps) => ;
- const handleTakePicture = (picture: MonkPicture) => {
- console.log('Picture Taken :', picture);
+ const hudProps: Omit = {
+ sights,
+ selectedSight: sightState.selectedSight,
+ sightsTaken: sightState.sightsTaken,
+ lastPictureTaken: sightState.lastPictureTaken,
+ mode: addDamageHandle.mode,
+ onSelectSight: sightState.selectSight,
+ onAddDamage: addDamageHandle.handleAddDamage,
+ onCancelAddDamage: addDamageHandle.handleCancelAddDamage,
+ onRetry: sightState.retryLoadingInspection,
+ loading,
+ onClose,
+ inspectionId,
};
return (
);
-}, i18nPhotoCaptureHUD);
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHOC.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHOC.tsx
new file mode 100644
index 000000000..15a55cedc
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHOC.tsx
@@ -0,0 +1,49 @@
+import { i18nWrap, MonkProvider, useI18nLink } from '@monkvision/common';
+import { i18nCamera } from '@monkvision/camera-web';
+import { i18nInspectionCaptureWeb } from '../i18n';
+import { PhotoCapture, PhotoCaptureProps } from './PhotoCapture';
+
+/**
+ * The PhotoCapture component is a ready-to-use, single page component that implements a Camera app and lets the user
+ * take pictures of their vehicle in order to add them to an already created Monk inspection. In order to use this
+ * component, you first need to generate an Auth0 authentication token, and create an inspection using the Monk Api.
+ * When creating the inspection, don't forget to set the tasks statuses to `NOT_STARTED`. This component will handle the
+ * starting of the tasks at the end of the capturing process. You can then pass the inspection ID, the api config (with
+ * the auth token), as well as the list of sights to be taken by the user to this component, and everything will be
+ * handled automatically for you.
+ *
+ * @example
+ * import { sights } from '@monkvision/sights';
+ * import { PhotoCapture } from '@monkvision/inspection-capture-web';
+ *
+ * const PHOTO_CAPTURE_SIGHTS = [
+ * sights['fesc20-0mJeXBDf'],
+ * sights['fesc20-26n47kaO'],
+ * sights['fesc20-2bLRuhEQ'],
+ * sights['fesc20-4Wqx52oU'],
+ * sights['fesc20-Tlu3sz8A'],
+ * sights['fesc20-5Ts1UkPT'],
+ * sights['fesc20-raHPDUNm'],
+ * ];
+ *
+ * export function PhotoCaptureScreen({ inspectionId, apiConfig }: PhotoCaptureScreenProps) {
+ * return (
+ * { / * Navigate to another page * / }}
+ * />
+ * );
+ * }
+ */
+export const PhotoCaptureHOC = i18nWrap((props: PhotoCaptureProps) => {
+ useI18nLink(i18nInspectionCaptureWeb, [i18nCamera]);
+
+ return (
+
+
+
+ );
+}, i18nInspectionCaptureWeb);
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD.tsx
deleted file mode 100644
index 182af5b39..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { useMemo } from 'react';
-import { Sight } from '@monkvision/types';
-import { CameraHUDProps } from '@monkvision/camera-web/lib/Camera/CameraHUD.types';
-import { PhotoCaptureHUDButtons } from './PhotoCaptureHUDButtons';
-import { PhotoCaptureHUDAddDamagePreview } from './PhotoCaptureHUDAddDamagePreview';
-import { PhotoCaptureHUDSightPreview } from './PhotoCaptureHUDSightPreview';
-import { HUDMode, usePhotoCaptureHUDStyle } from './hook';
-import { AddDamagePreviewMode, useSightState } from './hooks';
-
-export interface PhotoCaptureHUDProps extends CameraHUDProps {
- sights: Sight[];
-}
-
-/**
- * Displays the camera preview and HUD components for capturing photos with sights.
- *
- * @component
- *
- * @param {Sight[]} sights - The array of sights available for selection.
- *
- * @example
- * // Example usage of PhotoCaptureHUD component in Camera Component:
- * import { Camera } from '@monkvision/camera-web';
- * import { PhotoCaptureHUD } from '@monkvision/inspection-camera-web';
- *
- * export Function MyComponent () {
- * const [sights, setSights] = useState([]);
- * const hud = (props: CameraHUDProps) => ;
- *
- * return (
- *
- * );
- */
-export function PhotoCaptureHUD({ sights, cameraPreview, handle }: PhotoCaptureHUDProps) {
- const {
- selectedSight,
- setSelectedSight,
- sightsTaken,
- handleSightTaken,
- mode,
- setMode,
- addDamagePreviewMode,
- setAddDamagePreviewMode,
- } = useSightState(sights);
- const style = usePhotoCaptureHUDStyle();
-
- const hudPreview = useMemo(
- () =>
- mode === HUDMode.ADD_DAMAGE ? (
- {
- setMode(HUDMode.DEFAULT);
- setAddDamagePreviewMode(AddDamagePreviewMode.DEFAULT);
- }}
- sight={selectedSight}
- addDamagePreviewMode={addDamagePreviewMode}
- streamDimensions={handle?.dimensions}
- />
- ) : (
- setMode(HUDMode.ADD_DAMAGE)}
- streamDimensions={handle?.dimensions}
- />
- ),
- [mode, selectedSight, sightsTaken, handle?.dimensions, addDamagePreviewMode],
- );
- return (
-
-
- {cameraPreview}
- {hudPreview}
-
-
{
- handle?.takePicture?.();
- handleSightTaken();
- }}
- />
-
- );
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.styles.ts
similarity index 100%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD.styles.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.styles.ts
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx
new file mode 100644
index 000000000..6f949c5bd
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx
@@ -0,0 +1,140 @@
+import { useState } from 'react';
+import { Sight } from '@monkvision/types';
+import { useTranslation } from 'react-i18next';
+import { BackdropDialog } from '@monkvision/common-ui-web';
+import { CameraHUDProps, MonkPicture } from '@monkvision/camera-web';
+import { LoadingState } from '@monkvision/common';
+import { PhotoCaptureHUDButtons } from './PhotoCaptureHUDButtons';
+import { usePhotoCaptureHUDStyle } from './hooks';
+import { PhotoCaptureMode } from '../hooks';
+import { PhotoCaptureHUDOverlay } from './PhotoCaptureHUDOverlay';
+import { PhotoCaptureHUDPreview } from './PhotoCaptureHUDPreview';
+
+/**
+ * Props of the PhotoCaptureHUD component.
+ */
+export interface PhotoCaptureHUDProps extends CameraHUDProps {
+ /**
+ * The inspection ID.
+ */
+ inspectionId: string;
+ /**
+ * The list of sights provided to the PhotoCapture component.
+ */
+ sights: Sight[];
+ /**
+ * The currently selected sight in the PhotoCapture component : the sight that the user needs to capture.
+ */
+ selectedSight: Sight;
+ /**
+ * Array containing the list of sights that the user has already captured.
+ */
+ sightsTaken: Sight[];
+ /**
+ * Value storing the last picture taken by the user. If no picture has been taken yet, this value is null.
+ */
+ lastPictureTaken: MonkPicture | null;
+ /**
+ * The current mode of the component.
+ */
+ mode: PhotoCaptureMode;
+ /**
+ * Global loading state of the PhotoCapture component.
+ */
+ loading: LoadingState;
+ /**
+ * Callback called when the user manually select a new sight.
+ */
+ onSelectSight: (sight: Sight) => void;
+ /**
+ * Callback to be called when the user clicks on the "Add Damage" button.
+ */
+ onAddDamage: () => void;
+ /**
+ * Callback to be called when the user clicks on the "Cancel" button of the Add Damage mode.
+ */
+ onCancelAddDamage: () => void;
+ /**
+ * Callback that can be used to retry fetching this state object from the API in case the previous fetch failed.
+ */
+ onRetry: () => void;
+ /**
+ * Callback called when the user clicks on the close button. If this callback is not provided, the close button is not
+ * displayed.
+ */
+ onClose?: () => void;
+}
+
+/**
+ * This component implements the Camera HUD (head-up display) displayed in the PhotoCapture component over the Camera
+ * preview. It implements elements such as buttons to interact with the camera, PhotoCapture indicators, error messages,
+ * loaders etc.
+ */
+export function PhotoCaptureHUD({
+ inspectionId,
+ sights,
+ selectedSight,
+ sightsTaken,
+ lastPictureTaken,
+ mode,
+ onSelectSight,
+ onAddDamage,
+ onCancelAddDamage,
+ onRetry,
+ onClose,
+ loading,
+ handle,
+ cameraPreview,
+}: PhotoCaptureHUDProps) {
+ const { t } = useTranslation();
+ const [showCloseModal, setShowCloseModal] = useState(false);
+ const style = usePhotoCaptureHUDStyle();
+
+ const handleCloseConfirm = () => {
+ setShowCloseModal(false);
+ onClose?.();
+ };
+
+ return (
+
+
+
setShowCloseModal(true)}
+ galleryPreview={lastPictureTaken ?? undefined}
+ closeDisabled={!!loading.error || !!handle.error}
+ galleryDisabled={!!loading.error || !!handle.error}
+ takePictureDisabled={!!loading.error || !!handle.error}
+ />
+
+ setShowCloseModal(false)}
+ onConfirm={handleCloseConfirm}
+ />
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/CaptureHUDButtons.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles.ts
similarity index 100%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/CaptureHUDButtons.styles.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles.ts
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx
similarity index 63%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx
index 9673ad862..757260535 100644
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.tsx
@@ -3,21 +3,53 @@ import { Icon, TakePictureButton } from '@monkvision/common-ui-web';
import { useInteractiveStatus } from '@monkvision/common';
import { useCaptureHUDButtonsStyles } from './hooks';
+/**
+ * Props of the PhotoCaptureHUDButtons component.
+ */
export interface PhotoCaptureHUDButtonsProps {
+ /**
+ * Picture displayed in the gallery button icon. Usually, this is the last picture taken by the user. If no picture
+ * is provided, a gallery icon will be displayed instead.
+ */
galleryPreview?: MonkPicture;
+ /**
+ * Callback called when the user clicks on the take picture button.
+ */
onTakePicture?: () => void;
+ /**
+ * Callback called when the user clicks on the gallery button.
+ */
onOpenGallery?: () => void;
+ /**
+ * Callback called when the user clicks on the close button. If this callback is not provided, the close button will
+ * not be displayed on the screen.
+ */
onClose?: () => void;
+ /**
+ * Boolean indicating if the gallery button is disabled.
+ */
galleryDisabled?: boolean;
+ /**
+ * Boolean indicating if the take picture button is disabled.
+ */
takePictureDisabled?: boolean;
+ /**
+ * Boolean indicating if the close button is disabled.
+ */
closeDisabled?: boolean;
}
+/**
+ * Components implementing the main buttons of the PhotoCapture Camera HUD. This component implements 3 buttons :
+ * - A take picture button
+ * - A gallery button
+ * - A close button (only displayed if the `onClose` callback is defined)
+ */
export function PhotoCaptureHUDButtons({
galleryPreview,
- onTakePicture = () => {},
- onOpenGallery = () => {},
- onClose = () => {},
+ onTakePicture,
+ onOpenGallery,
+ onClose,
galleryDisabled = false,
takePictureDisabled = false,
closeDisabled = false,
@@ -31,6 +63,7 @@ export function PhotoCaptureHUDButtons({
const { containerStyle, gallery, close, backgroundCoverStyle } = useCaptureHUDButtonsStyles({
galleryStatus,
closeStatus,
+ closeBtnAvailable: !!onClose,
galleryPreviewUrl: galleryPreview?.uri,
});
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/hooks.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/hooks.ts
similarity index 69%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/hooks.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/hooks.ts
index 5e684bfbe..86b2b62ca 100644
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/hooks.ts
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/hooks.ts
@@ -1,15 +1,16 @@
import { InteractiveStatus } from '@monkvision/types';
-import { CSSProperties } from 'react';
-import { useResponsiveStyle } from '@monkvision/common';
+import { CSSProperties, useState } from 'react';
+import { useAsyncEffect, useResponsiveStyle, timeoutPromise } from '@monkvision/common';
import {
captureButtonBackgroundColors,
captureButtonForegroundColors,
styles,
-} from './CaptureHUDButtons.styles';
+} from './PhotoCaptureHUDButtons.styles';
interface PhotoCaptureHUDButtonsStylesParams {
galleryStatus: InteractiveStatus;
closeStatus: InteractiveStatus;
+ closeBtnAvailable: boolean;
galleryPreviewUrl?: string;
}
@@ -26,11 +27,25 @@ interface PhotoCaptureHUDButtonsStyles {
backgroundCoverStyle: CSSProperties;
}
+const ANIMATION_DELAY_MS = 50;
+
export function useCaptureHUDButtonsStyles(
params: PhotoCaptureHUDButtonsStylesParams,
): PhotoCaptureHUDButtonsStyles {
+ const [backgroundAnimationStart, setBackgroundAnimationStart] = useState(false);
const { responsive } = useResponsiveStyle();
+ useAsyncEffect(
+ () => {
+ setBackgroundAnimationStart(true);
+ return timeoutPromise(ANIMATION_DELAY_MS);
+ },
+ [params.galleryPreviewUrl],
+ {
+ onResolve: () => setBackgroundAnimationStart(false),
+ },
+ );
+
return {
containerStyle: {
...styles['container'],
@@ -51,12 +66,15 @@ export function useCaptureHUDButtonsStyles(
backgroundColor: captureButtonBackgroundColors[params.closeStatus],
borderColor: captureButtonForegroundColors[params.closeStatus],
...(params.closeStatus === InteractiveStatus.DISABLED ? styles['buttonDisabled'] : {}),
+ visibility: params.closeBtnAvailable ? 'visible' : 'hidden',
},
iconColor: captureButtonForegroundColors[params.closeStatus],
},
backgroundCoverStyle: {
...styles['backgroundCover'],
backgroundImage: params.galleryPreviewUrl ? `url(${params.galleryPreviewUrl})` : 'none',
+ transition: backgroundAnimationStart ? 'none' : 'transform 0.2s ease-out',
+ transform: `scale(${backgroundAnimationStart ? 0.3 : 1})`,
},
};
}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/index.ts
similarity index 100%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDButtons/index.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/index.ts
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/PhotoCaptureHUDCancelButton.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/PhotoCaptureHUDCancelButton.tsx
new file mode 100644
index 000000000..349b4bfa3
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/PhotoCaptureHUDCancelButton.tsx
@@ -0,0 +1,27 @@
+import { Button } from '@monkvision/common-ui-web';
+import { useTranslation } from 'react-i18next';
+import { usePhotoCaptureHUDButtonBackground } from '../hooks';
+
+/**
+ * Props of the PhotoCaptureHUDCancelButton component.
+ */
+export interface PhotoCaptureHUDCancelButtonProps {
+ /**
+ * Callback called when the user clicks on the button.
+ */
+ onCancel?: () => void;
+}
+
+/**
+ * Component implementing a cancel button displayed in the PhotoCapture Camera HUD.
+ */
+export function PhotoCaptureHUDCancelButton({ onCancel }: PhotoCaptureHUDCancelButtonProps) {
+ const { t } = useTranslation();
+ const backgroundColor = usePhotoCaptureHUDButtonBackground();
+
+ return (
+
+ {t('photo.hud.addDamage.cancelBtn')}
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/index.ts
new file mode 100644
index 000000000..aa5c1883c
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton/index.ts
@@ -0,0 +1,4 @@
+export {
+ PhotoCaptureHUDCancelButton,
+ type PhotoCaptureHUDCancelButtonProps,
+} from './PhotoCaptureHUDCancelButton';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter/DamageCounter.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.styles.ts
similarity index 100%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter/DamageCounter.styles.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.styles.ts
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.tsx
new file mode 100644
index 000000000..39f0561de
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/PhotoCaptureHUDCounter.tsx
@@ -0,0 +1,17 @@
+import { styles } from './PhotoCaptureHUDCounter.styles';
+import { usePhotoCaptureHUDButtonBackground } from '../hooks';
+import { PhotoCaptureHUDCounterProps, usePhotoCaptureHUDCounterLabel } from './hooks';
+
+/**
+ * Component that implements an indicator of pictures taken during the PhotoCapture process.
+ */
+export function PhotoCaptureHUDCounter(props: PhotoCaptureHUDCounterProps) {
+ const label = usePhotoCaptureHUDCounterLabel(props);
+ const backgroundColor = usePhotoCaptureHUDButtonBackground();
+
+ return (
+
+ {label}
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/hooks.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/hooks.ts
new file mode 100644
index 000000000..942f186cf
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/hooks.ts
@@ -0,0 +1,39 @@
+import { useTranslation } from 'react-i18next';
+import { PhotoCaptureMode } from '../../hooks';
+
+/**
+ * Props of the PhotoCaptureHUDCounter component.
+ */
+export type PhotoCaptureHUDCounterProps =
+ | {
+ /**
+ * The current mode of the PhotoCapture component.
+ */
+ mode: PhotoCaptureMode.SIGHT;
+ /**
+ * The total number of sights given to the PhotoCapture component.
+ */
+ totalSights: number;
+ /**
+ * The total number of sights taken by the user.
+ */
+ sightsTaken: number;
+ }
+ | {
+ /**
+ * The current mode of the PhotoCapture component.
+ */
+ mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT | PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT;
+ };
+
+export function usePhotoCaptureHUDCounterLabel(props: PhotoCaptureHUDCounterProps): string {
+ const { t } = useTranslation();
+
+ if (props.mode === PhotoCaptureMode.SIGHT) {
+ return `${props.sightsTaken} / ${props.totalSights}`;
+ }
+ if (props.mode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT) {
+ return t('photo.hud.addDamage.damagedPartCounter');
+ }
+ return t('photo.hud.addDamage.closeupPictureCounter');
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/index.ts
new file mode 100644
index 000000000..7b2aa8a93
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter/index.ts
@@ -0,0 +1,2 @@
+export { PhotoCaptureHUDCounter } from './PhotoCaptureHUDCounter';
+export { type PhotoCaptureHUDCounterProps } from './hooks';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.styles.ts
new file mode 100644
index 000000000..c0852c5d3
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.styles.ts
@@ -0,0 +1,38 @@
+import { Styles } from '@monkvision/types';
+
+export const styles: Styles = {
+ overlay: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ zIndex: 9,
+ backgroundColor: 'rgba(0,0,0,0.8)',
+ },
+ errorMessage: {
+ fontSize: 16,
+ fontFamily: 'sans-serif',
+ textAlign: 'center',
+ color: 'white',
+ maxWidth: '60vw',
+ paddingBottom: 20,
+ },
+ errorMessageMobile: {
+ __media: {
+ maxWidth: 750,
+ },
+ maxWidth: '90vw',
+ },
+ errorMessageTablet: {
+ __media: {
+ minWidth: 750,
+ maxWidth: 1100,
+ },
+ maxWidth: '75vw',
+ },
+};
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.tsx
new file mode 100644
index 000000000..83a25a489
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/PhotoCaptureHUDOverlay.tsx
@@ -0,0 +1,57 @@
+import { useTranslation } from 'react-i18next';
+import { Button, Spinner } from '@monkvision/common-ui-web';
+import { useResponsiveStyle } from '@monkvision/common';
+import { styles } from './PhotoCaptureHUDOverlay.styles';
+import { PhotoCaptureHUDOverlayProps, usePhotoCaptureErrorLabel, useRetry } from './hooks';
+
+/**
+ * Component that displays an overlay on top of the PhotoCapture component that is used to display elements such as
+ * error messages, spinning loaders etc.
+ */
+export function PhotoCaptureHUDOverlay({
+ isCaptureLoading,
+ captureError,
+ handle,
+ onRetry,
+ inspectionId,
+}: PhotoCaptureHUDOverlayProps) {
+ const { t } = useTranslation();
+ const { responsive } = useResponsiveStyle();
+ const error = usePhotoCaptureErrorLabel(captureError, handle, inspectionId);
+ const handleRetry = useRetry({ captureError, handle, onRetry });
+
+ if (!isCaptureLoading && !handle.isLoading && !error) {
+ return null;
+ }
+
+ return (
+
+ {!error && (isCaptureLoading || handle.isLoading) && (
+
+ )}
+ {error && (
+ <>
+
+ {error}
+
+ {handleRetry && (
+
+ {t('photo.hud.error.retry')}
+
+ )}
+ >
+ )}
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/hooks.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/hooks.ts
new file mode 100644
index 000000000..b76f52c56
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/hooks.ts
@@ -0,0 +1,90 @@
+import { CameraHandle, getCameraErrorLabel } from '@monkvision/camera-web';
+import { useTranslation } from 'react-i18next';
+import { useObjectTranslation } from '@monkvision/common';
+import { MonkNetworkError } from '@monkvision/network';
+
+/**
+ * Props of the PhotoCaptureHUDOverlay component.
+ */
+export interface PhotoCaptureHUDOverlayProps {
+ /**
+ * Boolean indicating if the global loading state of the PhotoCapture component is loading or not.
+ */
+ isCaptureLoading: boolean;
+ /**
+ * The error that occurred in the PhotoCapture component. Set this value to `null` if there is no error.
+ */
+ captureError: unknown | null;
+ /**
+ * The Camera handle.
+ */
+ handle: CameraHandle;
+ /**
+ * Callback called when the user wants to retry fetching inspection data.
+ */
+ onRetry: () => void;
+ /**
+ * The inspection ID.
+ */
+ inspectionId: string;
+}
+
+export function usePhotoCaptureErrorLabel(
+ captureError: unknown | null,
+ handle: CameraHandle,
+ inspectionId: string,
+): string | null {
+ const { t } = useTranslation();
+ const { tObj } = useObjectTranslation();
+ const cameraErrorLabel = getCameraErrorLabel(handle.error?.type);
+
+ if (handle.error && cameraErrorLabel) {
+ return tObj(cameraErrorLabel);
+ }
+ if (
+ captureError instanceof Error &&
+ [MonkNetworkError.MISSING_TOKEN, MonkNetworkError.INVALID_TOKEN].includes(
+ captureError.name as MonkNetworkError,
+ )
+ ) {
+ return t('photo.hud.error.invalidToken');
+ }
+ if (captureError instanceof Error && captureError.name === MonkNetworkError.EXPIRED_TOKEN) {
+ return t('photo.hud.error.expiredToken');
+ }
+ if (
+ captureError instanceof Error &&
+ captureError.name === MonkNetworkError.INSUFFICIENT_AUTHORIZATION
+ ) {
+ return t('photo.hud.error.insufficientAuth');
+ }
+ if (captureError) {
+ return `${t('photo.hud.error.inspectionLoading')} ${inspectionId}`;
+ }
+ return null;
+}
+
+export function useRetry({
+ captureError,
+ handle,
+ onRetry,
+}: Pick): (() => void) | null {
+ if (handle.error) {
+ return handle.retry;
+ }
+ if (
+ captureError instanceof Error &&
+ [
+ MonkNetworkError.MISSING_TOKEN,
+ MonkNetworkError.INVALID_TOKEN,
+ MonkNetworkError.EXPIRED_TOKEN,
+ MonkNetworkError.INSUFFICIENT_AUTHORIZATION,
+ ].includes(captureError.name as MonkNetworkError)
+ ) {
+ return null;
+ }
+ if (captureError) {
+ return onRetry;
+ }
+ return null;
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/index.ts
new file mode 100644
index 000000000..6952ada6b
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay/index.ts
@@ -0,0 +1,2 @@
+export { PhotoCaptureHUDOverlay } from './PhotoCaptureHUDOverlay';
+export { type PhotoCaptureHUDOverlayProps } from './hooks';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview/PhotoCaptureHUDPreview.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview/PhotoCaptureHUDPreview.tsx
new file mode 100644
index 000000000..1155b7f44
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview/PhotoCaptureHUDPreview.tsx
@@ -0,0 +1,81 @@
+import { PixelDimensions, Sight } from '@monkvision/types';
+import { PhotoCaptureMode } from '../../hooks';
+import { PhotoCaptureHUDPreviewSight } from '../PhotoCaptureHUDPreviewSight';
+import { PhotoCaptureHUDPreviewAddDamage1stShot } from '../PhotoCaptureHUDPreviewAddDamage1stShot';
+import { PhotoCaptureHUDPreviewAddDamage2ndShot } from '../PhotoCaptureHUDPreviewAddDamage2ndShot';
+
+/**
+ * Props of the PhotoCaptureHUDPreview component.
+ */
+export interface PhotoCaptureHUDPreviewProps {
+ /**
+ * The currently selected sight in the PhotoCapture component : the sight that the user needs to capture.
+ */
+ selectedSight: Sight;
+ /**
+ * The list of sights provided to the PhotoCapture component.
+ */
+ sights: Sight[];
+ /**
+ * Array containing the list of sights that the user has already captured.
+ */
+ sightsTaken: Sight[];
+ /**
+ * The current mode of the component.
+ */
+ mode: PhotoCaptureMode;
+ /**
+ * Callback called when the user presses the Add Damage button.
+ */
+ onAddDamage: () => void;
+ /**
+ * Callback called when the user cancels the Add Damage mode.
+ */
+ onCancelAddDamage: () => void;
+ /**
+ * Callback called when the user manually select a new sight.
+ */
+ onSelectSight: (sight: Sight) => void;
+ /**
+ * The dimensions of the Camera video stream.
+ */
+ streamDimensions: PixelDimensions | null;
+ /**
+ * Boolean indicating if the global loading state of the PhotoCapture component is loading or not.
+ */
+ isLoading?: boolean;
+ /**
+ * The error that occurred in the PhotoCapture component. Set this value to `null` if there is no error.
+ */
+ error?: unknown | null;
+}
+
+/**
+ * Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process.
+ */
+export function PhotoCaptureHUDPreview(params: PhotoCaptureHUDPreviewProps) {
+ if (params.isLoading || !!params.error) {
+ return null;
+ }
+ if (params.mode === PhotoCaptureMode.SIGHT) {
+ return (
+
+ );
+ }
+ if (params.mode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT) {
+ return ;
+ }
+ return (
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview/index.ts
new file mode 100644
index 000000000..6144e49a5
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview/index.ts
@@ -0,0 +1 @@
+export { PhotoCaptureHUDPreview, type PhotoCaptureHUDPreviewProps } from './PhotoCaptureHUDPreview';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview/CrosshairPreview.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot/PhotoCaptureHUDPreviewAddDamage1stShot.styles.ts
similarity index 100%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview/CrosshairPreview.styles.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot/PhotoCaptureHUDPreviewAddDamage1stShot.styles.ts
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot/PhotoCaptureHUDPreviewAddDamage1stShot.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot/PhotoCaptureHUDPreviewAddDamage1stShot.tsx
new file mode 100644
index 000000000..6020ee437
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot/PhotoCaptureHUDPreviewAddDamage1stShot.tsx
@@ -0,0 +1,50 @@
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Button, DynamicSVG } from '@monkvision/common-ui-web';
+import { usePhotoCaptureHUDButtonBackground } from '../hooks';
+import { styles } from './PhotoCaptureHUDPreviewAddDamage1stShot.styles';
+import { PhotoCaptureHUDCounter } from '../PhotoCaptureHUDCounter';
+import { PhotoCaptureMode } from '../../hooks';
+import { PhotoCaptureHUDCancelButton } from '../PhotoCaptureHUDCancelButton';
+import { crosshairSvg } from '../../../assets';
+
+/**
+ * Props of the PhotoCaptureHUDPreviewAddDamage1stShot component.
+ */
+export interface PhotoCaptureHUDAddDamagePreview1stShotProps {
+ /**
+ * Callback called when the user cancels the Add Damage mode.
+ */
+ onCancel?: () => void;
+}
+
+/**
+ * Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process when the current
+ * mode is ADD_DAMAGE_1ST_SHOT.
+ */
+export function PhotoCaptureHUDPreviewAddDamage1stShot({
+ onCancel,
+}: PhotoCaptureHUDAddDamagePreview1stShotProps) {
+ const [showInfoPopup, setShowInfoPopup] = useState(true);
+ const { t } = useTranslation();
+ const backgroundColor = usePhotoCaptureHUDButtonBackground();
+
+ return (
+
+
+
+ {showInfoPopup && (
+
setShowInfoPopup(false)}
+ >
+ {t('photo.hud.addDamage.infoBtn')}
+
+ )}
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot/index.ts
new file mode 100644
index 000000000..bfa89cf5e
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot/index.ts
@@ -0,0 +1,4 @@
+export {
+ PhotoCaptureHUDPreviewAddDamage1stShot,
+ type PhotoCaptureHUDAddDamagePreview1stShotProps,
+} from './PhotoCaptureHUDPreviewAddDamage1stShot';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/CloseupPreview.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot/PhotoCaptureHUDPreviewAddDamage2ndShot.styles.ts
similarity index 100%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/CloseupPreview.styles.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot/PhotoCaptureHUDPreviewAddDamage2ndShot.styles.ts
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot/PhotoCaptureHUDPreviewAddDamage2ndShot.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot/PhotoCaptureHUDPreviewAddDamage2ndShot.tsx
new file mode 100644
index 000000000..6cc0ba493
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot/PhotoCaptureHUDPreviewAddDamage2ndShot.tsx
@@ -0,0 +1,48 @@
+import { PixelDimensions } from '@monkvision/types';
+import { useTranslation } from 'react-i18next';
+import { PhotoCaptureMode } from '../../hooks';
+import { styles } from './PhotoCaptureHUDPreviewAddDamage2ndShot.styles';
+import { PhotoCaptureHUDCounter } from '../PhotoCaptureHUDCounter';
+import { PhotoCaptureHUDCancelButton } from '../PhotoCaptureHUDCancelButton';
+
+/**
+ * Props of the PhotoCaptureHUDPreviewAddDamage2ndShot component.
+ */
+export interface PhotoCaptureHUDAddDamagePreview2ndShotProps {
+ /**
+ * Callback called when the user cancels the Add Damage mode.
+ */
+ onCancel?: () => void;
+ /**
+ * The dimensions of the Camera video stream.
+ */
+ streamDimensions?: PixelDimensions | null;
+}
+
+/**
+ * Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process when the current
+ * mode is ADD_DAMAGE_2ND_SHOT.
+ */
+export function PhotoCaptureHUDPreviewAddDamage2ndShot({
+ onCancel,
+ streamDimensions,
+}: PhotoCaptureHUDAddDamagePreview2ndShotProps) {
+ const { t } = useTranslation();
+
+ const aspectRatio = streamDimensions
+ ? `${streamDimensions?.width}/${streamDimensions?.height}`
+ : '16/9';
+
+ return (
+
+
+
+
{t('photo.hud.addDamage.infoCloseup')}
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot/index.ts
new file mode 100644
index 000000000..b311fb588
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot/index.ts
@@ -0,0 +1,4 @@
+export {
+ PhotoCaptureHUDPreviewAddDamage2ndShot,
+ type PhotoCaptureHUDAddDamagePreview2ndShotProps,
+} from './PhotoCaptureHUDPreviewAddDamage2ndShot';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton/AddDamageButton.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton/AddDamageButton.tsx
new file mode 100644
index 000000000..6ebe1ee87
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton/AddDamageButton.tsx
@@ -0,0 +1,32 @@
+import { Button } from '@monkvision/common-ui-web';
+import { useTranslation } from 'react-i18next';
+import { usePhotoCaptureHUDButtonBackground } from '../../hooks';
+
+/**
+ * Props of the AddDamageButton component.
+ */
+export interface AddDamageButtonProps {
+ /**
+ * Callback called when the user presses the button.
+ */
+ onAddDamage?: () => void;
+}
+
+/**
+ * Custom button displayed in the PhotoCapture Camera HUD that allows user to enter add damage mode.
+ */
+export function AddDamageButton({ onAddDamage }: AddDamageButtonProps) {
+ const { t } = useTranslation();
+ const backgroundColor = usePhotoCaptureHUDButtonBackground();
+
+ return (
+
+ {t('photo.hud.sight.addDamageBtn')}
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton/index.ts
new file mode 100644
index 000000000..581b293d3
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton/index.ts
@@ -0,0 +1 @@
+export { AddDamageButton, type AddDamageButtonProps } from './AddDamageButton';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/PhotoCaptureHUDSightPreview.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.styles.ts
similarity index 100%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/PhotoCaptureHUDSightPreview.styles.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.styles.ts
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.tsx
new file mode 100644
index 000000000..ab5e0910b
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.tsx
@@ -0,0 +1,76 @@
+import { PixelDimensions, Sight } from '@monkvision/types';
+import { SightOverlay } from '@monkvision/common-ui-web';
+import { SightsSlider } from './SightsSlider';
+import { styles } from './PhotoCaptureHUDPreviewSight.styles';
+import { AddDamageButton } from './AddDamageButton';
+import { usePhotoCaptureHUDSightPreviewStyle } from './hook';
+import { PhotoCaptureHUDCounter } from '../PhotoCaptureHUDCounter';
+import { PhotoCaptureMode } from '../../hooks';
+
+/**
+ * Props of the PhotoCaptureHUDPreviewSight component.
+ */
+export interface PhotoCaptureHUDSightPreviewProps {
+ /**
+ * The list of sights provided to the PhotoCapture component.
+ */
+ sights: Sight[];
+ /**
+ * The currently selected sight in the PhotoCapture component : the sight that the user needs to capture.
+ */
+ selectedSight: Sight;
+ /**
+ * Callback called when the user manually select a new sight.
+ */
+ onSelectedSight?: (sight: Sight) => void;
+ /**
+ * Callback called when the user clicks on the AddDamage button.
+ */
+ onAddDamage?: () => void;
+ /**
+ * Array containing the list of sights that the user has already captured.
+ */
+ sightsTaken: Sight[];
+ /**
+ * The dimensions of the Camera video stream.
+ */
+ streamDimensions?: PixelDimensions | null;
+}
+
+/**
+ * Component implementing an HUD displayed on top of the Camera preview during the PhotoCapture process when the current
+ * mode is SIGHT.
+ */
+export function PhotoCaptureHUDPreviewSight({
+ sights,
+ selectedSight,
+ onSelectedSight = () => {},
+ onAddDamage = () => {},
+ sightsTaken,
+ streamDimensions,
+}: PhotoCaptureHUDSightPreviewProps) {
+ const style = usePhotoCaptureHUDSightPreviewStyle();
+ const aspectRatio = `${streamDimensions?.width}/${streamDimensions?.height}`;
+
+ return (
+
+ {streamDimensions && (
+
+ )}
+
+
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightSliderButton.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightSliderButton.tsx
new file mode 100644
index 000000000..feba5d9f5
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightSliderButton.tsx
@@ -0,0 +1,44 @@
+import { Button } from '@monkvision/common-ui-web';
+import { styles } from './SightsSlider.styles';
+
+/**
+ * Props of the SightSliderButton component.
+ */
+export interface SightSliderButtonProps {
+ /**
+ * The label of the sight, already translated.
+ */
+ label?: string;
+ /**
+ * Boolean indicating if the sight of the button is currently selected or not.
+ */
+ isSelected?: boolean;
+ /**
+ * Boolean indicating if the sight of the button has already been taken or not.
+ */
+ isTaken?: boolean;
+ /**
+ * Callback called when the user clicks on the button.
+ */
+ onClick?: () => void;
+}
+
+/**
+ * Button representing a sight in the PhotoCapture SightsSlider component.
+ */
+export function SightSliderButton({ label, isSelected, isTaken, onClick }: SightSliderButtonProps) {
+ const primaryColor = isSelected || isTaken ? 'primary-base' : 'secondary-xdark';
+ const icon = isTaken ? 'check' : undefined;
+
+ return (
+
+ {label}
+
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider/SightsSlider.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightsSlider.styles.ts
similarity index 100%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider/SightsSlider.styles.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightsSlider.styles.ts
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider/SightsSlider.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightsSlider.tsx
similarity index 51%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider/SightsSlider.tsx
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightsSlider.tsx
index 0db7b55aa..c0cee3ca5 100644
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider/SightsSlider.tsx
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightsSlider.tsx
@@ -1,58 +1,32 @@
-import { Button, IconName } from '@monkvision/common-ui-web';
import { Sight } from '@monkvision/types';
+import { RefObject, useEffect, useRef } from 'react';
import { useSightLabel } from '@monkvision/common';
import { labels } from '@monkvision/sights';
-import { RefObject, useEffect, useRef } from 'react';
import { styles } from './SightsSlider.styles';
+import { SightSliderButton } from './SightSliderButton';
+/**
+ * Props of the SightsSlider component.
+ */
export interface SightsSliderProps {
+ /**
+ * The list of sights provided to the PhotoCapture component.
+ */
sights: Sight[];
+ /**
+ * The currently selected sight in the PhotoCapture component : the sight that the user needs to capture.
+ */
selectedSight: Sight;
+ /**
+ * Array containing the list of sights that the user has already captured.
+ */
sightsTaken: Sight[];
+ /**
+ * Callback called when the user manually select a new sight.
+ */
onSelectedSight?: (sight: Sight) => void;
}
-interface SliderButtonProps {
- sight: Sight;
- selectedSight: Sight;
- sightsTaken: Sight[];
- onSelectedSight: (sight: Sight) => void;
- label: (sight: Sight) => string;
-}
-
-function SliderButton({
- sight,
- selectedSight,
- sightsTaken,
- onSelectedSight,
- label,
-}: SliderButtonProps) {
- const isSelected = sight.id === selectedSight.id;
- const isTaken = sightsTaken.some((sightTaken) => sightTaken.id === sight.id);
-
- let primaryColor = 'secondary-xdark';
- let icon = undefined as IconName | undefined;
-
- if (isSelected) {
- primaryColor = 'primary-base';
- }
- if (isTaken) {
- primaryColor = 'primary-light';
- icon = 'check';
- }
- return (
- onSelectedSight(sight)}
- data-testid={`sight-btn-${sight.id}`}
- >
- {label(sight)}
-
- );
-}
-
const scrollToSelectedSight = (
ref: RefObject,
index: number,
@@ -66,14 +40,19 @@ const scrollToSelectedSight = (
}
};
+/**
+ * A slider element displayed in the PhotoCapture Camera HUD. It displays a button for each sight of the capture
+ * process, allowing user to have indications of the remaining sights and allowing them to change the currently selected
+ * sight.
+ */
export function SightsSlider({
sights,
selectedSight,
sightsTaken,
onSelectedSight = () => {},
}: SightsSliderProps) {
- const ref = useRef(null);
const { label } = useSightLabel({ labels });
+ const ref = useRef(null);
useEffect(() => {
scrollToSelectedSight(ref, sights.indexOf(selectedSight), true);
@@ -82,13 +61,12 @@ export function SightsSlider({
return (
{sights.map((sight) => (
- onSelectedSight(sight)}
/>
))}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/index.ts
new file mode 100644
index 000000000..ad6048912
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/index.ts
@@ -0,0 +1,2 @@
+export { SightSliderButton, type SightSliderButtonProps } from './SightSliderButton';
+export { SightsSlider, type SightsSliderProps } from './SightsSlider';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/hook.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/hook.ts
similarity index 89%
rename from packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/hook.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/hook.ts
index 2b8087a03..f9ddf20b0 100644
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/hook.ts
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/hook.ts
@@ -1,5 +1,5 @@
import { CSSProperties } from 'react';
-import { styles } from './PhotoCaptureHUDSightPreview.styles';
+import { styles } from './PhotoCaptureHUDPreviewSight.styles';
export interface PhotoCaptureHUDSightPreviewStyle {
container: CSSProperties;
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/index.ts
new file mode 100644
index 000000000..f566d3fd2
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/index.ts
@@ -0,0 +1,6 @@
+export * from './SightsSlider';
+export * from './AddDamageButton';
+export {
+ PhotoCaptureHUDPreviewSight,
+ type PhotoCaptureHUDSightPreviewProps,
+} from './PhotoCaptureHUDPreviewSight';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts
new file mode 100644
index 000000000..3153a7936
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts
@@ -0,0 +1,2 @@
+export * from './usePhotoCaptureHUDStyle';
+export * from './usePhotoCaptureHUDButtonBackground';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDButtonBackground.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDButtonBackground.ts
new file mode 100644
index 000000000..9c8062174
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDButtonBackground.ts
@@ -0,0 +1,11 @@
+import { useMonkTheme, changeAlpha } from '@monkvision/common';
+import { useMemo } from 'react';
+
+/**
+ * Custom hook used to generate the background color for the buttons in the PhotoCaptureHUD component.
+ */
+export function usePhotoCaptureHUDButtonBackground() {
+ const { palette } = useMonkTheme();
+
+ return useMemo(() => changeAlpha(palette.secondary.xdark, 0.64), [palette]);
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hook.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDStyle.ts
similarity index 63%
rename from packages/public/inspection-capture-web/src/PhotoCapture/hook.ts
rename to packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDStyle.ts
index eeb2ea7b9..0aba9fea0 100644
--- a/packages/public/inspection-capture-web/src/PhotoCapture/hook.ts
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/usePhotoCaptureHUDStyle.ts
@@ -1,20 +1,6 @@
import { CSSProperties } from 'react';
import { useResponsiveStyle } from '@monkvision/common';
-import { styles } from './PhotoCaptureHUD.styles';
-
-/**
- * Enumeration of the different HUD mode view used in inspection capture web package
- */
-export enum HUDMode {
- /**
- * Default mode
- */
- DEFAULT = 'default',
- /**
- * Add damage mode/preview
- */
- ADD_DAMAGE = 'add-damage',
-}
+import { styles } from '../PhotoCaptureHUD.styles';
export interface PhotoCaptureHUDStyle {
container: CSSProperties;
@@ -23,6 +9,7 @@ export interface PhotoCaptureHUDStyle {
export function usePhotoCaptureHUDStyle(): PhotoCaptureHUDStyle {
const { responsive } = useResponsiveStyle();
+
return {
container: {
...styles['container'],
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/index.ts
new file mode 100644
index 000000000..8a0a241e0
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/index.ts
@@ -0,0 +1,10 @@
+export { PhotoCaptureHUD, type PhotoCaptureHUDProps } from './PhotoCaptureHUD';
+export * from './hooks';
+export * from './PhotoCaptureHUDButtons';
+export * from './PhotoCaptureHUDCancelButton';
+export * from './PhotoCaptureHUDCounter';
+export * from './PhotoCaptureHUDOverlay';
+export * from './PhotoCaptureHUDPreview';
+export * from './PhotoCaptureHUDPreviewAddDamage1stShot';
+export * from './PhotoCaptureHUDPreviewAddDamage2ndShot';
+export * from './PhotoCaptureHUDPreviewSight';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton/CancelButton.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton/CancelButton.tsx
deleted file mode 100644
index 2ac629233..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton/CancelButton.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Button } from '@monkvision/common-ui-web';
-import { useTranslation } from 'react-i18next';
-import { usePhotoHUDButtonBackground } from '../../hooks';
-
-export interface CancelButtonProps {
- onCancel: () => void;
-}
-
-export function CancelButton({ onCancel }: CancelButtonProps) {
- const { t } = useTranslation();
- const { bgColor } = usePhotoHUDButtonBackground();
-
- return (
-
- {t('photo.hud.addDamage.cancelBtn')}
-
- );
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton/index.ts
deleted file mode 100644
index 886c5b010..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './CancelButton';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/CloseupPreview.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/CloseupPreview.tsx
deleted file mode 100644
index b5769af44..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/CloseupPreview.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { PixelDimensions, Sight } from '@monkvision/types';
-import { useTranslation } from 'react-i18next';
-import { useSightLabel } from '@monkvision/common';
-import { labels } from '@monkvision/sights';
-import { styles } from './CloseupPreview.styles';
-import { CancelButton } from '../CancelButton';
-import { DamageCounter } from '../DamageCounter';
-import { useCloseupPreviewStyle } from './hook';
-import { AddDamagePreviewMode } from '../../hooks';
-
-export interface CloseupPreviewProps {
- sight?: Sight | undefined;
- onCancel: () => void;
- streamDimensions?: PixelDimensions | null;
-}
-
-export function CloseupPreview({ sight, onCancel, streamDimensions }: CloseupPreviewProps) {
- const { t } = useTranslation();
- const { label } = useSightLabel({ labels });
- const style = useCloseupPreviewStyle();
-
- const sightLabel = sight && label(sight);
- const aspectRatio = streamDimensions
- ? `${streamDimensions?.width}/${streamDimensions?.height}`
- : '16/9';
-
- return (
-
-
-
-
-
-
-
- {sightLabel}
-
-
{t('photo.hud.addDamage.infoCloseup')}
-
- );
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/hook.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/hook.ts
deleted file mode 100644
index af146a1a3..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/hook.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { useResponsiveStyle } from '@monkvision/common';
-import { styles } from './CloseupPreview.styles';
-
-export function useCloseupPreviewStyle() {
- const { responsive } = useResponsiveStyle();
- return {
- label: {
- ...styles['label'],
- ...responsive(styles['labelPortrait']),
- },
- };
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/index.ts
deleted file mode 100644
index 805563fca..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './CloseupPreview';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview/CrosshairPreview.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview/CrosshairPreview.tsx
deleted file mode 100644
index 9588f2b3f..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview/CrosshairPreview.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Button, DynamicSVG } from '@monkvision/common-ui-web';
-import { useTranslation } from 'react-i18next';
-import { useState } from 'react';
-import { AddDamagePreviewMode, usePhotoHUDButtonBackground } from '../../hooks';
-import { styles } from './CrosshairPreview.styles';
-import { DamageCounter } from '../DamageCounter';
-import { CancelButton } from '../CancelButton';
-
-export interface CrosshairPreviewProps {
- onCancel: () => void;
-}
-
-export function CrosshairPreview({ onCancel }: CrosshairPreviewProps) {
- const [showInfoBtn, setShowInfoBtn] = useState(true);
-
- const { t } = useTranslation();
- const { bgColor } = usePhotoHUDButtonBackground();
-
- const svg =
- ' ';
-
- return (
-
-
-
-
-
-
- {showInfoBtn && (
-
setShowInfoBtn(false)}
- >
- {t('photo.hud.addDamage.infoBtn')}
-
- )}
-
- );
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview/index.ts
deleted file mode 100644
index 496faf5ca..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './CrosshairPreview';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter/DamageCounter.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter/DamageCounter.tsx
deleted file mode 100644
index 4350d48fc..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter/DamageCounter.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { useTranslation } from 'react-i18next';
-import { styles } from './DamageCounter.styles';
-import { AddDamagePreviewMode, usePhotoHUDButtonBackground } from '../../hooks';
-
-export interface DamageCounterProps {
- addDamagePreviewMode: AddDamagePreviewMode;
-}
-
-export function DamageCounter({ addDamagePreviewMode }: DamageCounterProps) {
- const { t } = useTranslation();
- const { bgColor } = usePhotoHUDButtonBackground();
-
- const previewMode = Object.values(AddDamagePreviewMode).indexOf(addDamagePreviewMode) + 1;
- const totalDamage = Object.values(AddDamagePreviewMode).length;
- const counterText =
- addDamagePreviewMode === AddDamagePreviewMode.DEFAULT
- ? t('photo.hud.addDamage.damagedPart')
- : t('photo.hud.addDamage.closeupPicture');
-
- return (
- {`${previewMode} / ${totalDamage} • ${counterText}`}
- );
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter/index.ts
deleted file mode 100644
index e8bf1b7bd..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './DamageCounter';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.styles.ts
deleted file mode 100644
index b50378a04..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.styles.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Styles } from '@monkvision/types';
-
-export const styles: Styles = {
- container: {
- position: 'absolute',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- flexDirection: 'column',
- top: '0',
- right: '0',
- left: '0',
- bottom: '0',
- },
- top: {
- position: 'absolute',
- display: 'flex',
- alignSelf: 'stretch',
- flexDirection: 'row',
- justifyContent: 'space-between',
- margin: '10px',
- zIndex: '9',
- top: '0',
- right: '0',
- left: '0',
- },
- infoBtn: {
- position: 'absolute',
- margin: '10px',
- bottom: '0',
- },
- test: {
- position: 'absolute',
- width: '100%',
- aspectRatio: '16/9',
- },
-};
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.tsx
deleted file mode 100644
index ac78b280b..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { PixelDimensions, Sight } from '@monkvision/types';
-import { useMemo } from 'react';
-import { styles } from './PhotoCaptureHUDAddDamagePreview.styles';
-import { CloseupPreview } from './CloseupPreview';
-import { CrosshairPreview } from './CrosshairPreview';
-import { AddDamagePreviewMode } from '../hooks';
-
-export interface PhotoCaptureHUDAddDamageMenuProps {
- sight?: Sight | undefined;
- onCancel: () => void;
- addDamagePreviewMode: AddDamagePreviewMode;
- streamDimensions?: PixelDimensions | null;
-}
-
-export function PhotoCaptureHUDAddDamagePreview({
- onCancel,
- sight,
- addDamagePreviewMode,
- streamDimensions,
-}: PhotoCaptureHUDAddDamageMenuProps) {
- const addDamagePreview = useMemo(() => {
- return addDamagePreviewMode === AddDamagePreviewMode.DEFAULT ? (
-
- ) : (
-
- );
- }, [addDamagePreviewMode, onCancel, sight, streamDimensions]);
-
- return {addDamagePreview}
;
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/index.ts
deleted file mode 100644
index 1b947cbc0..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './PhotoCaptureHUDAddDamagePreview';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton/AddDamageButton.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton/AddDamageButton.tsx
deleted file mode 100644
index b609cf133..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton/AddDamageButton.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Button } from '@monkvision/common-ui-web';
-import { useTranslation } from 'react-i18next';
-import { HUDMode } from '../../hook';
-import { usePhotoHUDButtonBackground } from '../../hooks';
-
-export interface AddDamageButtonProps {
- onAddDamage: (newMode: HUDMode) => void;
-}
-
-export function AddDamageButton({ onAddDamage }: AddDamageButtonProps) {
- const { t } = useTranslation();
- const { bgColor } = usePhotoHUDButtonBackground();
-
- return (
- onAddDamage(HUDMode.ADD_DAMAGE)}
- data-testid='monk-test-btn'
- style={{ backgroundColor: bgColor }}
- >
- {t('photo.hud.sight.addDamageBtn')}
-
- );
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton/index.ts
deleted file mode 100644
index 35d2e8a9c..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './AddDamageButton';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/PhotoCaptureHUDSightPreview.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/PhotoCaptureHUDSightPreview.tsx
deleted file mode 100644
index be8c36edd..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/PhotoCaptureHUDSightPreview.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { PixelDimensions, Sight } from '@monkvision/types';
-import { SightOverlay } from '@monkvision/common-ui-web';
-import { SightsSlider } from './SightsSlider';
-import { SightsCounter } from './SightsCounter';
-import { HUDMode } from '../hook';
-import { styles } from './PhotoCaptureHUDSightPreview.styles';
-import { AddDamageButton } from './AddDamageButton';
-import { usePhotoCaptureHUDSightPreviewStyle } from './hook';
-
-export interface PhotoCaptureHUDPreviewProps {
- sights: Sight[];
- selectedSight: Sight;
- onSelectedSight?: (sight: Sight) => void;
- onAddDamage?: (newMode: HUDMode) => void;
- sightsTaken: Sight[];
- streamDimensions?: PixelDimensions | null;
-}
-
-export function PhotoCaptureHUDSightPreview({
- sights,
- selectedSight,
- onSelectedSight = () => {},
- onAddDamage = () => {},
- sightsTaken,
- streamDimensions,
-}: PhotoCaptureHUDPreviewProps) {
- const style = usePhotoCaptureHUDSightPreviewStyle();
- const aspectRatio = `${streamDimensions?.width}/${streamDimensions?.height}`;
-
- return (
-
- {streamDimensions && (
-
- )}
-
-
-
- );
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/SightsCounter.styles.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/SightsCounter.styles.ts
deleted file mode 100644
index 93ecea605..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/SightsCounter.styles.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Styles } from '@monkvision/types';
-
-export const styles: Styles = {
- counter: {
- display: 'flex',
- color: 'white',
- alignItems: 'center',
- justifyContent: 'center',
- padding: '6px 12px',
- borderRadius: '8px',
- zIndex: '9',
- },
-};
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/SightsCounter.tsx b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/SightsCounter.tsx
deleted file mode 100644
index 945a693a1..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/SightsCounter.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { styles } from './SightsCounter.styles';
-import { usePhotoHUDButtonBackground } from '../../hooks';
-
-export interface SightsCounterProps {
- totalSights: number;
- sightsTaken: number;
-}
-export function SightsCounter({ totalSights, sightsTaken }: SightsCounterProps) {
- const { bgColor } = usePhotoHUDButtonBackground();
-
- return (
- {`${sightsTaken} / ${totalSights}`}
- );
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/index.ts
deleted file mode 100644
index 594a9e401..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './SightsCounter';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider/index.ts
deleted file mode 100644
index 7022f8aab..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './SightsSlider';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/index.ts
deleted file mode 100644
index 154e48b61..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUDSightPreview/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './PhotoCaptureHUDSightPreview';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/index.ts
index 20ec9b488..0fc6cf00d 100644
--- a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/index.ts
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/index.ts
@@ -1,2 +1,5 @@
-export * from './useSightState';
-export * from './usePhotoHUDButtonBackground';
+export * from './usePhotoCaptureSightState';
+export * from './useStartTasksOnComplete';
+export * from './useAddDamageMode';
+export * from './useUploadQueue';
+export * from './usePictureTaken';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts
new file mode 100644
index 000000000..07d083de8
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts
@@ -0,0 +1,66 @@
+import { useState } from 'react';
+
+/**
+ * Enum of the different picture taking modes that the PhotoCapture component can be in.
+ */
+export enum PhotoCaptureMode {
+ /**
+ * SIGHT mode : user is asked to take a picture of its vehicle following a given Sight.
+ */
+ SIGHT = 'sight',
+ /**
+ * ADD_DAMAGE_1ST_SHOT mode : user is asked to take a picture centered on a damage, far away from the vehicle.
+ */
+ ADD_DAMAGE_1ST_SHOT = 'add_damage_1st_shot',
+ /**
+ * ADD_DAMAGE_2ND_SHOT mode : user is asked to take a zoomed picture of a damage on the car.
+ */
+ ADD_DAMAGE_2ND_SHOT = 'add_damage_2nd_shot',
+}
+
+/**
+ * Handle used to modify the current PhotoCaptureMode of the PhotoCaptureComponent.
+ */
+export interface AddDamageHandle {
+ /**
+ * The current mode of the component.
+ */
+ mode: PhotoCaptureMode;
+ /**
+ * Callback to be called when the user clicks on the "Add Damage" button.
+ */
+ handleAddDamage: () => void;
+ /**
+ * Callback to be called everytime the user takes a picture to update the mode after it.
+ */
+ updatePhotoCaptureModeAfterPictureTaken: () => void;
+ /**
+ * Callback to be called when the user clicks on the "Cancel" button of the Add Damage mode.
+ */
+ handleCancelAddDamage: () => void;
+}
+
+/**
+ * Custom hook used to switch between sight picture taking and add damage picture taking.
+ */
+export function useAddDamageMode(): AddDamageHandle {
+ const [mode, setMode] = useState(PhotoCaptureMode.SIGHT);
+
+ const handleAddDamage = () => setMode(PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT);
+
+ const updatePhotoCaptureModeAfterPictureTaken = () =>
+ setMode((currentMode) =>
+ currentMode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT
+ ? PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT
+ : PhotoCaptureMode.SIGHT,
+ );
+
+ const handleCancelAddDamage = () => setMode(PhotoCaptureMode.SIGHT);
+
+ return {
+ mode,
+ handleAddDamage,
+ updatePhotoCaptureModeAfterPictureTaken,
+ handleCancelAddDamage,
+ };
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts
new file mode 100644
index 000000000..e6481e1ec
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts
@@ -0,0 +1,166 @@
+import { useState } from 'react';
+import { MonkAPIConfig, MonkApiResponse, useMonkApi } from '@monkvision/network';
+import { useMonitoring } from '@monkvision/monitoring';
+import { LoadingState, MonkGotOneInspectionAction, useAsyncEffect } from '@monkvision/common';
+import { Image, Sight } from '@monkvision/types';
+import { sights } from '@monkvision/sights';
+import { MonkPicture } from '@monkvision/camera-web';
+
+/**
+ * Object containing state management utilities for the PhotoCapture sights.
+ */
+export interface PhotoCaptureSightState {
+ /**
+ * The currently selected sight in the PhotoCapture component : the sight that the user needs to capture.
+ */
+ selectedSight: Sight;
+ /**
+ * Array containing the list of sights that the user has already captured.
+ */
+ sightsTaken: Sight[];
+ /**
+ * Callback called when the user manually select a new sight.
+ */
+ selectSight: (s: Sight) => void;
+ /**
+ * Callback called when the user has taken a picture of a sight.
+ */
+ takeSelectedSight: () => void;
+ /**
+ * Value storing the last picture taken by the user. If no picture has been taken yet, this value is null.
+ */
+ lastPictureTaken: MonkPicture | null;
+ /**
+ * Callback used to manually update the last picture taken by the user after they take a picture.
+ */
+ setLastPictureTaken: (picture: MonkPicture) => void;
+ /**
+ * Callback that can be used to retry fetching this state object from the API in case the previous fetch failed.
+ */
+ retryLoadingInspection: () => void;
+}
+
+/**
+ * Parameters of the usePhotoCaptureSightState hook.
+ */
+export interface PhotoCaptureSightsParams {
+ /**
+ * The inspection ID.
+ */
+ inspectionId: string;
+ /**
+ * The list of sights provided to the PhotoCapture component.
+ */
+ captureSights: Sight[];
+ /**
+ * The api config used to communicate with the API.
+ */
+ apiConfig: MonkAPIConfig;
+ /**
+ * Global loading state of the PhotoCapture component.
+ */
+ loading: LoadingState;
+ /**
+ * Callback called when the last sight has been taken by the user.
+ */
+ onLastSightTaken: () => void;
+}
+
+function getSightsTaken(
+ inspectionId: string,
+ response: MonkApiResponse,
+): Sight[] {
+ return (
+ response.action.payload?.images
+ ?.filter(
+ (image: Image) => image.inspectionId === inspectionId && image.additionalData?.['sight_id'],
+ )
+ .map((image: Image) => sights[image.additionalData?.['sight_id'] as string]) ?? []
+ );
+}
+
+function getLastPictureTaken(
+ inspectionId: string,
+ response: MonkApiResponse,
+): MonkPicture | null {
+ const images = response.action.payload?.images?.filter(
+ (image: Image) => image.inspectionId === inspectionId,
+ );
+ if (images && images.length > 0) {
+ return {
+ uri: images[images.length - 1].path,
+ mimetype: images[images.length - 1].mimetype,
+ width: images[images.length - 1].width,
+ height: images[images.length - 1].height,
+ };
+ }
+ return null;
+}
+
+/**
+ * Custom hook used to manage the state of the PhotoCapture sights. This state is automatically fetched from the API at
+ * the start of the PhotoCapture process in order to allow users to start the inspection where they left it before.
+ */
+export function usePhotoCaptureSightState({
+ inspectionId,
+ captureSights,
+ apiConfig,
+ loading,
+ onLastSightTaken,
+}: PhotoCaptureSightsParams): PhotoCaptureSightState {
+ if (captureSights.length === 0) {
+ throw new Error('Empty sight list given to the Monk PhotoCapture component.');
+ }
+ const [retryCount, setRetryCount] = useState(0);
+ const [lastPictureTaken, setLastPictureTaken] = useState(null);
+ const [selectedSight, setSelectedSight] = useState(captureSights[0]);
+ const [sightsTaken, setSightsTaken] = useState([]);
+ const { getInspection } = useMonkApi(apiConfig);
+ const { handleError } = useMonitoring();
+
+ useAsyncEffect(
+ () => {
+ loading.start();
+ return getInspection(inspectionId);
+ },
+ [inspectionId, retryCount],
+ {
+ onResolve: (response) => {
+ const alreadyTakenSights = getSightsTaken(inspectionId, response);
+ setSightsTaken(alreadyTakenSights);
+ setSelectedSight(captureSights.filter((s) => !alreadyTakenSights.includes(s))[0]);
+ setLastPictureTaken(getLastPictureTaken(inspectionId, response));
+ loading.onSuccess();
+ },
+ onReject: (err) => {
+ handleError(err);
+ loading.onError(err);
+ },
+ },
+ );
+
+ const retryLoadingInspection = () => {
+ setRetryCount((value) => value + 1);
+ };
+
+ const takeSelectedSight = () => {
+ const updatedSightsTaken = [...sightsTaken, selectedSight];
+ setSightsTaken(updatedSightsTaken);
+ const nextSight = captureSights.filter((s) => !updatedSightsTaken.includes(s))[0];
+ if (nextSight) {
+ setSelectedSight(nextSight);
+ } else {
+ onLastSightTaken();
+ }
+ };
+
+ return {
+ selectedSight,
+ sightsTaken,
+ selectSight: setSelectedSight,
+ takeSelectedSight,
+ lastPictureTaken,
+ setLastPictureTaken,
+ retryLoadingInspection,
+ };
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePhotoHUDButtonBackground.ts b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePhotoHUDButtonBackground.ts
deleted file mode 100644
index aa3026a74..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePhotoHUDButtonBackground.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { useMonkTheme, changeAlpha } from '@monkvision/common';
-import { useMemo } from 'react';
-
-export function usePhotoHUDButtonBackground() {
- const { palette } = useMonkTheme();
-
- const bgColor = useMemo(() => changeAlpha(palette.secondary.xdark, 0.64), [palette]);
-
- return { bgColor };
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts
new file mode 100644
index 000000000..25143e8c2
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts
@@ -0,0 +1,69 @@
+import { MonkPicture } from '@monkvision/camera-web';
+import { TaskName } from '@monkvision/types';
+import { Queue } from '@monkvision/common';
+import { PictureUpload } from './useUploadQueue';
+import { AddDamageHandle, PhotoCaptureMode } from './useAddDamageMode';
+import { PhotoCaptureSightState } from './usePhotoCaptureSightState';
+
+/**
+ * Parameters of the usePictureTaken hook.
+ */
+export interface UseTakePictureParams {
+ /**
+ * The PhotoCapture sight state, created using the usePhotoCaptureSightState hook.
+ */
+ sightState: PhotoCaptureSightState;
+ /**
+ * The PhotoCapture add damage handle, created using the useAddDamageMode hook.
+ */
+ addDamageHandle: AddDamageHandle;
+ /**
+ * Photo capture upload queue, created using the useUploadQueue hook.
+ */
+ uploadQueue: Queue;
+ /**
+ * Record associating each sight to a list of tasks to execute for it. If not provided, the default tasks of the sight
+ * will be used.
+ */
+ tasksBySight?: Record;
+}
+
+/**
+ * Result returned by the usePictureTaken hook.
+ */
+export interface UseTakePictureResult {
+ /**
+ * Callback called when the user has taken a picture.
+ */
+ handlePictureTaken: (picture: MonkPicture) => void;
+}
+
+/**
+ * Custom hook used to generate the callback called when the user has taken a picture to handle picture upload etc.
+ */
+export function usePictureTaken({
+ sightState,
+ addDamageHandle,
+ uploadQueue,
+ tasksBySight,
+}: UseTakePictureParams): UseTakePictureResult {
+ const handlePictureTaken = (picture: MonkPicture) => {
+ sightState.setLastPictureTaken(picture);
+ const upload: PictureUpload =
+ addDamageHandle.mode === PhotoCaptureMode.SIGHT
+ ? {
+ mode: addDamageHandle.mode,
+ picture,
+ sightId: sightState.selectedSight.id,
+ tasks: tasksBySight?.[sightState.selectedSight.id] ?? sightState.selectedSight.tasks,
+ }
+ : { mode: addDamageHandle.mode, picture };
+ uploadQueue.push(upload);
+ if (addDamageHandle.mode === PhotoCaptureMode.SIGHT) {
+ sightState.takeSelectedSight();
+ }
+ addDamageHandle.updatePhotoCaptureModeAfterPictureTaken();
+ };
+
+ return { handlePictureTaken };
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useSightState.ts b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useSightState.ts
deleted file mode 100644
index bba2401d2..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useSightState.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useState } from 'react';
-import { Sight } from '@monkvision/types';
-import { HUDMode } from '../hook';
-
-/**
- * Enumeration of the different AddDamagePreview when HUD is in ADD_DAMAGE mode
- */
-export enum AddDamagePreviewMode {
- /**
- * Default preview which is the Crosshair preview
- */
- DEFAULT = 'default',
- /**
- * Closeup preview
- */
- CLOSEUP_PREVIEW = 'closeup-preview',
-}
-
-/**
- * Custom hook used to initialize and manipulate sight state.
- */
-export function useSightState(sights: Sight[]) {
- const [selectedSight, setSelectedSight] = useState(sights[0]);
- const [sightsTaken, setSightsTaken] = useState([]);
- const [mode, setMode] = useState(HUDMode.DEFAULT);
- const [addDamagePreviewMode, setAddDamagePreviewMode] = useState(
- AddDamagePreviewMode.DEFAULT,
- );
-
- const handleSightTaken = () => {
- if (mode === HUDMode.DEFAULT) {
- if (sightsTaken.includes(selectedSight)) {
- return;
- }
- const updatedSightsTaken = [...sightsTaken, selectedSight];
- setSightsTaken(updatedSightsTaken);
- const nextSight = sights.filter((sight) => !updatedSightsTaken.includes(sight))[0];
- if (nextSight) {
- setSelectedSight(nextSight);
- }
- }
- if (mode === HUDMode.ADD_DAMAGE) {
- if (addDamagePreviewMode === AddDamagePreviewMode.DEFAULT) {
- setAddDamagePreviewMode(AddDamagePreviewMode.CLOSEUP_PREVIEW);
- return;
- }
- if (addDamagePreviewMode === AddDamagePreviewMode.CLOSEUP_PREVIEW) {
- setMode(HUDMode.DEFAULT);
- setAddDamagePreviewMode(AddDamagePreviewMode.DEFAULT);
- }
- }
- };
-
- return {
- selectedSight,
- setSelectedSight,
- sightsTaken,
- handleSightTaken,
- mode,
- setMode,
- addDamagePreviewMode,
- setAddDamagePreviewMode,
- };
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useStartTasksOnComplete.ts b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useStartTasksOnComplete.ts
new file mode 100644
index 000000000..cd8629d2d
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useStartTasksOnComplete.ts
@@ -0,0 +1,103 @@
+import { Sight, TaskName } from '@monkvision/types';
+import { flatMap, LoadingState, uniq } from '@monkvision/common';
+import { MonkAPIConfig, useMonkApi } from '@monkvision/network';
+import { useMonitoring } from '@monkvision/monitoring';
+import { useMemo } from 'react';
+
+/**
+ * Parameters of the useStartTasksOnComplete hook.
+ */
+export interface UseStartTasksOnCompleteParams {
+ /**
+ * The inspection ID.
+ */
+ inspectionId: string;
+ /**
+ * The list of sights passed to the PhotoCapture component.
+ */
+ sights: Sight[];
+ /**
+ * The api config used to communicate with the API.
+ */
+ apiConfig: MonkAPIConfig;
+ /**
+ * Global loading state of the PhotoCapture component.
+ */
+ loading: LoadingState;
+ /**
+ * Record associating each sight with a list of tasks to execute for it. If not provided, the default tasks of the
+ * sight will be used.
+ */
+ tasksBySight?: Record;
+ /**
+ * Value indicating if tasks should be started at the end of the inspection :
+ * - If not provided or if value is set to `false`, no tasks will be started.
+ * - If set to `true`, the tasks described by the `tasksBySight` param (or, if not provided, the default tasks of each
+ * sight) will be started.
+ * - If an array of tasks is provided, the tasks started will be the ones contained in the array.
+ */
+ startTasksOnComplete?: boolean | TaskName[];
+}
+
+/**
+ * Result of the useStartTasksOnComplete hook.
+ */
+export interface StartTasksOnCompleteHandle {
+ /**
+ * Callback to be called when the PhotoCapture inspection is complete in order to start (or not) to inspection tasks.
+ */
+ startTasks: () => Promise;
+}
+
+function getTasksToStart({
+ sights,
+ tasksBySight,
+ startTasksOnComplete,
+}: Pick<
+ UseStartTasksOnCompleteParams,
+ 'sights' | 'tasksBySight' | 'startTasksOnComplete'
+>): TaskName[] {
+ if (Array.isArray(startTasksOnComplete)) {
+ return startTasksOnComplete;
+ }
+ if (tasksBySight) {
+ return uniq(flatMap(sights, (sight) => tasksBySight[sight.id]));
+ }
+ return uniq(flatMap(sights, (sight) => sight.tasks));
+}
+
+/**
+ * Custom hook used to generate a callback called at the end of the PhotoCapture inspection in order to automatically
+ * start (or not) the inspection tasks.
+ */
+export function useStartTasksOnComplete({
+ inspectionId,
+ apiConfig,
+ sights,
+ tasksBySight,
+ startTasksOnComplete,
+ loading,
+}: UseStartTasksOnCompleteParams): StartTasksOnCompleteHandle {
+ const { startInspectionTasks } = useMonkApi(apiConfig);
+ const { handleError } = useMonitoring();
+
+ return useMemo(() => {
+ if (!startTasksOnComplete) {
+ return { startTasks: () => Promise.resolve() };
+ }
+ const tasks = getTasksToStart({ sights, tasksBySight, startTasksOnComplete });
+
+ return {
+ startTasks: async () => {
+ loading.start();
+ try {
+ await startInspectionTasks(inspectionId, tasks);
+ loading.onSuccess();
+ } catch (err) {
+ handleError(err);
+ loading.onError(err);
+ }
+ },
+ };
+ }, [startTasksOnComplete, loading, sights, tasksBySight, inspectionId, handleError]);
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useUploadQueue.ts b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useUploadQueue.ts
new file mode 100644
index 000000000..dde2a02a0
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/hooks/useUploadQueue.ts
@@ -0,0 +1,148 @@
+import { LoadingState, Queue, useQueue } from '@monkvision/common';
+import { MonkPicture } from '@monkvision/camera-web';
+import { AddImageOptions, ComplianceOptions, MonkAPIConfig, useMonkApi } from '@monkvision/network';
+import { ImageType, TaskName } from '@monkvision/types';
+import { useRef } from 'react';
+import { useMonitoring } from '@monkvision/monitoring';
+import { PhotoCaptureMode } from './useAddDamageMode';
+
+/**
+ * Parameters of the useUploadQueue hook.
+ */
+export interface UploadQueueParams {
+ /**
+ * The inspection ID.
+ */
+ inspectionId: string;
+ /**
+ * The api config used to communicate with the API.
+ */
+ apiConfig: MonkAPIConfig;
+ /**
+ * Global loading state of the PhotoCapture component.
+ */
+ loading: LoadingState;
+ /**
+ * Compliance options used to enable or not certain compliance checks.
+ */
+ compliances?: ComplianceOptions;
+}
+
+/**
+ * Upload options for a normal sight picture.
+ */
+export interface SightPictureUpload {
+ /**
+ * Upload mode : `PhotoCaptureMode.SIGHT`.
+ */
+ mode: PhotoCaptureMode.SIGHT;
+ /**
+ * The picture to upload.
+ */
+ picture: MonkPicture;
+ /**
+ * The ID of the sight of the picture uploaded.
+ */
+ sightId: string;
+ /**
+ * The tasks to run for the given sight.
+ */
+ tasks: TaskName[];
+}
+
+/**
+ * Upload options for the first picture of a 2-shot add damage process.
+ */
+export interface AddDamage1stShotPictureUpload {
+ /**
+ * Upload mode : `PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT`.
+ */
+ mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT;
+ /**
+ * The picture to upload.
+ */
+ picture: MonkPicture;
+}
+
+/**
+ * Upload options for the second picture of a 2-shot add damage process.
+ */
+export interface AddDamage2ndShotPictureUpload {
+ /**
+ * Upload mode : `PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT`.
+ */
+ mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT;
+ /**
+ * The picture to upload.
+ */
+ picture: MonkPicture;
+}
+
+/**
+ * Union type describing every possible upload configurations for a picture taken.
+ */
+export type PictureUpload =
+ | SightPictureUpload
+ | AddDamage1stShotPictureUpload
+ | AddDamage2ndShotPictureUpload;
+
+function createAddImageOptions(
+ upload: PictureUpload,
+ inspectionId: string,
+ siblingId: number,
+ compliances?: ComplianceOptions,
+): AddImageOptions {
+ if (upload.mode === PhotoCaptureMode.SIGHT) {
+ return {
+ type: ImageType.BEAUTY_SHOT,
+ picture: upload.picture,
+ sightId: upload.sightId,
+ tasks: upload.tasks,
+ compliances,
+ inspectionId,
+ };
+ }
+ return {
+ type: ImageType.CLOSE_UP,
+ picture: upload.picture,
+ siblingKey: `closeup-sibling-key-${siblingId}`,
+ firstShot: upload.mode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT,
+ compliances,
+ inspectionId,
+ };
+}
+
+/**
+ * Custom hook used to generate the UploadQueue (using the `useQueue` hook) for the PhotoCapture component.
+ */
+export function useUploadQueue({
+ inspectionId,
+ apiConfig,
+ loading,
+ compliances,
+}: UploadQueueParams): Queue {
+ const { handleError } = useMonitoring();
+ const siblingIdRef = useRef(0);
+ const { addImage } = useMonkApi(apiConfig);
+
+ return useQueue(
+ async (upload: PictureUpload) => {
+ if (upload.mode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT) {
+ siblingIdRef.current += 1;
+ }
+ try {
+ await addImage(
+ createAddImageOptions(upload, inspectionId, siblingIdRef.current, compliances),
+ );
+ } catch (err) {
+ handleError(err);
+ loading.onError(err);
+ throw err;
+ }
+ },
+ {
+ maxProcessingItems: 5,
+ storeFailedItems: true,
+ },
+ );
+}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/i18n.ts b/packages/public/inspection-capture-web/src/PhotoCapture/i18n.ts
deleted file mode 100644
index 5e5895ac6..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/i18n.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { i18nCreateSDKInstance } from '@monkvision/common';
-import en from './translations/en.json';
-import fr from './translations/fr.json';
-import de from './translations/de.json';
-
-export const i18nPhotoCaptureHUD = i18nCreateSDKInstance({
- resources: {
- en: { translation: en },
- fr: { translation: fr },
- de: { translation: de },
- },
-});
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/index.ts b/packages/public/inspection-capture-web/src/PhotoCapture/index.ts
index f527cdcd1..ea38e6460 100644
--- a/packages/public/inspection-capture-web/src/PhotoCapture/index.ts
+++ b/packages/public/inspection-capture-web/src/PhotoCapture/index.ts
@@ -1,2 +1,3 @@
export * from './PhotoCaptureHUD';
-export * from './PhotoCapture';
+export { type PhotoCaptureProps } from './PhotoCapture';
+export { PhotoCaptureHOC as PhotoCapture } from './PhotoCaptureHOC';
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/translations/de.json b/packages/public/inspection-capture-web/src/PhotoCapture/translations/de.json
deleted file mode 100644
index a928f1ae0..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/translations/de.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "photo": {
- "hud": {
- "sight": {
- "addDamageBtn": "Schaden"
- },
- "addDamage": {
- "cancelBtn": "Stornieren",
- "damagedPart": "Beschädigtes Teil",
- "infoBtn": "Richte das Ziel auf den beschädigten Teil aus und tippe dann auf den Auslöserknopf",
- "closeupPicture": "Nahaufnahme-Vorschau",
- "infoCloseup": "Ein Nahaufnahmebild von dem Schaden machen"
- }
- }
- }
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/translations/en.json b/packages/public/inspection-capture-web/src/PhotoCapture/translations/en.json
deleted file mode 100644
index 677ae2f4a..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/translations/en.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "photo": {
- "hud": {
- "sight": {
- "addDamageBtn": "Damage"
- },
- "addDamage": {
- "cancelBtn": "Cancel",
- "damagedPart": "Damaged part",
- "infoBtn": "Aim the target at the damaged part then tap the shutter button",
- "closeupPicture": "Closeup Picture",
- "infoCloseup": "Take a closeup picture of the damage"
- }
- }
- }
-}
diff --git a/packages/public/inspection-capture-web/src/PhotoCapture/translations/fr.json b/packages/public/inspection-capture-web/src/PhotoCapture/translations/fr.json
deleted file mode 100644
index 12249a347..000000000
--- a/packages/public/inspection-capture-web/src/PhotoCapture/translations/fr.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "photo": {
- "hud": {
- "sight": {
- "addDamageBtn": "Dégât"
- },
- "addDamage": {
- "cancelBtn": "Annuler",
- "damagedPart": "Pièce endommagée",
- "infoBtn": "Placer le viseur sur la pièce endommagée puis enclencher le bouton capture de la photo",
- "closeupPicture": "Dégât en gros plan",
- "infoCloseup": "Prendre une photo en gros plan du dégât"
- }
- }
- }
-}
diff --git a/packages/public/inspection-capture-web/src/assets/crosshair.asset.ts b/packages/public/inspection-capture-web/src/assets/crosshair.asset.ts
new file mode 100644
index 000000000..c5ce3db48
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/assets/crosshair.asset.ts
@@ -0,0 +1,2 @@
+export const crosshairSvg =
+ ' ';
diff --git a/packages/public/inspection-capture-web/src/assets/index.ts b/packages/public/inspection-capture-web/src/assets/index.ts
new file mode 100644
index 000000000..759efae09
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/assets/index.ts
@@ -0,0 +1 @@
+export * from './crosshair.asset';
diff --git a/packages/public/inspection-capture-web/src/i18n.ts b/packages/public/inspection-capture-web/src/i18n.ts
new file mode 100644
index 000000000..bbc15fa7e
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/i18n.ts
@@ -0,0 +1,21 @@
+import { i18nCreateSDKInstance, i18nLinkSDKInstances } from '@monkvision/common';
+import { i18nCamera } from '@monkvision/camera-web';
+import en from './translations/en.json';
+import fr from './translations/fr.json';
+import de from './translations/de.json';
+
+/**
+ * i18n instance of the Inspection CApture Web package. You can use this instance to automatically sync your application
+ * current language with the one used by the components of the package.
+ */
+const i18nInspectionCaptureWeb = i18nCreateSDKInstance({
+ resources: {
+ en: { translation: en },
+ fr: { translation: fr },
+ de: { translation: de },
+ },
+});
+
+i18nLinkSDKInstances(i18nInspectionCaptureWeb, [i18nCamera]);
+
+export { i18nInspectionCaptureWeb };
diff --git a/packages/public/inspection-capture-web/src/index.ts b/packages/public/inspection-capture-web/src/index.ts
new file mode 100644
index 000000000..462180cd3
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/index.ts
@@ -0,0 +1,2 @@
+export * from './PhotoCapture';
+export * from './i18n';
diff --git a/packages/public/inspection-capture-web/src/translations/de.json b/packages/public/inspection-capture-web/src/translations/de.json
new file mode 100644
index 000000000..30cb007c1
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/translations/de.json
@@ -0,0 +1,28 @@
+{
+ "photo": {
+ "hud": {
+ "sight": {
+ "addDamageBtn": "Schaden"
+ },
+ "addDamage": {
+ "cancelBtn": "Stornieren",
+ "damagedPartCounter": "1 / 2 • Beschädigtes Teil",
+ "closeupPictureCounter": "2 / 2 • Nahaufnahme-Vorschau",
+ "infoBtn": "Richte das Ziel auf den beschädigten Teil aus und tippe dann auf den Auslöserknopf",
+ "infoCloseup": "Ein Nahaufnahmebild von dem Schaden machen"
+ },
+ "error": {
+ "retry": "Erneut versuchen",
+ "invalidToken": "Das verwendete Authentifizierungstoken ist ungĂĽltig.",
+ "expiredToken": "Das verwendete Authentifizierungstoken ist abgelaufen.",
+ "insufficientAuth": "Sie haben nicht die erforderlichen Berechtigungen, um diese Aktion durchzufĂĽhren.",
+ "inspectionLoading": "Beim Laden der Inspektion ist ein Fehler aufgetreten. Bitte versuchen Sie es in KĂĽrze erneut oder kontaktieren Sie den Support mit der folgenden Inspektions-ID:"
+ },
+ "closeConfirm": {
+ "message": "Sind Sie sicher, dass Sie das Erfassungstool schlieĂźen wollen?",
+ "cancel": "Abbrechen",
+ "confirm": "Ja"
+ }
+ }
+ }
+}
diff --git a/packages/public/inspection-capture-web/src/translations/en.json b/packages/public/inspection-capture-web/src/translations/en.json
new file mode 100644
index 000000000..a0c38ebd1
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/translations/en.json
@@ -0,0 +1,28 @@
+{
+ "photo": {
+ "hud": {
+ "sight": {
+ "addDamageBtn": "Damage"
+ },
+ "addDamage": {
+ "cancelBtn": "Cancel",
+ "damagedPartCounter": "1 / 2 • Damaged part",
+ "closeupPictureCounter": "2 / 2 • Closeup Picture",
+ "infoBtn": "Aim the target at the damaged part then tap the shutter button",
+ "infoCloseup": "Take a closeup picture of the damage"
+ },
+ "error": {
+ "retry": "Retry",
+ "invalidToken": "The authentication token used is invalid.",
+ "expiredToken": "The authentication token used is expired.",
+ "insufficientAuth": "You do not have the required autorizations to perform this action.",
+ "inspectionLoading": "An error occurred during the loading of the inspection. Please try again in a few minutes or contact the support with the following inspection id :"
+ },
+ "closeConfirm": {
+ "message": "Are you sure you want to close the capture tool?",
+ "cancel": "Cancel",
+ "confirm": "Yes"
+ }
+ }
+ }
+}
diff --git a/packages/public/inspection-capture-web/src/translations/fr.json b/packages/public/inspection-capture-web/src/translations/fr.json
new file mode 100644
index 000000000..073f8390c
--- /dev/null
+++ b/packages/public/inspection-capture-web/src/translations/fr.json
@@ -0,0 +1,28 @@
+{
+ "photo": {
+ "hud": {
+ "sight": {
+ "addDamageBtn": "Dégât"
+ },
+ "addDamage": {
+ "cancelBtn": "Annuler",
+ "damagedPartCounter": "1 / 2 • Pièce endommagée",
+ "closeupPictureCounter": "2 / 2 • Dégât en gros plan",
+ "infoBtn": "Placer le viseur sur la pièce endommagée puis enclencher le bouton capture de la photo",
+ "infoCloseup": "Prendre une photo en gros plan du dégât"
+ },
+ "error": {
+ "retry": "Réessayer",
+ "invalidToken": "Le token d'authentification utilisé est invalide.",
+ "expiredToken": "Le token d'authentification utilisé est expiré.",
+ "insufficientAuth": "Vous n'avez pas les autorisations nécessaires pour effectuer cette action.",
+ "inspectionLoading": "Une erreur est survenue lors du chargement de l'inspection. Veuillez réessayer dans quelques instants ou contacter le support avec l'identifiant d'inspection suivant :"
+ },
+ "closeConfirm": {
+ "message": "Êtes-vous sûr(e) de vouloir fermer l'outil de capture ?",
+ "cancel": "Annuler",
+ "confirm": "Oui"
+ }
+ }
+ }
+}
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx
index dbbeb056b..6539c34c8 100644
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx
@@ -1,60 +1,269 @@
-jest.mock('@monkvision/common-ui-web');
-jest.mock('@monkvision/camera-web');
-jest.mock('../../src/PhotoCapture/i18n', () => ({
- i18nPhotoCaptureHUD: {},
+import { sights } from '@monkvision/sights';
+
+const { PhotoCaptureMode } = jest.requireActual('../../src/PhotoCapture/hooks');
+
+jest.mock('../../src/PhotoCapture/hooks', () => ({
+ useAddDamageMode: jest.fn(() => ({
+ mode: PhotoCaptureMode.SIGHT,
+ handleAddDamage: jest.fn(),
+ updatePhotoCaptureModeAfterPictureTaken: jest.fn(),
+ handleCancelAddDamage: jest.fn(),
+ })),
+ usePhotoCaptureSightState: jest.fn(() => ({
+ selectedSight: sights['test-sight-2'],
+ sightsTaken: [sights['test-sight-1']],
+ selectSight: jest.fn(),
+ takeSelectedSight: jest.fn(),
+ lastPictureTaken: {
+ uri: 'test-uri-test',
+ mimetype: 'test-mimetype-test',
+ width: 1234,
+ height: 4567,
+ },
+ setLastPictureTaken: jest.fn(),
+ retryLoadingInspection: jest.fn(),
+ })),
+ usePictureTaken: jest.fn(() => ({
+ handlePictureTaken: jest.fn(),
+ })),
+ useUploadQueue: jest.fn(() => ({
+ length: 3,
+ })),
+ useStartTasksOnComplete: jest.fn(() => ({
+ startTasks: jest.fn(),
+ })),
}));
-import { render } from '@testing-library/react';
-import { expectPropsOnChildMock } from '@monkvision/test-utils';
-import { Sight } from '@monkvision/types';
-import { i18nWrap } from '@monkvision/common';
+import { render, waitFor } from '@testing-library/react';
import { Camera, CameraResolution, CompressionFormat } from '@monkvision/camera-web';
-import { i18nPhotoCaptureHUD } from '../../src/PhotoCapture/i18n';
-import { PhotoCapture } from '../../src/PhotoCapture';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { PhotoCapture, PhotoCaptureHUD, PhotoCaptureProps } from '../../src';
+import {
+ useAddDamageMode,
+ usePhotoCaptureSightState,
+ usePictureTaken,
+ useStartTasksOnComplete,
+ useUploadQueue,
+} from '../../src/PhotoCapture/hooks';
+import { useLoadingState } from '@monkvision/common';
+import { TaskName } from '@monkvision/types';
+import { useMonitoring } from '@monkvision/monitoring';
-const sights = [
- { id: 'id', label: { en: 'en', fr: 'fr', de: 'de' } },
- { id: 'id2', label: { en: 'en2', fr: 'fr2', de: 'de2' } },
-] as unknown as Sight[];
+function createProps(): PhotoCaptureProps {
+ return {
+ sights: [sights['test-sight-1'], sights['test-sight-2'], sights['test-sight-3']],
+ inspectionId: 'test-inspection-test',
+ apiConfig: { apiDomain: 'test-api-domain-test', authToken: 'test-auth-token-test' },
+ compliances: { iqa: true },
+ onClose: jest.fn(),
+ onComplete: jest.fn(),
+ resolution: CameraResolution.NHD_360P,
+ format: CompressionFormat.JPEG,
+ quality: 0.4,
+ };
+}
describe('PhotoCapture component', () => {
afterEach(() => {
jest.clearAllMocks();
});
- it('should wrap the component with the i18nWrap method', () => {
- const { unmount } = render( );
+ it('should pass the proper params to the useStartTasksOnComplete hook', () => {
+ const props = {
+ ...createProps(),
+ startTasksOnComplete: true,
+ tasksBySight: { test: [TaskName.DAMAGE_DETECTION] },
+ };
+ const { unmount } = render( );
- expect(i18nWrap).toHaveBeenCalledWith(expect.any(Function), i18nPhotoCaptureHUD);
+ expect(useLoadingState).toHaveBeenCalled();
+ const loading = (useLoadingState as jest.Mock).mock.results[0].value;
+ expect(useStartTasksOnComplete).toHaveBeenCalledWith({
+ inspectionId: props.inspectionId,
+ apiConfig: props.apiConfig,
+ sights: props.sights,
+ tasksBySight: props.tasksBySight,
+ startTasksOnComplete: props.startTasksOnComplete,
+ loading,
+ });
unmount();
});
- it('should render a Camera component', () => {
- const { unmount } = render( );
+ it('should pass the proper params to the usePhotoCaptureSightState hooks', () => {
+ const props = { ...createProps(), tasksBySight: { test: [TaskName.DAMAGE_DETECTION] } };
+ const { unmount } = render( );
- expect(Camera).toHaveBeenCalled();
+ expect(useLoadingState).toHaveBeenCalled();
+ const loading = (useLoadingState as jest.Mock).mock.results[0].value;
+ expect(usePhotoCaptureSightState).toHaveBeenCalledWith({
+ inspectionId: props.inspectionId,
+ captureSights: props.sights,
+ apiConfig: props.apiConfig,
+ loading,
+ onLastSightTaken: expect.any(Function),
+ });
unmount();
});
- it('should pass states, hud and handlePictureTaken to Camera component', () => {
- const CameraMock = Camera as jest.Mock;
- const state = {
- resolution: CameraResolution.UHD_4K,
- compressionFormat: CompressionFormat.JPEG,
- quality: '0.8',
- };
- const { unmount } = render( );
+ it('should call start tasks on the last sight and handle the promise correctly', async () => {
+ const startTasksMock = jest.fn(() => Promise.resolve());
+ (useStartTasksOnComplete as jest.Mock).mockImplementation(() => ({
+ startTasks: startTasksMock,
+ }));
+ const props = createProps();
+ const { unmount } = render( );
+
+ expect(useMonitoring).toHaveBeenCalled();
+ const handleErrorMock = (useMonitoring as jest.Mock).mock.results[0].value.handleError;
+ expect(useLoadingState).toHaveBeenCalled();
+ const loadingMock = (useLoadingState as jest.Mock).mock.results[0].value;
+ expect(usePhotoCaptureSightState).toHaveBeenCalled();
+ const { onLastSightTaken } = (usePhotoCaptureSightState as jest.Mock).mock.calls[0][0];
+
+ expect(startTasksMock).not.toHaveBeenCalled();
+ expect(props.onComplete).not.toHaveBeenCalled();
+ onLastSightTaken();
+
+ await waitFor(() => {
+ expect(startTasksMock).toHaveBeenCalled();
+ expect(props.onComplete).toHaveBeenCalled();
+ expect(loadingMock.onError).not.toHaveBeenCalled();
+ expect(handleErrorMock).not.toHaveBeenCalled();
+ });
+
+ startTasksMock.mockClear();
+ (props.onComplete as jest.Mock).mockClear();
+ const err = 'hello';
+ startTasksMock.mockImplementation(() => Promise.reject(err));
+ onLastSightTaken();
+
+ await waitFor(() => {
+ expect(startTasksMock).toHaveBeenCalled();
+ expect(props.onComplete).not.toHaveBeenCalled();
+ expect(loadingMock.onError).toHaveBeenCalledWith(err);
+ expect(handleErrorMock).toHaveBeenCalledWith(err);
+ });
+
+ unmount();
+ (useStartTasksOnComplete as jest.Mock).mockClear();
+ });
+
+ it('should pass the proper params to the usePhotoCaptureSightState hook', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expect(useLoadingState).toHaveBeenCalled();
+ const loading = (useLoadingState as jest.Mock).mock.results[0].value;
+ expect(usePhotoCaptureSightState).toHaveBeenCalled();
+ const { onLastSightTaken } = (usePhotoCaptureSightState as jest.Mock).mock.calls[0][0];
+ expect(usePhotoCaptureSightState).toHaveBeenCalledWith({
+ inspectionId: props.inspectionId,
+ captureSights: props.sights,
+ apiConfig: props.apiConfig,
+ loading,
+ onLastSightTaken,
+ });
+
+ unmount();
+ });
+
+ it('should pass the proper params to the useUploadQueue hook', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expect(useLoadingState).toHaveBeenCalled();
+ const loading = (useLoadingState as jest.Mock).mock.results[0].value;
+ expect(useUploadQueue).toHaveBeenCalledWith({
+ inspectionId: props.inspectionId,
+ apiConfig: props.apiConfig,
+ compliances: props.compliances,
+ loading,
+ });
+
+ unmount();
+ });
+
+ it('should pass the proper params to the usePictureTaken hook', () => {
+ const props = { ...createProps(), tasksBySight: { test: [TaskName.PRICING] } };
+ const { unmount } = render( );
+
+ expect(useAddDamageMode).toHaveBeenCalled();
+ const addDamageHandle = (useAddDamageMode as jest.Mock).mock.results[0].value;
+ expect(usePhotoCaptureSightState).toHaveBeenCalled();
+ const sightState = (usePhotoCaptureSightState as jest.Mock).mock.results[0].value;
+ expect(useUploadQueue).toHaveBeenCalled();
+ const uploadQueue = (useUploadQueue as jest.Mock).mock.results[0].value;
+ expect(usePictureTaken).toHaveBeenCalledWith({
+ addDamageHandle,
+ sightState,
+ uploadQueue,
+ tasksBySight: props.tasksBySight,
+ });
+
+ unmount();
+ });
+
+ it('should display a Camera with the given config', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Camera, {
+ resolution: props.resolution,
+ format: props.format,
+ quality: props.quality,
+ });
+
+ unmount();
+ });
+
+ it('should use the PhotoCaptureHUD component as the Camera HUD', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Camera, { HUDComponent: PhotoCaptureHUD });
+
+ unmount();
+ });
+
+ it('should pass the callback from the usePictureTaken hook to the Camera component', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expect(usePictureTaken).toHaveBeenCalled();
+ const { handlePictureTaken } = (usePictureTaken as jest.Mock).mock.results[0].value;
+ expectPropsOnChildMock(Camera, { onPictureTaken: handlePictureTaken });
+
+ unmount();
+ });
+
+ it('should pass the proper props to the HUD component', () => {
+ const props = createProps();
+ const { unmount } = render( );
- expectPropsOnChildMock(Camera as jest.Mock, {
- HUDComponent: expect.any(Function),
+ expect(useAddDamageMode).toHaveBeenCalled();
+ const addDamageHandle = (useAddDamageMode as jest.Mock).mock.results[0].value;
+ expect(usePhotoCaptureSightState).toHaveBeenCalled();
+ const sightState = (usePhotoCaptureSightState as jest.Mock).mock.results[0].value;
+ expect(useLoadingState).toHaveBeenCalled();
+ const loading = (useLoadingState as jest.Mock).mock.results[0].value;
+ expectPropsOnChildMock(Camera, {
+ hudProps: {
+ sights: props.sights,
+ selectedSight: sightState.selectedSight,
+ sightsTaken: sightState.sightsTaken,
+ lastPictureTaken: sightState.lastPictureTaken,
+ mode: addDamageHandle.mode,
+ onSelectSight: sightState.selectSight,
+ onAddDamage: addDamageHandle.handleAddDamage,
+ onCancelAddDamage: addDamageHandle.handleCancelAddDamage,
+ onRetry: sightState.retryLoadingInspection,
+ loading,
+ onClose: props.onClose,
+ inspectionId: props.inspectionId,
+ },
});
- const { HUDComponent } = CameraMock.mock.calls[0][0];
- HUDComponent({ sights, cameraPreview: <> >, handle: jest.fn() });
- expect(CameraMock.mock.calls[0][0].resolution).toEqual(state.resolution);
- expect(CameraMock.mock.calls[0][0].format).toEqual(state.compressionFormat);
- expect(CameraMock.mock.calls[0][0].quality).toEqual(Number(state.quality));
unmount();
});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD.test.tsx
deleted file mode 100644
index 6f63617bd..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD.test.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-jest.mock('react-i18next');
-jest.mock('@monkvision/common');
-jest.mock('@monkvision/common-ui-web');
-jest.mock('../../src/PhotoCapture/PhotoCaptureHUDSightPreview', () => ({
- PhotoCaptureHUDSightPreview: jest.fn(() => <>>),
-}));
-jest.mock('../../src/PhotoCapture/PhotoCaptureHUDButtons', () => ({
- PhotoCaptureHUDButtons: jest.fn(() => <>>),
-}));
-jest.mock('../../src/PhotoCapture/hooks', () => ({
- ...jest.requireActual('../../src/PhotoCapture/hooks'),
- useSightState: jest.fn(() => ({ handleSightSelected: jest.fn() })),
-}));
-
-import { render } from '@testing-library/react';
-import { Sight } from '@monkvision/types';
-import { PhotoCaptureHUD } from '../../src/PhotoCapture';
-import { PhotoCaptureHUDSightPreview } from '../../src/PhotoCapture/PhotoCaptureHUDSightPreview';
-import { PhotoCaptureHUDButtons } from '../../src/PhotoCapture/PhotoCaptureHUDButtons';
-
-const sights = [
- { id: 'id', label: { en: 'en', fr: 'fr', de: 'de' } },
- { id: 'id2', label: { en: 'en2', fr: 'fr2', de: 'de2' } },
-] as unknown as Sight[];
-
-describe('PhotoCaptureHUD component', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should render PhotoCaptureHUDSightPreview and PhotoCaptureHUDButtons by default', () => {
- const { unmount } = render(>} />);
-
- expect(PhotoCaptureHUDSightPreview).toHaveBeenCalled();
- expect(PhotoCaptureHUDButtons).toHaveBeenCalled();
- unmount();
- });
-
- it('should render PhotoCaptureHUDButtons component', () => {
- const { unmount } = render(>} />);
-
- expect(PhotoCaptureHUDButtons).toHaveBeenCalledTimes(1);
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDButtons.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons.test.tsx
similarity index 94%
rename from packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDButtons.test.tsx
rename to packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons.test.tsx
index eb2418619..0e4d37af0 100644
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDButtons.test.tsx
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons.test.tsx
@@ -1,13 +1,10 @@
-jest.mock('@monkvision/common-ui-web');
-jest.mock('@monkvision/common');
-
import { expectPropsOnChildMock } from '@monkvision/test-utils';
import { InteractiveStatus } from '@monkvision/types';
import { MonkPicture } from '@monkvision/camera-web';
import { TakePictureButton, Icon } from '@monkvision/common-ui-web';
import { fireEvent, render, screen } from '@testing-library/react';
-import { captureButtonForegroundColors } from '../../src/PhotoCapture/PhotoCaptureHUDButtons/CaptureHUDButtons.styles';
-import { PhotoCaptureHUDButtons } from '../../src/PhotoCapture/PhotoCaptureHUDButtons';
+import { PhotoCaptureHUDButtons } from '../../../src';
+import { captureButtonForegroundColors } from '../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDButtons/PhotoCaptureHUDButtons.styles';
const GALLERY_BTN_TEST_ID = 'monk-gallery-btn';
const CLOSE_BTN_TEST_ID = 'monk-close-btn';
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton.test.tsx
new file mode 100644
index 000000000..60f6d36f3
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton.test.tsx
@@ -0,0 +1,32 @@
+import { render } from '@testing-library/react';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { Button } from '@monkvision/common-ui-web';
+import { useTranslation } from 'react-i18next';
+import { PhotoCaptureHUDCancelButton } from '../../../src';
+
+describe('PhotoCaptureHUDCancelButton component', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should display a Button with the proper label', () => {
+ const label = 'wow-test-label-yes';
+ const tMock = jest.fn(() => label);
+ (useTranslation as jest.Mock).mockImplementationOnce(() => ({ t: tMock }));
+ const { unmount } = render( );
+
+ expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.cancelBtn');
+ expectPropsOnChildMock(Button, { children: label });
+
+ unmount();
+ });
+
+ it('should pass the onClick event to the Button', () => {
+ const onCancel = jest.fn();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, { onClick: onCancel });
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter.test.tsx
new file mode 100644
index 000000000..1113addd3
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter.test.tsx
@@ -0,0 +1,45 @@
+import { render, screen } from '@testing-library/react';
+import { PhotoCaptureHUDCounter } from '../../../src';
+import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks';
+import { useTranslation } from 'react-i18next';
+
+describe('PhotoCaptureHUDCounter component', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should display the sight count if the capture mode is Sight', () => {
+ const totalSights = 51;
+ const sightsTaken = 12;
+ const { unmount } = render(
+ ,
+ );
+
+ expect(screen.queryByText(`${sightsTaken} / ${totalSights}`)).not.toBeNull();
+
+ unmount();
+ });
+
+ it('should display the proper labels for the AddDamage modes', () => {
+ const label = 'fake-label';
+ const tMock = jest.fn(() => label);
+ (useTranslation as jest.Mock).mockImplementation(() => ({ t: tMock }));
+ const { unmount, rerender } = render(
+ ,
+ );
+
+ expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.damagedPartCounter');
+ expect(screen.queryByText(label)).not.toBeNull();
+
+ rerender( );
+ expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.closeupPictureCounter');
+ expect(screen.queryByText(label)).not.toBeNull();
+
+ (useTranslation as jest.Mock).mockRestore();
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay.test.tsx
new file mode 100644
index 000000000..b003d7c81
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDOverlay.test.tsx
@@ -0,0 +1,187 @@
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/react';
+import {
+ CameraHandle,
+ getCameraErrorLabel,
+ UserMediaError,
+ UserMediaErrorType,
+} from '@monkvision/camera-web';
+import { Button, Spinner } from '@monkvision/common-ui-web';
+import { useTranslation } from 'react-i18next';
+import { useObjectTranslation } from '@monkvision/common';
+import { MonkNetworkError } from '@monkvision/network';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { PhotoCaptureHUDOverlay, PhotoCaptureHUDOverlayProps } from '../../../src';
+
+const OVERLAY_TEST_ID = 'overlay';
+
+function createProps(): PhotoCaptureHUDOverlayProps {
+ return {
+ isCaptureLoading: false,
+ captureError: null,
+ handle: {
+ isLoading: false,
+ error: null,
+ retry: jest.fn(),
+ } as unknown as CameraHandle,
+ onRetry: jest.fn(),
+ inspectionId: 'test-inspection-id',
+ };
+}
+
+function mockTranslationFunction(returnValue: string, mockTObj = false): jest.Mock {
+ const mock = jest.fn(() => returnValue);
+ if (mockTObj) {
+ (useObjectTranslation as jest.Mock).mockImplementation(() => ({ tObj: mock }));
+ } else {
+ (useTranslation as jest.Mock).mockImplementation(() => ({ t: mock }));
+ }
+ return mock;
+}
+
+describe('PhotoCaptureHUDOverlay component', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should return null there is no loading or error', () => {
+ const props = createProps();
+ const { container, unmount } = render( );
+
+ expect(container).toBeEmptyDOMElement();
+
+ unmount();
+ });
+
+ it('should display a fixed overlay on top of the screen', () => {
+ const props = createProps();
+ props.isCaptureLoading = true;
+ const { unmount } = render( );
+
+ expect(screen.getByTestId(OVERLAY_TEST_ID)).toHaveStyle({
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ zIndex: 9,
+ backgroundColor: 'rgba(0,0,0,0.8)',
+ });
+
+ unmount();
+ });
+
+ it('should display a spinner on the screen if the camera is loading', () => {
+ const props = createProps();
+ props.handle.isLoading = true;
+ const { unmount } = render( );
+
+ expect(Spinner).toHaveBeenCalled();
+
+ unmount();
+ });
+
+ it('should display a spinner on the screen if the capture is loading', () => {
+ const props = createProps();
+ props.isCaptureLoading = true;
+ const { unmount } = render( );
+
+ expect(Spinner).toHaveBeenCalled();
+
+ unmount();
+ });
+
+ [
+ {
+ errors: [MonkNetworkError.MISSING_TOKEN, MonkNetworkError.INVALID_TOKEN],
+ label: 'photo.hud.error.invalidToken',
+ },
+ { errors: [MonkNetworkError.EXPIRED_TOKEN], label: 'photo.hud.error.expiredToken' },
+ {
+ errors: [MonkNetworkError.INSUFFICIENT_AUTHORIZATION],
+ label: 'photo.hud.error.insufficientAuth',
+ },
+ { errors: [null], label: 'photo.hud.error.inspectionLoading' },
+ ].forEach(({ errors, label }) => {
+ it(`should display the proper error label for ${errors ?? 'unknown capture'} errors`, () => {
+ const props = createProps();
+ const { unmount, rerender } = render( );
+
+ errors.forEach((err) => {
+ const translationLabel = `test-${err}`;
+ const tMock = mockTranslationFunction(translationLabel);
+ props.captureError = new Error();
+ (props.captureError as Error).name = err ?? 'unknown';
+ rerender( );
+
+ expect(tMock).toHaveBeenCalledWith(label);
+ expect(
+ screen.queryByText(err ? translationLabel : `${translationLabel} ${props.inspectionId}`),
+ ).not.toBeNull();
+ });
+
+ (useTranslation as jest.Mock).mockClear();
+ unmount();
+ });
+ });
+
+ it('should display the proper error label for camera errors', () => {
+ const obj = { test: 'hello' };
+ (getCameraErrorLabel as jest.Mock).mockImplementationOnce(() => obj);
+ const label = 'test-label-hello';
+ const tObjMock = mockTranslationFunction(label, true);
+ const props = createProps();
+ props.handle.error = { type: UserMediaErrorType.OTHER } as UserMediaError;
+ const { unmount } = render( );
+
+ expect(getCameraErrorLabel).toHaveBeenCalledWith(props.handle.error.type);
+ expect(tObjMock).toHaveBeenCalledWith(obj);
+ expect(screen.queryByText(label)).not.toBeNull();
+
+ (useObjectTranslation as jest.Mock).mockClear();
+ unmount();
+ });
+
+ it('should display a retry button for camera errors', () => {
+ const props = createProps();
+ props.handle.error = { type: UserMediaErrorType.OTHER } as UserMediaError;
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, {
+ onClick: props.handle.retry,
+ });
+
+ unmount();
+ });
+
+ [
+ MonkNetworkError.MISSING_TOKEN,
+ MonkNetworkError.INVALID_TOKEN,
+ MonkNetworkError.EXPIRED_TOKEN,
+ MonkNetworkError.INSUFFICIENT_AUTHORIZATION,
+ ].forEach((error) => {
+ it(`should not display a retry button for ${error} errors`, () => {
+ const props = createProps();
+ props.captureError = new Error();
+ (props.captureError as Error).name = error;
+ const { unmount } = render( );
+
+ expect(Button).not.toHaveBeenCalled();
+
+ unmount();
+ });
+ });
+
+ it('should display a retry button for unknown capture errors', () => {
+ const props = createProps();
+ props.captureError = new Error();
+ (props.captureError as Error).name = 'unknown';
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, {
+ onClick: props.onRetry,
+ });
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview.test.tsx
new file mode 100644
index 000000000..d4fc619e5
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview.test.tsx
@@ -0,0 +1,117 @@
+jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight', () => ({
+ PhotoCaptureHUDPreviewSight: jest.fn(() => <>>),
+}));
+jest.mock(
+ '../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot',
+ () => ({
+ PhotoCaptureHUDPreviewAddDamage1stShot: jest.fn(() => <>>),
+ }),
+);
+jest.mock(
+ '../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot',
+ () => ({
+ PhotoCaptureHUDPreviewAddDamage2ndShot: jest.fn(() => <>>),
+ }),
+);
+
+import { sights } from '@monkvision/sights';
+import '@testing-library/jest-dom';
+import { render } from '@testing-library/react';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks';
+import {
+ PhotoCaptureHUDPreview,
+ PhotoCaptureHUDPreviewAddDamage1stShot,
+ PhotoCaptureHUDPreviewAddDamage2ndShot,
+ PhotoCaptureHUDPreviewProps,
+ PhotoCaptureHUDPreviewSight,
+} from '../../../src';
+
+function createProps(): PhotoCaptureHUDPreviewProps {
+ const captureSights = [sights['test-sight-1'], sights['test-sight-2'], sights['test-sight-3']];
+ return {
+ selectedSight: captureSights[1],
+ sights: captureSights,
+ sightsTaken: [captureSights[0]],
+ mode: PhotoCaptureMode.SIGHT,
+ onAddDamage: jest.fn(),
+ onCancelAddDamage: jest.fn(),
+ onSelectSight: jest.fn(),
+ streamDimensions: { height: 1234, width: 45678 },
+ isLoading: false,
+ error: null,
+ };
+}
+
+describe('PhotoCaptureHUDPreview component', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should return null if the capture is loading', () => {
+ const props = createProps();
+ props.isLoading = true;
+ const { container, unmount } = render( );
+
+ expect(container).toBeEmptyDOMElement();
+
+ unmount();
+ });
+
+ it('should return null if the capture is in error', () => {
+ const props = createProps();
+ props.error = true;
+ const { container, unmount } = render( );
+
+ expect(container).toBeEmptyDOMElement();
+
+ unmount();
+ });
+
+ it('should return the PhotoCaptureHUDPreviewSight component if the mode is Sight', () => {
+ const props = createProps();
+ props.mode = PhotoCaptureMode.SIGHT;
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(PhotoCaptureHUDPreviewSight, {
+ sights: props.sights,
+ selectedSight: props.selectedSight,
+ onSelectedSight: props.onSelectSight,
+ sightsTaken: props.sightsTaken,
+ onAddDamage: props.onAddDamage,
+ streamDimensions: props.streamDimensions,
+ });
+ expect(PhotoCaptureHUDPreviewAddDamage1stShot).not.toHaveBeenCalled();
+ expect(PhotoCaptureHUDPreviewAddDamage2ndShot).not.toHaveBeenCalled();
+
+ unmount();
+ });
+
+ it('should return the PhotoCaptureHUDPreviewAddDamage1stShot component if the mode is AD 1st Shot', () => {
+ const props = createProps();
+ props.mode = PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT;
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(PhotoCaptureHUDPreviewAddDamage1stShot, {
+ onCancel: props.onCancelAddDamage,
+ });
+ expect(PhotoCaptureHUDPreviewSight).not.toHaveBeenCalled();
+ expect(PhotoCaptureHUDPreviewAddDamage2ndShot).not.toHaveBeenCalled();
+
+ unmount();
+ });
+
+ it('should return the PhotoCaptureHUDPreviewAddDamage1stShot component if the mode is AD 2nd Shot', () => {
+ const props = createProps();
+ props.mode = PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT;
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(PhotoCaptureHUDPreviewAddDamage2ndShot, {
+ onCancel: props.onCancelAddDamage,
+ });
+ expect(PhotoCaptureHUDPreviewSight).not.toHaveBeenCalled();
+ expect(PhotoCaptureHUDPreviewAddDamage1stShot).not.toHaveBeenCalled();
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot.test.tsx
new file mode 100644
index 000000000..0693fb462
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage1stShot.test.tsx
@@ -0,0 +1,76 @@
+jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter', () => ({
+ PhotoCaptureHUDCounter: jest.fn(() => <>>),
+}));
+jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton', () => ({
+ PhotoCaptureHUDCancelButton: jest.fn(() => <>>),
+}));
+
+import { act, render, screen } from '@testing-library/react';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { Button, DynamicSVG } from '@monkvision/common-ui-web';
+import { useTranslation } from 'react-i18next';
+import {
+ PhotoCaptureHUDCancelButton,
+ PhotoCaptureHUDCounter,
+ PhotoCaptureHUDPreviewAddDamage1stShot,
+} from '../../../src';
+import { crosshairSvg } from '../../../src/assets';
+import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks';
+
+describe('PhotoCaptureHUDPreviewAddDamage1stShot component', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should display a crosshair SVG on the screen', () => {
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(DynamicSVG, { svg: crosshairSvg });
+
+ unmount();
+ });
+
+ it('should display the PhotoCaptureHUDCounter component on AD 1st Shot mode', () => {
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(PhotoCaptureHUDCounter, { mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT });
+
+ unmount();
+ });
+
+ it('should display the a cancel button component and pass it the onCancel prop', () => {
+ const onCancel = jest.fn();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(PhotoCaptureHUDCancelButton, { onCancel });
+
+ unmount();
+ });
+
+ it('should display an info popup with the proper label', () => {
+ const label = 'test-label';
+ const tMock = jest.fn(() => label);
+ (useTranslation as jest.Mock).mockImplementationOnce(() => ({ t: tMock }));
+ const { unmount } = render( );
+
+ expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.infoBtn');
+ expectPropsOnChildMock(Button, { children: label });
+
+ unmount();
+ });
+
+ it('should remove the info popup when the user clicks on it', () => {
+ const testId = 'button-test-id';
+ (Button as unknown as jest.Mock).mockImplementation(() =>
);
+ const { unmount } = render( );
+
+ expect(screen.queryByTestId(testId)).not.toBeNull();
+ act(() => {
+ (Button as unknown as jest.Mock).mock.calls[0][0].onClick();
+ });
+ expect(screen.queryByTestId(testId)).toBeNull();
+
+ unmount();
+ (Button as unknown as jest.Mock).mockClear();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot.test.tsx
new file mode 100644
index 000000000..b75066903
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewAddDamage2ndShot.test.tsx
@@ -0,0 +1,88 @@
+jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter', () => ({
+ PhotoCaptureHUDCounter: jest.fn(() => <>>),
+}));
+jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCancelButton', () => ({
+ PhotoCaptureHUDCancelButton: jest.fn(() => <>>),
+}));
+
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/react';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { useTranslation } from 'react-i18next';
+import {
+ PhotoCaptureHUDCancelButton,
+ PhotoCaptureHUDCounter,
+ PhotoCaptureHUDPreviewAddDamage2ndShot,
+} from '../../../src';
+import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks';
+
+const FRAME_CONTAINER_TEST_ID = 'frame-container';
+
+describe('PhotoCaptureHUDPreviewAddDamage2ndShot component', () => {
+ it('should display a frame on the center of the screen', () => {
+ const streamDimensions = { width: 5436, height: 1231 };
+ const { unmount } = render(
+ ,
+ );
+
+ const frameContainerEl = screen.getByTestId(FRAME_CONTAINER_TEST_ID);
+ expect(frameContainerEl.style.aspectRatio).toEqual(
+ `${streamDimensions.width}/${streamDimensions.height}`,
+ );
+ expect(frameContainerEl).toHaveStyle({
+ position: 'absolute',
+ width: '100%',
+ });
+ const frameEl = frameContainerEl.children.item(0);
+ expect(frameEl).toHaveStyle({
+ position: 'absolute',
+ top: '25%',
+ left: '32%',
+ width: '36%',
+ height: '50%',
+ border: '2px solid #FFC000',
+ borderRadius: '10px',
+ boxShadow: '0px 0px 0px 100pc rgba(0, 0, 0, 0.5)',
+ });
+
+ unmount();
+ });
+
+ it('should give the frame container a 16:9 ratio by default if the stream dimensions are not defined', () => {
+ const { unmount } = render( );
+
+ const frameContainerEl = screen.getByTestId(FRAME_CONTAINER_TEST_ID);
+ expect(frameContainerEl.style.aspectRatio).toEqual('16/9');
+
+ unmount();
+ });
+
+ it('should display the PhotoCaptureHUDCounter in AD 2nd Shot mode', () => {
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(PhotoCaptureHUDCounter, { mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT });
+
+ unmount();
+ });
+
+ it('should display the a cancel button component and pass it the onCancel prop', () => {
+ const onCancel = jest.fn();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(PhotoCaptureHUDCancelButton, { onCancel });
+
+ unmount();
+ });
+
+ it('should display a help message with the proper label', () => {
+ const label = 'test';
+ const tMock = jest.fn(() => label);
+ (useTranslation as jest.Mock).mockImplementationOnce(() => ({ t: tMock }));
+ const { unmount } = render( );
+
+ expect(tMock).toHaveBeenCalledWith('photo.hud.addDamage.infoCloseup');
+ expect(screen.queryByText(label)).not.toBeNull();
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton.test.tsx
new file mode 100644
index 000000000..b6ca1585d
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton.test.tsx
@@ -0,0 +1,28 @@
+import { render } from '@testing-library/react';
+import { useTranslation } from 'react-i18next';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { Button } from '@monkvision/common-ui-web';
+import { AddDamageButton } from '../../../../src';
+
+describe('AddDamageButton component', () => {
+ it('should pass the onAddDamage callback to the onClick event of the Button', () => {
+ const onAddDamage = jest.fn();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, { onClick: onAddDamage });
+
+ unmount();
+ });
+
+ it('should display a Button with the proper label', () => {
+ const label = 'test-label-ok';
+ const tMock = jest.fn(() => label);
+ (useTranslation as jest.Mock).mockImplementationOnce(() => ({ t: tMock }));
+ const { unmount } = render( );
+
+ expect(tMock).toHaveBeenCalledWith('photo.hud.sight.addDamageBtn');
+ expectPropsOnChildMock(Button, { children: label });
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.test.tsx
new file mode 100644
index 000000000..aff5a7a66
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.test.tsx
@@ -0,0 +1,99 @@
+jest.mock('../../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter', () => ({
+ PhotoCaptureHUDCounter: jest.fn(() => <>>),
+}));
+jest.mock(
+ '../../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/AddDamageButton',
+ () => ({
+ AddDamageButton: jest.fn(() => <>>),
+ }),
+);
+jest.mock(
+ '../../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider',
+ () => ({
+ SightsSlider: jest.fn(() => <>>),
+ }),
+);
+
+import { render } from '@testing-library/react';
+import { sights } from '@monkvision/sights';
+import { SightOverlay } from '@monkvision/common-ui-web';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import {
+ AddDamageButton,
+ PhotoCaptureHUDCounter,
+ PhotoCaptureHUDPreviewSight,
+ PhotoCaptureHUDSightPreviewProps,
+ SightsSlider,
+} from '../../../../src';
+import { PhotoCaptureMode } from '../../../../src/PhotoCapture/hooks';
+
+function createProps(): PhotoCaptureHUDSightPreviewProps {
+ const captureSights = [
+ sights['test-sight-1'],
+ sights['test-sight-2'],
+ sights['test-sight-3'],
+ sights['test-sight-4'],
+ ];
+ return {
+ sights: captureSights,
+ selectedSight: captureSights[2],
+ sightsTaken: [captureSights[0], captureSights[1]],
+ onSelectedSight: jest.fn(),
+ onAddDamage: jest.fn(),
+ streamDimensions: { width: 4563, height: 992 },
+ };
+}
+
+describe('PhotoCaptureHUDPreviewSight component', () => {
+ it('should display the current sight overlay with the proper aspect ratio', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(SightOverlay, {
+ sight: props.selectedSight,
+ style: expect.objectContaining({
+ aspectRatio: `${props.streamDimensions?.width}/${props.streamDimensions?.height}`,
+ }),
+ });
+
+ unmount();
+ });
+
+ it('should display the PhotoCaptureHUDCounter component with the proper props', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(PhotoCaptureHUDCounter, {
+ mode: PhotoCaptureMode.SIGHT,
+ totalSights: props.sights.length,
+ sightsTaken: props.sightsTaken.length,
+ });
+
+ unmount();
+ });
+
+ it('should display the AddDamageButton component with the proper props', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(AddDamageButton, {
+ onAddDamage: props.onAddDamage,
+ });
+
+ unmount();
+ });
+
+ it('should display the SightsSlider component with the proper props', () => {
+ const props = createProps();
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(SightsSlider, {
+ sights: props.sights,
+ selectedSight: props.selectedSight,
+ sightsTaken: props.sightsTaken,
+ onSelectedSight: props.onSelectedSight,
+ });
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightSliderButton.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightSliderButton.test.tsx
new file mode 100644
index 000000000..aa6f483f0
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightSliderButton.test.tsx
@@ -0,0 +1,55 @@
+import { render } from '@testing-library/react';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { Button } from '@monkvision/common-ui-web';
+import { SightSliderButton } from '../../../../../src';
+
+describe('SightSliderButton component', () => {
+ it('should display a Button with the proper label', () => {
+ const label = 'hello-test-test';
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, { children: label });
+
+ unmount();
+ });
+
+ it('should use the secondary-xdark color by default', () => {
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, { primaryColor: 'secondary-xdark' });
+
+ unmount();
+ });
+
+ it('should use the primary-base color if the sight is selected', () => {
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, { primaryColor: 'primary-base' });
+
+ unmount();
+ });
+
+ it('should use the primary-base color if the sight is taken', () => {
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, { primaryColor: 'primary-base' });
+
+ unmount();
+ });
+
+ it('should not have any icon if the sight is not taken', () => {
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, { icon: undefined });
+
+ unmount();
+ });
+
+ it('should have a check icon if the sight is taken', () => {
+ const { unmount } = render( );
+
+ expectPropsOnChildMock(Button, { icon: 'check' });
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightsSlider.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightsSlider.test.tsx
new file mode 100644
index 000000000..3c9f84459
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightsSlider.test.tsx
@@ -0,0 +1,63 @@
+jest.mock(
+ '../../../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightsSlider/SightSliderButton',
+ () => ({
+ SightSliderButton: jest.fn(() => <>>),
+ }),
+);
+
+import { sights } from '@monkvision/sights';
+import { render } from '@testing-library/react';
+import { useSightLabel } from '@monkvision/common';
+import { Sight } from '@monkvision/types';
+import {
+ SightSliderButton,
+ SightSliderButtonProps,
+ SightsSlider,
+ SightsSliderProps,
+} from '../../../../../src';
+
+function createProps(): SightsSliderProps {
+ const captureSights = [
+ sights['test-sight-1'],
+ sights['test-sight-2'],
+ sights['test-sight-3'],
+ sights['test-sight-4'],
+ sights['test-sight-5'],
+ ];
+ return {
+ sights: captureSights,
+ selectedSight: captureSights[2],
+ sightsTaken: [captureSights[0], captureSights[1]],
+ onSelectedSight: jest.fn(),
+ };
+}
+
+describe('SightsSlider component', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should display a Button for each sight with the proper props', () => {
+ (useSightLabel as jest.Mock).mockImplementation(() => ({ label: (sight: Sight) => sight.id }));
+ const props = createProps();
+ const { unmount } = render( );
+
+ expect(SightSliderButton).toHaveBeenCalledTimes(props.sights.length);
+ props.sights.forEach((sight) => {
+ (props.onSelectedSight as jest.Mock).mockClear();
+ const buttonProps = (SightSliderButton as unknown as jest.Mock).mock.calls.find(
+ (args) => args[0].label === sight.id,
+ )?.[0] as SightSliderButtonProps & { key: string };
+
+ expect(buttonProps).toBeDefined();
+ expect(buttonProps.isSelected).toEqual(props.selectedSight === sight);
+ expect(buttonProps.isTaken).toEqual(props.sightsTaken.includes(sight));
+ expect(typeof buttonProps.onClick).toBe('function');
+ buttonProps.onClick?.();
+ expect(props.onSelectedSight).toHaveBeenCalledWith(sight);
+ });
+
+ unmount();
+ (useSightLabel as jest.Mock).mockClear();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton.test.tsx
deleted file mode 100644
index 72fb996dc..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton.test.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-jest.mock('@monkvision/common-ui-web');
-
-import { render } from '@testing-library/react';
-import { useTranslation } from 'react-i18next';
-import { Button } from '@monkvision/common-ui-web';
-import { CancelButton } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton';
-
-describe('CancelButton component', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should call Button component', () => {
- const buttonMock = Button as unknown as jest.Mock;
- const onCancel = jest.fn();
- const { unmount } = render( );
-
- expect(buttonMock).toHaveBeenCalled();
- expect(buttonMock.mock.calls[0][0].children).toEqual('photo.hud.addDamage.cancelBtn');
-
- unmount();
- });
-
- it('should translate cancel button', () => {
- const onCancel = jest.fn();
- const { unmount } = render( );
-
- const { t } = (useTranslation as jest.Mock).mock.results[0].value;
- expect(t).toHaveBeenCalled();
-
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview.test.tsx
deleted file mode 100644
index 70da1ce8c..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview.test.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { useSightLabel } from '@monkvision/common';
-
-jest.mock('@monkvision/common-ui-web');
-jest.mock('@monkvision/common');
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter', () => ({
- DamageCounter: jest.fn(() => <>>),
-}));
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton', () => ({
- CancelButton: jest.fn(() => <>>),
-}));
-
-import { render, screen } from '@testing-library/react';
-import { useTranslation } from 'react-i18next';
-import { Sight } from '@monkvision/types';
-import { CloseupPreview } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview';
-import { CancelButton } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton';
-import { DamageCounter } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter';
-
-describe('CloseupPreview component', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should render 2 components: DamageCounter, CancelButton', () => {
- const onCancel = jest.fn();
- const { unmount } = render( );
- expect(DamageCounter).toHaveBeenCalled();
- expect(CancelButton).toHaveBeenCalled();
-
- unmount();
- });
-
- it('should use t function from useTranslation hook', () => {
- const onCancel = jest.fn();
- const useTranslationMock = useTranslation as jest.Mock;
- const { unmount } = render( );
-
- const { t } = useTranslationMock.mock.results[0].value;
- expect(t).toHaveBeenCalled();
-
- unmount();
- });
-
- it('should apply the streamDimensions to the aspect ratio', () => {
- const onCancel = jest.fn();
- const dimensions = { width: 1920, height: 1080 };
- const { unmount } = render(
- ,
- );
-
- const frameContainer = screen.getByTestId('frame-container');
- expect(frameContainer.style.aspectRatio).toEqual(`${dimensions.width}/${dimensions.height}`);
-
- unmount();
- });
-
- it('should not render sight label if undefined', () => {
- const onCancel = jest.fn();
- const { unmount } = render( );
-
- const sightLabel = screen.getByTestId('sight-label');
- expect(sightLabel.innerHTML).toEqual('');
-
- unmount();
- });
-
- it('should call label function from useSightLabel hook', () => {
- const onCancel = jest.fn();
- const useSightLabelMock = useSightLabel as jest.Mock;
- const sight = { label: '' } as Sight;
- const { unmount } = render( );
-
- const { label } = useSightLabelMock.mock.results[0].value;
- expect(label).toHaveBeenCalled();
-
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview.test.tsx
deleted file mode 100644
index 3928d57f1..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview.test.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-jest.mock('@monkvision/common-ui-web');
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter', () => ({
- DamageCounter: jest.fn(() => <>>),
-}));
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton', () => ({
- CancelButton: jest.fn(() => <>>),
-}));
-
-import { render } from '@testing-library/react';
-import { Button, DynamicSVG } from '@monkvision/common-ui-web';
-import { CrosshairPreview } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview';
-import { CancelButton } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CancelButton';
-import { DamageCounter } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter';
-
-describe('CrosshairPreview component', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should render 4 components: DynamicSVG, DamageCounter, CancelButton, Button', () => {
- const onCancel = jest.fn();
- const { unmount } = render( );
- expect(DynamicSVG).toHaveBeenCalled();
- expect(DamageCounter).toHaveBeenCalled();
- expect(CancelButton).toHaveBeenCalled();
- expect(Button).toHaveBeenCalled();
-
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter.test.tsx
deleted file mode 100644
index 8ba1d18a6..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter.test.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import { useTranslation } from 'react-i18next';
-import { DamageCounter } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/DamageCounter';
-import { AddDamagePreviewMode } from '../../../src/PhotoCapture/hooks';
-
-describe('DamageCounter component', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should call useTranslation hook and use t function from it', () => {
- const { unmount } = render(
- ,
- );
-
- const { t } = (useTranslation as jest.Mock).mock.results[0].value;
- expect(t).toHaveBeenCalled();
-
- unmount();
- });
-
- it('should render the default text value', () => {
- const { unmount } = render(
- ,
- );
-
- const counterText = screen.getByTestId('damage-counter').textContent;
- expect(counterText).toEqual('1 / 2 • photo.hud.addDamage.damagedPart');
-
- unmount();
- });
-
- it('should render the addDamageMode Closeup preview text value', () => {
- const { unmount } = render(
- ,
- );
-
- const counterText = screen.getByTestId('damage-counter').textContent;
- expect(counterText).toEqual('2 / 2 • photo.hud.addDamage.closeupPicture');
-
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.test.tsx
deleted file mode 100644
index fbaaff636..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDAddDamagePreview/PhotoCaptureHUDAddDamagePreview.test.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-jest.mock('@monkvision/common-ui-web');
-jest.mock('react-i18next');
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview', () => ({
- CrosshairPreview: jest.fn(() => <>>),
-}));
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview', () => ({
- CloseupPreview: jest.fn(() => <>>),
-}));
-
-import { AddDamagePreviewMode } from '../../../src/PhotoCapture/hooks';
-import { render } from '@testing-library/react';
-import { PhotoCaptureHUDAddDamagePreview } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview';
-import { CrosshairPreview } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CrosshairPreview';
-import { CloseupPreview } from '../../../src/PhotoCapture/PhotoCaptureHUDAddDamagePreview/CloseupPreview';
-
-describe('PhotoCaptureHUDAddDamagePreview component', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should call CrosshairPreview component when AddDamagePreviewMode is set to DEFAULT', () => {
- const onCancel = jest.fn();
- const { unmount } = render(
- ,
- );
- expect(CrosshairPreview).toHaveBeenCalled();
-
- unmount();
- });
-
- it('should call CloseupPreview component when AddDamagePreviewMode is set to CLOSEUP_PREVIEW', () => {
- const onCancel = jest.fn();
- const { unmount } = render(
- ,
- );
- expect(CloseupPreview).toHaveBeenCalled();
-
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton.test.tsx
deleted file mode 100644
index e7bd9cf8b..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton.test.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-const { HUDMode } = jest.requireActual('../../../src/PhotoCapture/hook');
-
-jest.mock('@monkvision/common-ui-web');
-jest.mock('react-i18next');
-jest.mock('../../../src/PhotoCapture/hook', () => ({
- i18nAddDamage: {},
- HUDMode,
-}));
-
-import { render } from '@testing-library/react';
-import { Button } from '@monkvision/common-ui-web';
-import { useTranslation } from 'react-i18next';
-import { expectPropsOnChildMock } from '@monkvision/test-utils';
-import { AddDamageButton } from '../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton';
-
-describe('AddDamage component', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should get passed onAddDamage callback', () => {
- const buttonMock = Button as unknown as jest.Mock;
- const onAddDamage = jest.fn();
-
- const { unmount } = render( );
- expect(buttonMock).toHaveBeenCalled();
- expectPropsOnChildMock(buttonMock, { onClick: expect.any(Function) });
- const onClickProp = buttonMock.mock.calls[0][0].onClick;
- onClickProp();
- expect(onAddDamage).toBeCalledWith(HUDMode.ADD_DAMAGE);
-
- unmount();
- });
-
- it('should call t function to translate the button text', () => {
- const onAddDamage = jest.fn();
-
- const { unmount } = render( );
-
- const { t } = (useTranslation as jest.Mock).mock.results[0].value;
- expect(t).toHaveBeenCalled();
-
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/PhotoCaptureHUDSightPreview.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/PhotoCaptureHUDSightPreview.test.tsx
deleted file mode 100644
index c9d25e64f..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/PhotoCaptureHUDSightPreview.test.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter', () => ({
- SightsCounter: jest.fn(() => <>>),
-}));
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton', () => ({
- AddDamageButton: jest.fn(() => <>>),
-}));
-jest.mock('../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider', () => ({
- SightsSlider: jest.fn(() => <>>),
-}));
-
-import { render } from '@testing-library/react';
-import { Sight } from '@monkvision/types';
-import { PhotoCaptureHUDSightPreview } from '../../../src/PhotoCapture/PhotoCaptureHUDSightPreview';
-import { SightsCounter } from '../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter';
-import { AddDamageButton } from '../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/AddDamageButton';
-import { SightsSlider } from '../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider';
-
-const sights = [
- { id: 'id', label: { en: 'en', fr: 'fr', de: 'de' } },
- { id: 'id2', label: { en: 'en2', fr: 'fr2', de: 'de2' } },
-] as unknown as Sight[];
-const sightsTaken = [...sights].slice(0, 1);
-
-describe('PhotoCaptureHUDPreview component', () => {
- it('should render 4 components: SightOverlay, SightsCounter, AddDamageButton, SightsSlider', () => {
- const { unmount } = render(
- ,
- );
-
- expect(SightsCounter).toHaveBeenCalled();
- expect(AddDamageButton).toHaveBeenCalled();
- expect(SightsSlider).toHaveBeenCalled();
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter.test.tsx
deleted file mode 100644
index 709ad49c0..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter.test.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { render } from '@testing-library/react';
-import { SightsCounter } from '../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsCounter';
-
-describe('SightsCounter component', () => {
- it('render counter properly when sightsTaken and totalSights is provided', () => {
- const totalSights = 10;
- const sightsTaken = 5;
-
- const { getByText, unmount } = render(
- ,
- );
- const expectedText = `${sightsTaken} / ${totalSights}`;
- expect(getByText(expectedText).textContent).toEqual(expectedText);
- unmount();
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider.test.tsx
deleted file mode 100644
index 7856b6439..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider.test.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-jest.mock('react-i18next');
-jest.mock('@monkvision/common-ui-web');
-jest.mock('@monkvision/common');
-
-import { render } from '@testing-library/react';
-import { expectPropsOnChildMock } from '@monkvision/test-utils';
-import { Button } from '@monkvision/common-ui-web';
-import { useSightLabel } from '@monkvision/common';
-import { Sight } from '@monkvision/types';
-import { SightsSlider } from '../../../src/PhotoCapture/PhotoCaptureHUDSightPreview/SightsSlider';
-
-const sights = [
- { id: 'id', label: { en: 'en', fr: 'fr', de: 'de' } },
- { id: 'id2', label: { en: 'en2', fr: 'fr2', de: 'de2' } },
-] as unknown as Sight[];
-const sightsTaken = [...sights].slice(0, 1);
-
-describe('SightsSlider component', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should render a slider of buttons when sights is provided', () => {
- const { unmount } = render(
- ,
- );
-
- expect(Button).toHaveBeenCalledTimes(sights.length);
- unmount();
- });
-
- it('should get passed onSightSelected callback', () => {
- const buttonMock = Button as unknown as jest.Mock;
- const onSightSelected = jest.fn();
- const { unmount } = render(
- ,
- );
-
- expect(buttonMock).toHaveBeenCalledTimes(sights.length);
- sights.forEach((sight, index) => {
- expectPropsOnChildMock(buttonMock, { onClick: expect.any(Function) });
- const onClickProp = buttonMock.mock.calls[index][0].onClick;
- onClickProp();
- expect(onSightSelected).toHaveBeenCalledWith(sight);
- });
-
- unmount();
- });
-
- describe('Sight Button', () => {
- it('should have primary-base as primary color if button selected', () => {
- const buttonMock = Button as unknown as jest.Mock;
- const { unmount } = render(
- ,
- );
- sights.forEach((sight, index) => {
- expectPropsOnChildMock(buttonMock, { primaryColor: expect.any(String) });
- const primaryColorProp = buttonMock.mock.calls[index][0].primaryColor;
-
- const baseColor = 'primary-base';
- const secondColor = 'secondary-xdark';
- const thirdColor = 'primary-light';
-
- let expectedColor = secondColor;
- if (sight.id === sights[0].id) {
- expectedColor = baseColor;
- }
- if (sightsTaken?.some((sightTaken) => sightTaken.id === sight.id)) {
- expectedColor = thirdColor;
- }
-
- expect(primaryColorProp).toEqual(expectedColor);
- });
-
- unmount();
- });
-
- it('should call label function to translate the sight label', () => {
- const useSightLabelMock = useSightLabel as jest.Mock;
- const { unmount } = render(
- ,
- );
- const { label } = useSightLabelMock.mock.results[0].value;
- sights.forEach(() => {
- expect(label).toHaveBeenCalled();
- });
- expect(label).toHaveBeenCalledTimes(sights.length);
-
- unmount();
- });
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useAddDamageMode.test.ts b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useAddDamageMode.test.ts
new file mode 100644
index 000000000..d819ec617
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useAddDamageMode.test.ts
@@ -0,0 +1,47 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { PhotoCaptureMode, useAddDamageMode } from '../../../src/PhotoCapture/hooks';
+import { act } from '@testing-library/react';
+
+describe('useAddDamageMode hook', () => {
+ it('should be in the SIGHT mode by default', () => {
+ const { result, unmount } = renderHook(useAddDamageMode);
+ expect(result.current.mode).toEqual(PhotoCaptureMode.SIGHT);
+ unmount();
+ });
+
+ it('should switch to ADD_DAMAGE_1ST_SHOT', () => {
+ const { result, unmount } = renderHook(useAddDamageMode);
+ act(() => result.current.handleAddDamage());
+ expect(result.current.mode).toEqual(PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT);
+ unmount();
+ });
+
+ it('should switch to ADD_DAMAGE_2ND_SHOT', () => {
+ const { result, unmount } = renderHook(useAddDamageMode);
+ act(() => result.current.handleAddDamage());
+ act(() => result.current.updatePhotoCaptureModeAfterPictureTaken());
+ expect(result.current.mode).toEqual(PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT);
+ unmount();
+ });
+
+ it('should go back to the SIGHT mode', () => {
+ const { result, unmount } = renderHook(useAddDamageMode);
+ act(() => result.current.handleAddDamage());
+ act(() => result.current.updatePhotoCaptureModeAfterPictureTaken());
+ act(() => result.current.updatePhotoCaptureModeAfterPictureTaken());
+ expect(result.current.mode).toEqual(PhotoCaptureMode.SIGHT);
+ unmount();
+ });
+
+ it('should allow to cancel add damage at any time', () => {
+ const { result, unmount } = renderHook(useAddDamageMode);
+ act(() => result.current.handleAddDamage());
+ act(() => result.current.handleCancelAddDamage());
+ expect(result.current.mode).toEqual(PhotoCaptureMode.SIGHT);
+ act(() => result.current.handleAddDamage());
+ act(() => result.current.updatePhotoCaptureModeAfterPictureTaken());
+ act(() => result.current.handleCancelAddDamage());
+ expect(result.current.mode).toEqual(PhotoCaptureMode.SIGHT);
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts
new file mode 100644
index 000000000..b11e85d5e
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts
@@ -0,0 +1,188 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { LoadingState, useAsyncEffect } from '@monkvision/common';
+import { Sight } from '@monkvision/types';
+import { useMonitoring } from '@monkvision/monitoring';
+import { sights } from '@monkvision/sights';
+import { useMonkApi } from '@monkvision/network';
+import { act } from '@testing-library/react';
+import {
+ PhotoCaptureSightsParams,
+ usePhotoCaptureSightState,
+} from '../../../src/PhotoCapture/hooks';
+
+function createParams(): PhotoCaptureSightsParams {
+ return {
+ inspectionId: 'test-inspection-id',
+ captureSights: [
+ sights['test-sight-1'],
+ sights['test-sight-2'],
+ sights['test-sight-3'],
+ sights['test-sight-4'],
+ ],
+ apiConfig: { apiDomain: 'test-api-domain', authToken: 'test-auth-token' },
+ loading: {
+ start: jest.fn(),
+ onSuccess: jest.fn(),
+ onError: jest.fn(),
+ } as unknown as LoadingState,
+ onLastSightTaken: jest.fn(),
+ };
+}
+
+function mockGetInspectionResponse(inspectionId: string, takenSights: Sight[]) {
+ return {
+ action: {
+ payload: {
+ images: takenSights.map((sight, index) => ({
+ inspectionId,
+ additionalData: { sight_id: sight.id },
+ path: `test-path-${index}`,
+ mimetype: `test-mimetype-${index}`,
+ width: index * 2000,
+ height: index * 1000,
+ })),
+ },
+ },
+ };
+}
+
+describe('usePhotoCaptureSightState hook', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should throw an error if the no sights are passed', () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ const initialProps = { ...createParams(), captureSights: [] };
+ const { result, unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+ expect(result.error).toBeDefined();
+ unmount();
+ jest.spyOn(console, 'error').mockRestore();
+ });
+
+ it('should properly initialize the state', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+ expect(result.current.selectedSight).toEqual(initialProps.captureSights[0]);
+ expect(result.current.sightsTaken).toEqual([]);
+ expect(result.current.lastPictureTaken).toBeNull();
+ unmount();
+ });
+
+ it('should start a request to fetch the inspection state from the API', () => {
+ const initialProps = createParams();
+ const { unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+
+ expect(useMonkApi).toHaveBeenCalledWith(initialProps.apiConfig);
+ const getInspectionMock = (useMonkApi as jest.Mock).mock.results[0].value.getInspection;
+ expect(initialProps.loading.start).not.toHaveBeenCalled();
+ expect(getInspectionMock).not.toHaveBeenCalled();
+ expect(useAsyncEffect).toHaveBeenCalled();
+ const effect = (useAsyncEffect as jest.Mock).mock.calls[0][0];
+ effect();
+ expect(initialProps.loading.start).toHaveBeenCalled();
+ expect(getInspectionMock).toHaveBeenCalledWith(initialProps.inspectionId);
+
+ unmount();
+ });
+
+ it('should properly update the taken sights after the getInspection API call', () => {
+ const initialProps = createParams();
+ const takenSights = [initialProps.captureSights[0], initialProps.captureSights[1]];
+ const apiResponse = mockGetInspectionResponse(initialProps.inspectionId, takenSights);
+ const { result, unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+
+ expect(initialProps.loading.onSuccess).not.toHaveBeenCalled();
+ expect(useAsyncEffect).toHaveBeenCalled();
+ const { onResolve } = (useAsyncEffect as jest.Mock).mock.calls[0][2];
+ act(() => onResolve(apiResponse));
+
+ expect(result.current.sightsTaken).toEqual(takenSights);
+ expect(result.current.selectedSight).toEqual(
+ initialProps.captureSights.filter((s) => !takenSights.includes(s))[0],
+ );
+ const { images } = apiResponse.action.payload;
+ expect(result.current.lastPictureTaken).toEqual({
+ uri: images[images.length - 1].path,
+ mimetype: images[images.length - 1].mimetype,
+ width: images[images.length - 1].width,
+ height: images[images.length - 1].height,
+ });
+ expect(initialProps.loading.onSuccess).toHaveBeenCalled();
+
+ unmount();
+ });
+
+ it('should properly handle the error returned from the getInspection API call', () => {
+ const initialProps = createParams();
+ const { unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+
+ expect(useMonitoring).toHaveBeenCalled();
+ const handleErrorMock = (useMonitoring as jest.Mock).mock.results[0].value.handleError;
+ expect(handleErrorMock).not.toHaveBeenCalled();
+ expect(initialProps.loading.onError).not.toHaveBeenCalled();
+ expect(useAsyncEffect).toHaveBeenCalled();
+ const { onReject } = (useAsyncEffect as jest.Mock).mock.calls[0][2];
+ const err = { test: 'hello' };
+ act(() => onReject(err));
+
+ expect(handleErrorMock).toHaveBeenCalledWith(err);
+ expect(initialProps.loading.onError).toHaveBeenCalledWith(err);
+
+ unmount();
+ });
+
+ it('should fetch the inspection state again when the inspectionId changes', () => {
+ const initialProps = createParams();
+ const { unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+
+ expect(useAsyncEffect).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.arrayContaining([initialProps.inspectionId]),
+ expect.anything(),
+ );
+
+ unmount();
+ });
+
+ it('should fetch the inspection state again when the retry function is called', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+
+ const retryDep = (useAsyncEffect as jest.Mock).mock.calls[0][1].filter(
+ (dep: any) => dep !== initialProps.inspectionId,
+ )[0];
+ act(() => result.current.retryLoadingInspection());
+ const newRetryDep = (useAsyncEffect as jest.Mock).mock.calls[1][1].filter(
+ (dep: any) => dep !== initialProps.inspectionId,
+ )[0];
+ expect(Object.is(retryDep, newRetryDep)).toBe(false);
+
+ unmount();
+ });
+
+ it('should properly update the state when a sight is taken', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+
+ act(() => result.current.takeSelectedSight());
+ expect(result.current.sightsTaken).toEqual([initialProps.captureSights[0]]);
+ expect(result.current.selectedSight).toEqual(initialProps.captureSights[1]);
+
+ unmount();
+ });
+
+ it('should call onLastSightTaken when the last sight is taken', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(usePhotoCaptureSightState, { initialProps });
+
+ act(() => result.current.takeSelectedSight());
+ act(() => result.current.takeSelectedSight());
+ act(() => result.current.takeSelectedSight());
+ expect(initialProps.onLastSightTaken).not.toHaveBeenCalled();
+ act(() => result.current.takeSelectedSight());
+ expect(initialProps.onLastSightTaken).toHaveBeenCalled();
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/hooks/usePictureTaken.test.ts b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/usePictureTaken.test.ts
new file mode 100644
index 000000000..b5a15ccbb
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/usePictureTaken.test.ts
@@ -0,0 +1,151 @@
+import { TaskName } from '@monkvision/types';
+import {
+ AddDamageHandle,
+ PhotoCaptureMode,
+ usePictureTaken,
+ UseTakePictureParams,
+} from '../../../src/PhotoCapture/hooks';
+import { renderHook } from '@testing-library/react-hooks';
+import { MonkPicture } from '@monkvision/camera-web';
+
+function createParams(): UseTakePictureParams {
+ return {
+ sightState: {
+ setLastPictureTaken: jest.fn(),
+ takeSelectedSight: jest.fn(),
+ selectedSight: { id: 'test-selected-sight', tasks: [TaskName.WHEEL_ANALYSIS] },
+ },
+ addDamageHandle: {
+ mode: PhotoCaptureMode.SIGHT,
+ updatePhotoCaptureModeAfterPictureTaken: jest.fn(),
+ },
+ uploadQueue: {
+ push: jest.fn(),
+ },
+ } as unknown as UseTakePictureParams;
+}
+
+function createMonkPicture(): MonkPicture {
+ return {
+ uri: 'test-monk-uri',
+ mimetype: 'test-mimetype',
+ height: 1234,
+ width: 4567,
+ };
+}
+
+describe('usePictureTaken hook', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should update the last picture taken after a picture is taken', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(usePictureTaken, { initialProps });
+
+ expect(initialProps.sightState.setLastPictureTaken).not.toHaveBeenCalled();
+ const picture = createMonkPicture();
+ result.current.handlePictureTaken(picture);
+ expect(initialProps.sightState.setLastPictureTaken).toHaveBeenCalledWith(picture);
+
+ unmount();
+ });
+
+ it('should create a new sight upload and add it to the upload queue', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(usePictureTaken, { initialProps });
+
+ expect(initialProps.uploadQueue.push).not.toHaveBeenCalled();
+ const picture = createMonkPicture();
+ result.current.handlePictureTaken(picture);
+ expect(initialProps.uploadQueue.push).toHaveBeenCalledWith({
+ mode: initialProps.addDamageHandle.mode,
+ picture,
+ sightId: initialProps.sightState.selectedSight.id,
+ tasks: initialProps.sightState.selectedSight.tasks,
+ });
+
+ unmount();
+ });
+
+ it('should use the tasksBySight map if provided', () => {
+ const tasks = [TaskName.WHEEL_ANALYSIS, TaskName.IMAGES_OCR];
+ const initialProps = {
+ ...createParams(),
+ tasksBySight: {
+ [createParams().sightState.selectedSight.id]: tasks,
+ },
+ };
+ const { result, unmount } = renderHook(usePictureTaken, { initialProps });
+
+ expect(initialProps.uploadQueue.push).not.toHaveBeenCalled();
+ const picture = createMonkPicture();
+ result.current.handlePictureTaken(picture);
+ expect(initialProps.uploadQueue.push).toHaveBeenCalledWith(expect.objectContaining({ tasks }));
+
+ unmount();
+ });
+
+ it('should create a new add damage upload and add it to the upload queue', () => {
+ const defaultParams = createParams();
+ const initialProps = {
+ ...defaultParams,
+ addDamageHandle: {
+ ...defaultParams.addDamageHandle,
+ mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT,
+ } as unknown as AddDamageHandle,
+ };
+ const { result, unmount } = renderHook(usePictureTaken, { initialProps });
+
+ expect(initialProps.uploadQueue.push).not.toHaveBeenCalled();
+ const picture = createMonkPicture();
+ result.current.handlePictureTaken(picture);
+ expect(initialProps.uploadQueue.push).toHaveBeenCalledWith({
+ mode: initialProps.addDamageHandle.mode,
+ picture,
+ });
+
+ unmount();
+ });
+
+ it('should take the current sight if the mode is sight', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(usePictureTaken, { initialProps });
+
+ expect(initialProps.sightState.takeSelectedSight).not.toHaveBeenCalled();
+ result.current.handlePictureTaken(createMonkPicture());
+ expect(initialProps.sightState.takeSelectedSight).toHaveBeenCalled();
+
+ unmount();
+ });
+
+ it('should not take the current sight if the mode is add damage', () => {
+ const defaultParams = createParams();
+ const initialProps = {
+ ...defaultParams,
+ addDamageHandle: {
+ ...defaultParams.addDamageHandle,
+ mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT,
+ } as unknown as AddDamageHandle,
+ };
+ const { result, unmount } = renderHook(usePictureTaken, { initialProps });
+
+ result.current.handlePictureTaken(createMonkPicture());
+ expect(initialProps.sightState.takeSelectedSight).not.toHaveBeenCalled();
+
+ unmount();
+ });
+
+ it('should update the photo capture mode', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(usePictureTaken, { initialProps });
+
+ expect(
+ initialProps.addDamageHandle.updatePhotoCaptureModeAfterPictureTaken,
+ ).not.toHaveBeenCalled();
+ result.current.handlePictureTaken(createMonkPicture());
+ expect(initialProps.addDamageHandle.updatePhotoCaptureModeAfterPictureTaken).toHaveBeenCalled();
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useSightState.test.tsx b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useSightState.test.tsx
deleted file mode 100644
index 50c53f356..000000000
--- a/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useSightState.test.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { HUDMode } from '../../../src/PhotoCapture/hook';
-import { act } from '@testing-library/react';
-import { renderHook } from '@testing-library/react-hooks';
-import { Sight } from '@monkvision/types';
-import { AddDamagePreviewMode, useSightState } from '../../../src/PhotoCapture/hooks';
-
-jest.mock('@monkvision/common');
-
-const sights = [
- { id: 'id', label: { en: 'en', fr: 'fr', de: 'de' } },
- { id: 'id2', label: { en: 'en2', fr: 'fr2', de: 'de2' } },
-] as unknown as Sight[];
-
-describe('useSightState hook', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should initialize with the first sight as selected and an empty list of taken sights', () => {
- const { result, unmount } = renderHook(() => useSightState(sights));
-
- expect(result.current.selectedSight).toEqual(sights[0]);
- expect(result.current.sightsTaken).toEqual([]);
- expect(typeof result.current.handleSightTaken).toEqual('function');
- expect(typeof result.current.setSelectedSight).toEqual('function');
- unmount();
- });
-
- it('should update sightSelected when handleSightSelected is called', () => {
- const { result } = renderHook(() => useSightState(sights));
-
- const newSelectedSight = sights[1];
- act(() => {
- result.current.setSelectedSight(newSelectedSight);
- });
-
- expect(result.current.selectedSight).toEqual(newSelectedSight);
- });
-
- it('should update sightsTaken and sightSelected when handleSightTaken is called with mode is set to DEFAULT', () => {
- const { result } = renderHook(() => useSightState(sights));
-
- expect(result.current.sightsTaken).toEqual([]);
- expect(result.current.selectedSight).toEqual(sights[0]);
- expect(result.current.mode).toEqual(HUDMode.DEFAULT);
- expect(result.current.addDamagePreviewMode).toEqual(AddDamagePreviewMode.DEFAULT);
-
- act(() => {
- result.current.handleSightTaken();
- });
- expect(result.current.sightsTaken).toEqual([sights[0]]);
- expect(result.current.selectedSight).toEqual(sights[1]);
-
- act(() => {
- result.current.handleSightTaken();
- });
-
- expect(result.current.sightsTaken).toEqual([sights[0], sights[1]]);
- expect(result.current.selectedSight).toEqual(sights[1]);
- });
-
- it('should update damagedPictureTaken handleSightTaken is called', async () => {
- const { result, waitForNextUpdate } = renderHook(() => useSightState(sights));
-
- await act(async () => {
- result.current.setMode(HUDMode.ADD_DAMAGE);
- await waitForNextUpdate();
- result.current.handleSightTaken();
- });
- expect(result.current.addDamagePreviewMode).toEqual(AddDamagePreviewMode.CLOSEUP_PREVIEW);
-
- await act(async () => {
- result.current.handleSightTaken();
- });
- expect(result.current.addDamagePreviewMode).toEqual(AddDamagePreviewMode.DEFAULT);
- expect(result.current.mode).toEqual(HUDMode.DEFAULT);
- });
-});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useStartTasksOnComplete.test.ts b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useStartTasksOnComplete.test.ts
new file mode 100644
index 000000000..8ffeede73
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useStartTasksOnComplete.test.ts
@@ -0,0 +1,177 @@
+import { waitFor } from '@testing-library/react';
+import { renderHook } from '@testing-library/react-hooks';
+import { flatMap, LoadingState, uniq } from '@monkvision/common';
+import { sights } from '@monkvision/sights';
+import { useMonitoring } from '@monkvision/monitoring';
+import { useMonkApi } from '@monkvision/network';
+import { TaskName } from '@monkvision/types';
+import { createFakePromise } from '@monkvision/test-utils';
+import {
+ useStartTasksOnComplete,
+ UseStartTasksOnCompleteParams,
+} from '../../../src/PhotoCapture/hooks';
+
+function createParams(): UseStartTasksOnCompleteParams {
+ return {
+ inspectionId: 'test-inspection-id',
+ apiConfig: { apiDomain: 'test-api-domain', authToken: 'test-auth-token' },
+ sights: [
+ sights['test-sight-1'],
+ sights['test-sight-2'],
+ sights['test-sight-3'],
+ sights['test-sight-4'],
+ ],
+ loading: {
+ start: jest.fn(),
+ onSuccess: jest.fn(),
+ onError: jest.fn(),
+ } as unknown as LoadingState,
+ };
+}
+
+describe('useStartTasksOnComplete hook', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should do nothing if startTasksOnComplete is false', () => {
+ const initialProps = { ...createParams(), startTasksOnComplete: false };
+ const { result, unmount } = renderHook(useStartTasksOnComplete, { initialProps });
+
+ expect(useMonitoring).toHaveBeenCalled();
+ const handleErrorMock = (useMonitoring as jest.Mock).mock.results[0].value.handleError;
+ expect(useMonkApi).toHaveBeenCalledWith(initialProps.apiConfig);
+ const startInspectionTasksMock = (useMonkApi as jest.Mock).mock.results[0].value
+ .startInspectionTasks;
+
+ result.current.startTasks();
+ expect(initialProps.loading.start).not.toHaveBeenCalled();
+ expect(initialProps.loading.onSuccess).not.toHaveBeenCalled();
+ expect(initialProps.loading.onError).not.toHaveBeenCalled();
+ expect(initialProps.loading.start).not.toHaveBeenCalled();
+ expect(handleErrorMock).not.toHaveBeenCalled();
+ expect(startInspectionTasksMock).not.toHaveBeenCalled();
+
+ unmount();
+ });
+
+ it('should start the tasks given in the startTasksOnComplete param', () => {
+ const initialProps = { ...createParams(), startTasksOnComplete: [TaskName.DASHBOARD_OCR] };
+ const { result, unmount } = renderHook(useStartTasksOnComplete, { initialProps });
+
+ const startInspectionTasksMock = (useMonkApi as jest.Mock).mock.results[0].value
+ .startInspectionTasks;
+
+ result.current.startTasks();
+ expect(startInspectionTasksMock).toHaveBeenCalledWith(
+ initialProps.inspectionId,
+ initialProps.startTasksOnComplete,
+ );
+
+ unmount();
+ });
+
+ it('should start the tasks given in the tasksBySight param if startTasksOnComplete is true', () => {
+ const defaultProps = createParams();
+ const initialProps = {
+ ...defaultProps,
+ startTasksOnComplete: true,
+ tasksBySight: {
+ 'test-sight-1': [TaskName.DAMAGE_DETECTION, TaskName.PRICING],
+ 'test-sight-2': [TaskName.DASHBOARD_OCR],
+ 'test-sight-3': [TaskName.DAMAGE_DETECTION, TaskName.IMAGES_OCR],
+ 'test-sight-4': [TaskName.WHEEL_ANALYSIS],
+ },
+ };
+ const { result, unmount } = renderHook(useStartTasksOnComplete, { initialProps });
+
+ const startInspectionTasksMock = (useMonkApi as jest.Mock).mock.results[0].value
+ .startInspectionTasks;
+
+ result.current.startTasks();
+ expect(startInspectionTasksMock).toHaveBeenCalledWith(initialProps.inspectionId, [
+ TaskName.DAMAGE_DETECTION,
+ TaskName.PRICING,
+ TaskName.DASHBOARD_OCR,
+ TaskName.IMAGES_OCR,
+ TaskName.WHEEL_ANALYSIS,
+ ]);
+
+ unmount();
+ });
+
+ it('should start the sight tasks if startTasksOnComplete is true and no tasksBySight is given', () => {
+ const defaultProps = createParams();
+ const initialProps = { ...defaultProps, startTasksOnComplete: true };
+ const { result, unmount } = renderHook(useStartTasksOnComplete, { initialProps });
+
+ const startInspectionTasksMock = (useMonkApi as jest.Mock).mock.results[0].value
+ .startInspectionTasks;
+
+ result.current.startTasks();
+ const tasks = uniq(flatMap(initialProps.sights, (sight) => sight.tasks));
+ expect(startInspectionTasksMock).toHaveBeenCalledWith(initialProps.inspectionId, tasks);
+
+ unmount();
+ });
+
+ it('should properly handle loading and error in case of success', async () => {
+ const promise = createFakePromise();
+ const startInspectionTasksMock = jest.fn(() => promise);
+ (useMonkApi as jest.Mock).mockImplementationOnce(() => ({
+ startInspectionTasks: startInspectionTasksMock,
+ }));
+ const initialProps = { ...createParams(), startTasksOnComplete: [TaskName.DASHBOARD_OCR] };
+ const { result, unmount } = renderHook(useStartTasksOnComplete, { initialProps });
+
+ const handleErrorMock = (useMonitoring as jest.Mock).mock.results[0].value.handleError;
+
+ expect(initialProps.loading.start).not.toHaveBeenCalled();
+ expect(startInspectionTasksMock).not.toHaveBeenCalled();
+
+ result.current.startTasks();
+ expect(initialProps.loading.start).toHaveBeenCalled();
+ expect(startInspectionTasksMock).toHaveBeenCalled();
+ expect(initialProps.loading.onSuccess).not.toHaveBeenCalled();
+
+ promise.resolve();
+ await waitFor(() => {
+ expect(initialProps.loading.onSuccess).toHaveBeenCalled();
+ expect(initialProps.loading.onError).not.toHaveBeenCalled();
+ expect(handleErrorMock).not.toHaveBeenCalled();
+ });
+
+ unmount();
+ });
+
+ it('should properly handle loading and error in case of error', async () => {
+ const promise = createFakePromise();
+ const startInspectionTasksMock = jest.fn(() => promise);
+ (useMonkApi as jest.Mock).mockImplementationOnce(() => ({
+ startInspectionTasks: startInspectionTasksMock,
+ }));
+ const initialProps = { ...createParams(), startTasksOnComplete: [TaskName.DASHBOARD_OCR] };
+ const { result, unmount } = renderHook(useStartTasksOnComplete, { initialProps });
+
+ const handleErrorMock = (useMonitoring as jest.Mock).mock.results[0].value.handleError;
+
+ expect(initialProps.loading.start).not.toHaveBeenCalled();
+ expect(startInspectionTasksMock).not.toHaveBeenCalled();
+
+ result.current.startTasks().catch((e) => console.log('beuh', e));
+ expect(initialProps.loading.start).toHaveBeenCalled();
+ expect(startInspectionTasksMock).toHaveBeenCalled();
+ expect(initialProps.loading.onError).not.toHaveBeenCalled();
+ expect(handleErrorMock).not.toHaveBeenCalled();
+
+ const err = new Error('test');
+ promise.reject(err);
+ await waitFor(() => {
+ expect(initialProps.loading.onError).toHaveBeenCalledWith(err);
+ expect(initialProps.loading.onSuccess).not.toHaveBeenCalled();
+ expect(handleErrorMock).toHaveBeenCalledWith(err);
+ });
+
+ unmount();
+ });
+});
diff --git a/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useUploadQueue.test.ts b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useUploadQueue.test.ts
new file mode 100644
index 000000000..bea8c3e85
--- /dev/null
+++ b/packages/public/inspection-capture-web/test/PhotoCapture/hooks/useUploadQueue.test.ts
@@ -0,0 +1,180 @@
+import { LoadingState, useQueue } from '@monkvision/common';
+import { renderHook } from '@testing-library/react-hooks';
+import {
+ AddDamage1stShotPictureUpload,
+ AddDamage2ndShotPictureUpload,
+ PhotoCaptureMode,
+ SightPictureUpload,
+ UploadQueueParams,
+ useUploadQueue,
+} from '../../../src/PhotoCapture/hooks';
+import { ImageType, TaskName } from '@monkvision/types';
+import { useMonkApi } from '@monkvision/network';
+import { useMonitoring } from '@monkvision/monitoring';
+import { act } from '@testing-library/react';
+
+function createParams(): UploadQueueParams {
+ return {
+ inspectionId: 'test-inspection-id',
+ apiConfig: { apiDomain: 'test-api-domain', authToken: 'test-auth-token' },
+ loading: { onError: jest.fn() } as unknown as LoadingState,
+ compliances: { iqa: false },
+ };
+}
+
+describe('useUploadQueue hook', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should return a queue created using the useQueue hook with the proper options', () => {
+ const initialProps = createParams();
+ const { result, unmount } = renderHook(useUploadQueue, { initialProps });
+
+ expect(useQueue).toHaveBeenCalledWith(expect.anything(), {
+ maxProcessingItems: 5,
+ storeFailedItems: true,
+ });
+ const queue = (useQueue as jest.Mock).mock.results[0].value;
+ expect(result.current).toBe(queue);
+
+ unmount();
+ });
+
+ describe('processing function', () => {
+ it('should properly add a sight image', async () => {
+ const initialProps = createParams();
+ const { unmount } = renderHook(useUploadQueue, { initialProps });
+ expect(useMonkApi).toHaveBeenCalled();
+ const addImageMock = (useMonkApi as jest.Mock).mock.results[0].value.addImage;
+ expect(useQueue).toHaveBeenCalled();
+ const process = (useQueue as jest.Mock).mock.calls[0][0];
+
+ const upload: SightPictureUpload = {
+ mode: PhotoCaptureMode.SIGHT,
+ picture: {
+ uri: 'test-monk-uri',
+ mimetype: 'test-mimetype',
+ height: 1234,
+ width: 4567,
+ },
+ sightId: 'test-sight-id',
+ tasks: [TaskName.IMAGES_OCR],
+ };
+ await process(upload);
+
+ expect(addImageMock).toHaveBeenCalledWith({
+ type: ImageType.BEAUTY_SHOT,
+ picture: upload.picture,
+ sightId: upload.sightId,
+ tasks: upload.tasks,
+ compliances: initialProps.compliances,
+ inspectionId: initialProps.inspectionId,
+ });
+
+ unmount();
+ });
+
+ it('should properly add an add damage images', async () => {
+ const initialProps = createParams();
+ const { unmount } = renderHook(useUploadQueue, { initialProps });
+ expect(useMonkApi).toHaveBeenCalled();
+ const addImageMock = (useMonkApi as jest.Mock).mock.results[0].value.addImage;
+ expect(useQueue).toHaveBeenCalled();
+ const process = (useQueue as jest.Mock).mock.calls[0][0];
+
+ const upload1: AddDamage1stShotPictureUpload = {
+ mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT,
+ picture: {
+ uri: 'test-monk-uri-1',
+ mimetype: 'test-mimetype-1',
+ height: 12341,
+ width: 45671,
+ },
+ };
+ await act(async () => {
+ await process(upload1);
+ });
+ expect(addImageMock).toHaveBeenCalledWith({
+ type: ImageType.CLOSE_UP,
+ picture: upload1.picture,
+ siblingKey: expect.any(String),
+ firstShot: true,
+ compliances: initialProps.compliances,
+ inspectionId: initialProps.inspectionId,
+ });
+ const { siblingKey } = addImageMock.mock.calls[0][0];
+
+ addImageMock.mockClear();
+ const upload2: AddDamage2ndShotPictureUpload = {
+ mode: PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT,
+ picture: {
+ uri: 'test-monk-uri-2',
+ mimetype: 'test-mimetype-2',
+ height: 12342,
+ width: 45672,
+ },
+ };
+ await process(upload2);
+
+ expect(addImageMock).toHaveBeenCalledWith({
+ type: ImageType.CLOSE_UP,
+ picture: upload2.picture,
+ siblingKey,
+ firstShot: false,
+ compliances: initialProps.compliances,
+ inspectionId: initialProps.inspectionId,
+ });
+
+ addImageMock.mockClear();
+ await act(async () => {
+ await process({
+ mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT,
+ picture: {
+ uri: 'test-monk-uri-3',
+ mimetype: 'test-mimetype-3',
+ height: 12343,
+ width: 45673,
+ },
+ });
+ });
+ const newSiblingKey = addImageMock.mock.calls[0][0].siblingKey;
+ expect(newSiblingKey).not.toEqual(siblingKey);
+
+ unmount();
+ });
+
+ it('should properly handle api errors', async () => {
+ const err = new Error('test');
+ (useMonkApi as jest.Mock).mockImplementationOnce(() => ({
+ addImage: jest.fn(() => Promise.reject(err)),
+ }));
+ const initialProps = createParams();
+ const { unmount } = renderHook(useUploadQueue, { initialProps });
+
+ expect(useMonitoring).toHaveBeenCalled();
+ const handleErrorMock = (useMonitoring as jest.Mock).mock.results[0].value.handleError;
+ expect(useMonkApi).toHaveBeenCalled();
+ expect(useQueue).toHaveBeenCalled();
+ const process = (useQueue as jest.Mock).mock.calls[0][0];
+
+ await expect(
+ process({
+ mode: PhotoCaptureMode.SIGHT,
+ picture: {
+ uri: 'test-monk-uri',
+ mimetype: 'test-mimetype',
+ height: 1234,
+ width: 4567,
+ },
+ sightId: 'test-sight-id',
+ tasks: [TaskName.IMAGES_OCR],
+ }),
+ ).rejects.toBe(err);
+ expect(handleErrorMock).toHaveBeenCalledWith(err);
+ expect(initialProps.loading.onError).toHaveBeenCalledWith(err);
+
+ unmount();
+ });
+ });
+});
diff --git a/packages/public/monitoring/README.md b/packages/public/monitoring/README.md
index 81f9296f0..dcaa21c6e 100644
--- a/packages/public/monitoring/README.md
+++ b/packages/public/monitoring/README.md
@@ -79,7 +79,7 @@ class MyCustomMonitoringAdapter implements MonitoringAdapter {
// Log stuff
}
- handleError(err: Error | string, context?: Omit): void {
+ handleError(err: unknown, context?: Omit): void {
// Report errors
}
diff --git a/packages/public/monitoring/src/adapters/adapter.ts b/packages/public/monitoring/src/adapters/adapter.ts
index 5d909fb70..2b2bbd2cb 100644
--- a/packages/public/monitoring/src/adapters/adapter.ts
+++ b/packages/public/monitoring/src/adapters/adapter.ts
@@ -221,7 +221,7 @@ export interface MonitoringAdapter {
* @param err The error to handle.
* @param context Optional context that can be sent with the error.
*/
- handleError: (err: Error | string, context?: Omit) => void;
+ handleError: (err: unknown, context?: Omit) => void;
/**
* Create a transaction used for performance measurement.
* @param context Context of the transaction.
diff --git a/packages/public/monitoring/src/adapters/debugAdapter.ts b/packages/public/monitoring/src/adapters/debugAdapter.ts
index c77507087..874bebf1a 100644
--- a/packages/public/monitoring/src/adapters/debugAdapter.ts
+++ b/packages/public/monitoring/src/adapters/debugAdapter.ts
@@ -43,15 +43,15 @@ export class DebugMonitoringAdapter extends EmptyMonitoringAdapter {
loggingFunction(msg, context);
}
- override handleError(err: Error | string, context?: Omit): void {
+ override handleError(err: unknown, context?: Omit): void {
const loggingFunction = DebugMonitoringAdapter.getLoggingFunction(Severity.ERROR);
loggingFunction(err, context);
}
private static createLoggingFunction(
consoleFunction: (...data: any[]) => void,
- ): (msg: string | Error, context?: LogContext | Severity) => void {
- return (msg: string | Error, context?: LogContext | Severity) => {
+ ): (msg: unknown, context?: LogContext | Severity) => void {
+ return (msg: unknown, context?: LogContext | Severity) => {
if (typeof context === 'object' && context.extras) {
return consoleFunction(msg, context.extras);
}
@@ -61,7 +61,7 @@ export class DebugMonitoringAdapter extends EmptyMonitoringAdapter {
private static getLoggingFunction(
context?: LogContext | Severity,
- ): (msg: string | Error, context?: LogContext | Severity) => void {
+ ): (msg: unknown, context?: LogContext | Severity) => void {
let severity = Severity.INFO;
if (typeof context === 'string') {
severity = context;
diff --git a/packages/public/monitoring/src/adapters/emptyAdapter.ts b/packages/public/monitoring/src/adapters/emptyAdapter.ts
index 0f43e130d..24e36d3e9 100644
--- a/packages/public/monitoring/src/adapters/emptyAdapter.ts
+++ b/packages/public/monitoring/src/adapters/emptyAdapter.ts
@@ -71,7 +71,7 @@ export class EmptyMonitoringAdapter implements MonitoringAdapter {
}
}
- handleError(err: Error | string, context?: Omit): void {
+ handleError(err: unknown, context?: Omit): void {
if (this.options.showUnsupportedMethodWarnings) {
console.warn(
'Error handling is not supported by the current Monk Monitoring Adapter and calling handleError will have no effect.',
diff --git a/packages/public/network/README.md b/packages/public/network/README.md
index 451b82435..eb66c00fb 100644
--- a/packages/public/network/README.md
+++ b/packages/public/network/README.md
@@ -11,28 +11,123 @@ yarn add @monkvision/network
If you are using TypeScript, this package comes with its type definitions integrated, so you don't need to install
anything else!
-# Configuring the Package
-In order for this package to work properly, some configuration properties need to be specified. Without properly
-configuring the Network package, most of the MonkJs SDK will not work and errors will be thrown by the different
-components.
+# API Requests
+This package exports an object called `MonkApi` that regroups the different API requests available. The request
+available in this pacakge all follow the same format :
-The recommended way to configure this package is to set the required properties in your index file :
+- They can accept a certain amount of parameters
+- The last parameter of the function will always be the `apiConfig` object, that describes how to communicate with the
+ API (API domain and authentication token).
+- They always return the same object, containing the following properties:
+ - `action` : A `MonkAction` that you can dispatch in the `MonkState` in order to synchronize the local state of the
+ application with the distant state after this request has been made. See the documentation for the
+ `@monkvision/common` package for more details about state management.
+ - `body` : The API response body.
+ - `response` : The raw HTTP response object.
+### getInspection
```typescript
-// index.ts
-import { config } from '@monkvision/network';
+import { MonkApi } from '@monkvision/network';
-config.apiDomain = 'api.preview.monk.ai/v1';
-// etc...
+MonkApi.getInspection(inspectionId, apiConfig);
```
-Note that these values are global and can be set and accessed anywhere within the app. Here is the list of configuration
-properties available :
+Fetch the details of an inspection using its ID. The resulting action of this request will contain the list of
+every entity that has been fetched using this API call.
-| Name | Type | Required | Description |
-|--------------|--------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| apiDomain | string | ✔️ | The HTTP domain of the Monk API. |
-| authDomain | string | ✔️ | The authentication domain URL. |
-| authAudience | string | ✔️ | The authentication token resource identifier. |
-| authClientId | string | ✔️ | The authentication client ID. |
-| authToken | string | | The authentication token used to communicate with the API. **Note : This value is automatically set if you are using the Auth features from the Network package.** |
+| Parameter | Type | Description | Required |
+|--------------|--------------|----------------------------------------------------------|----------|
+| inspectionId | string | The ID of the inspection to get the details of. | ✔️ |
+| apiConfig | ApiConfig | Api config containing the api domain and the auth token. | ✔️ |
+
+### addImage
+```typescript
+import { MonkApi } from '@monkvision/network';
+
+MonkApi.addImage(options, apiConfig);
+```
+
+Add a new image to an inspection. The resulting action of this request will contain the details of the image that has
+been created in the API.
+
+| Parameter | Type | Description | Required |
+|-----------|-----------------|----------------------------------------------------------|----------|
+| options | AddImageOptions | The options used to specify how to upload the image. | ✔️ |
+| apiConfig | ApiConfig | Api config containing the api domain and the auth token. | ✔️ |
+
+### updateTaskStatus
+```typescript
+import { MonkApi } from '@monkvision/network';
+import { ProgressStatus, TaskName } from '@monkvision/types';
+
+MonkApi.updateTaskStatus(inspectionId, TaskName.DAMAGE_DETECTION, ProgressStatus.TODO);
+```
+
+Update the progress status of an inspection task.
+
+**Note : This API call is known to sometimes fail for unknown reasons. In order to fix this, we added a retry config
+to this API request : when failing, this request will retry itself up to 4 times (5 API calls in total), with
+exponentially increasing delay between each request (max delay : 1.5s).**
+
+| Parameter | Type | Description | Required |
+|--------------|----------------|----------------------------------------------------------|----------|
+| inspectionId | string | The ID of the inspection. | ✔️ |
+| name | TaskName | The name of the task to update the progress status of. | ✔️ |
+| status | ProgressStatus | The new progress status of the task. | ✔️ |
+| apiConfig | ApiConfig | Api config containing the api domain and the auth token. | ✔️ |
+
+### startInspectionTasks
+```typescript
+import { MonkApi } from '@monkvision/network';
+import { TaskName } from '@monkvision/types';
+
+MonkApi.startInspectionTasks(inspectionId, [TaskName.DAMAGE_DETECTION, TaskName.WHEEL_ANALYSIS]);
+```
+
+Start some inspection tasks that were in the NOT_STARTED status. This function actually makes one API call for each
+task provided using the `updateTaskStatus`.
+
+**Note : This API call is known to sometimes fail for unknown reasons. Please take note of the details provided in
+this documentation for the `updateTaskStatus` function.**
+
+| Parameter | Type | Description | Required |
+|--------------|----------------|----------------------------------------------------------|----------|
+| inspectionId | string | The ID of the inspection. | ✔️ |
+| names | TaskName[] | The names of the task to start. | ✔️ |
+| apiConfig | ApiConfig | Api config containing the api domain and the auth token. | ✔️ |
+
+# React Tools
+In order to simply integrate the Monk Api requests into your React app, you can make use of the `useMonkApi` hook. This
+custom hook returns a custom version of the `MonkApi` object described in the section above, in which the requests do
+not need to be passed the `apiConfig` parameter (since it is already provided to the `useMonkApi` hook), and in which
+every request call will automatically dispatch the action into the MonkState :
+
+```tsx
+import { useEffect } from 'react';
+import { MonkApi, useMonkApi } from '@monkvision/network';
+import { TaskName } from '@monkvision/types';
+
+function App() {
+ const { getInspection } = useMonkApi(apiConfig);
+
+ useEffect(() => {
+ // This call automatically syncs the local state with the distant state, which means that entities located in the
+ // MonkState (accessed using the `useMonkState` hook) are automatically updated after the response from the server.
+ getInspection(inspectionId);
+ }, []);
+}
+```
+
+# Authentication
+This package also exports tools for dealing with authentication within the Monk SDK :
+
+### JWT token decoding
+You can decode Monk JWT token issued by Auth0 using the `decodeMonkJwt` util function provided by this package :
+
+```typescript
+import { decodeMonkJwt, MonkJwtPayload } from '@monkvision/network';
+
+const decodedToken: MonkJwtPayload = decodeMonkJwt(token);
+```
+
+The available properties in the Monk JWT token payload are described in the MonkJwtPayload typescript interface.
diff --git a/packages/public/network/package.json b/packages/public/network/package.json
index c807bb7af..c52dc887f 100644
--- a/packages/public/network/package.json
+++ b/packages/public/network/package.json
@@ -25,6 +25,8 @@
"lint:fix": "yarn run prettier:fix && yarn run eslint:fix"
},
"dependencies": {
+ "@monkvision/common": "4.0.0",
+ "@monkvision/sights": "4.0.0",
"jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0",
"ky": "^1.2.0"
@@ -34,7 +36,7 @@
"react-dom": "^17.0.2"
},
"devDependencies": {
- "@monkvision/common": "4.0.0",
+ "@monkvision/camera-web": "4.0.0",
"@monkvision/eslint-config-base": "4.0.0",
"@monkvision/eslint-config-typescript": "4.0.0",
"@monkvision/jest-config": "4.0.0",
diff --git a/packages/public/network/src/api/api.ts b/packages/public/network/src/api/api.ts
new file mode 100644
index 000000000..e565deca3
--- /dev/null
+++ b/packages/public/network/src/api/api.ts
@@ -0,0 +1,14 @@
+import { getInspection } from './inspection';
+import { addImage } from './image';
+import { startInspectionTasks, updateTaskStatus } from './task';
+
+/**
+ * Object regrouping the different API requests available to communicate with the API using the `@monkvision/network`
+ * package.
+ */
+export const MonkApi = {
+ getInspection,
+ addImage,
+ updateTaskStatus,
+ startInspectionTasks,
+};
diff --git a/packages/public/network/src/api/config.ts b/packages/public/network/src/api/config.ts
index 8802904a7..e0b811375 100644
--- a/packages/public/network/src/api/config.ts
+++ b/packages/public/network/src/api/config.ts
@@ -1,4 +1,6 @@
+import { Options } from 'ky';
import packageJson from '../../package.json';
+import { beforeError } from './error';
export const sdkVersion = packageJson.version;
@@ -16,12 +18,7 @@ export interface MonkAPIConfig {
authToken: string;
}
-export interface KyConfig {
- baseUrl: string;
- headers: Record;
-}
-
-export function getKyConfig(config: MonkAPIConfig): KyConfig {
+export function getDefaultOptions(config: MonkAPIConfig): Options {
const apiDomain = config.apiDomain.endsWith('/')
? config.apiDomain.substring(0, config.apiDomain.length - 1)
: config.apiDomain;
@@ -29,11 +26,14 @@ export function getKyConfig(config: MonkAPIConfig): KyConfig {
? config.authToken
: `Bearer ${config.authToken}`;
return {
- baseUrl: `https://${apiDomain}`,
+ prefixUrl: `https://${apiDomain}`,
headers: {
'Access-Control-Allow-Origin': '*',
'Authorization': authorizationHeader,
'X-Monk-SDK-Version': sdkVersion,
},
+ hooks: {
+ beforeError: [beforeError],
+ },
};
}
diff --git a/packages/public/network/src/api/error.ts b/packages/public/network/src/api/error.ts
new file mode 100644
index 000000000..319844f41
--- /dev/null
+++ b/packages/public/network/src/api/error.ts
@@ -0,0 +1,74 @@
+import { HTTPError } from 'ky';
+import { BeforeErrorHook } from 'ky/distribution/types/hooks';
+import { ApiError } from './models';
+
+/**
+ * Enumeration of names of known error that can occur when making Monk api requests.
+ */
+export enum MonkNetworkError {
+ /**
+ * The authentication token was not provided.
+ */
+ MISSING_TOKEN = 'MissingToken',
+ /**
+ * The authentication token provided in the request is invalid (badly formatted etc.).
+ */
+ INVALID_TOKEN = 'InvalidToken',
+ /**
+ * The authentication token provided in the request is expired.
+ */
+ EXPIRED_TOKEN = 'TokenExpired',
+ /**
+ * The user corresponding to the authentication token provided in the request does not have the sufficient
+ * authorization to perform the request.
+ */
+ INSUFFICIENT_AUTHORIZATION = 'InsufficientAuthorization',
+}
+
+function getErrorMessage(name: string): string | null {
+ switch (name) {
+ case MonkNetworkError.MISSING_TOKEN:
+ return 'Missing authentication token in request headers.';
+ case MonkNetworkError.INVALID_TOKEN:
+ return 'Invalid authentication token in request.';
+ case MonkNetworkError.EXPIRED_TOKEN:
+ return 'Authentication token is expired.';
+ case MonkNetworkError.INSUFFICIENT_AUTHORIZATION:
+ return 'User does not have the proper authorization grants to perform this request.';
+ default:
+ return null;
+ }
+}
+
+function getErrorName(status: number, message: string): MonkNetworkError | null {
+ if (status === 401 && message.includes('Authorization header is required')) {
+ return MonkNetworkError.MISSING_TOKEN;
+ }
+ if (
+ status === 401 &&
+ [
+ 'Token payload schema unknown',
+ 'Token decode failed',
+ 'Token audience invalid, please check audience',
+ 'Token issuer invalid, please check issuer',
+ 'Wrong authorization header format, should be in the format : Bearer TOKEN',
+ 'Invalid authentication token in request.',
+ ].some((str) => message.includes(str))
+ ) {
+ return MonkNetworkError.INVALID_TOKEN;
+ }
+ if (status === 401 && message.includes('Token is expired')) {
+ return MonkNetworkError.EXPIRED_TOKEN;
+ }
+ // TODO : Also check conditions for MonkNetworkError.INSUFFICIENT_AUTHORIZATION.
+ return null;
+}
+
+/* eslint-disable no-param-reassign */
+export const beforeError: BeforeErrorHook = async (error: HTTPError) => {
+ const { response } = error;
+ const body = (await response.json()) as ApiError;
+ error.name = getErrorName(response.status, body.message) ?? error.name;
+ error.message = getErrorMessage(error.name) ?? error.message;
+ return error;
+};
diff --git a/packages/public/network/src/api/requests/inspections/index.ts b/packages/public/network/src/api/image/index.ts
similarity index 100%
rename from packages/public/network/src/api/requests/inspections/index.ts
rename to packages/public/network/src/api/image/index.ts
diff --git a/packages/public/network/src/api/image/mappers.ts b/packages/public/network/src/api/image/mappers.ts
new file mode 100644
index 000000000..36bd1480e
--- /dev/null
+++ b/packages/public/network/src/api/image/mappers.ts
@@ -0,0 +1,85 @@
+import {
+ CentersOnElement,
+ ComplianceResult,
+ ComplianceResults,
+ Image,
+ ImageSubtype,
+ ImageType,
+ MonkEntityType,
+ ProgressStatus,
+ VehiclePart,
+} from '@monkvision/types';
+import { ApiComplianceResultBase, ApiImage, ApiImageComplianceResults } from '../models';
+
+function mapBaseComplianceResult(result: ApiComplianceResultBase): ComplianceResult {
+ return {
+ status: result.status as ProgressStatus,
+ isCompliant: result.is_compliant,
+ reasons: result.reasons,
+ };
+}
+
+function mapApiImageComplianceResults(
+ compliances: ApiImageComplianceResults | undefined,
+): ComplianceResults | undefined {
+ if (!compliances) {
+ return undefined;
+ }
+ return {
+ imageQualityAssessment: compliances.image_quality_assessment
+ ? {
+ ...mapBaseComplianceResult(compliances.image_quality_assessment),
+ details: compliances.image_quality_assessment.details
+ ? {
+ blurrinessScore: compliances.image_quality_assessment.details.blurriness_score,
+ underexposureScore:
+ compliances.image_quality_assessment.details.underexposure_score,
+ overexposureScore: compliances.image_quality_assessment.details.overexposure_score,
+ }
+ : undefined,
+ }
+ : undefined,
+ coverage360: compliances.coverage_360
+ ? mapBaseComplianceResult(compliances.coverage_360)
+ : undefined,
+ zoomLevel: compliances.zoom_level
+ ? {
+ ...mapBaseComplianceResult(compliances.zoom_level),
+ details: compliances.zoom_level.details
+ ? { zoomScore: compliances.zoom_level.details.zoom_score }
+ : undefined,
+ }
+ : undefined,
+ };
+}
+
+export function mapApiImage(image: ApiImage, inspectionId: string): Image {
+ return {
+ id: image.id,
+ entityType: MonkEntityType.IMAGE,
+ inspectionId,
+ label: image.additional_data?.label,
+ path: image.path,
+ width: image.image_width,
+ height: image.image_height,
+ size: image.binary_size,
+ mimetype: image.mimetype,
+ type: image.image_type as ImageType,
+ subtype: image.image_subtype as ImageSubtype | undefined,
+ siblingKey: image.image_sibling_key,
+ viewpoint: image.viewpoint,
+ detailedViewpoint: image.detailed_viewpoint
+ ? {
+ isExterior: image.detailed_viewpoint.is_exterior,
+ distance: image.detailed_viewpoint.distance,
+ centersOn: image.detailed_viewpoint.centers_on as
+ | (VehiclePart | CentersOnElement)[]
+ | undefined,
+ }
+ : undefined,
+ compliances: mapApiImageComplianceResults(image.compliances),
+ additionalData: image.additional_data,
+ renderedOutputs: [],
+ views: [],
+ };
+}
diff --git a/packages/public/network/src/api/image/requests.ts b/packages/public/network/src/api/image/requests.ts
new file mode 100644
index 000000000..5728bbea4
--- /dev/null
+++ b/packages/public/network/src/api/image/requests.ts
@@ -0,0 +1,206 @@
+import ky from 'ky';
+import { MonkPicture } from '@monkvision/camera-web';
+import { getFileExtensions, MonkActionType, MonkCreatedOneImageAction } from '@monkvision/common';
+import { ImageSubtype, ImageType, TaskName } from '@monkvision/types';
+import { labels, sights } from '@monkvision/sights';
+import { getDefaultOptions, MonkAPIConfig } from '../config';
+import { ApiImage, ApiImagePost } from '../models';
+import { MonkAPIRequest } from '../types';
+import { mapApiImage } from './mappers';
+
+/**
+ * Options used to enable or disable certain compliance checks when uploading a picture to the Monk Api.
+ */
+export interface ComplianceOptions {
+ /**
+ * Set this parameter to `true` to enable the Image Quality Assessment (IQA) compliance check. This checks will verify
+ * the picture bluriness and exposure.
+ */
+ iqa?: boolean;
+}
+
+/**
+ * Options specififed when adding a beauty shot (normal "sight" image) to an inspection.
+ */
+export interface AddBeautyShotImageOptions {
+ /**
+ * The type of the image : `ImageType.BEAUTY_SHOT`;
+ */
+ type: ImageType.BEAUTY_SHOT;
+ /**
+ * The picture to add to the inspection.
+ */
+ picture: MonkPicture;
+ /**
+ * The ID of the inspection to add the image to.
+ */
+ inspectionId: string;
+ /**
+ * The ID of the sight of the image.
+ */
+ sightId: string;
+ /**
+ * The list of tasks to run for this image.
+ */
+ tasks: TaskName[];
+ /**
+ * Additional options used to enable certain compliance checks on the picture.
+ */
+ compliances?: ComplianceOptions;
+}
+
+/**
+ * Options specififed when adding a close up (an "add damage" image) to an inspection using the 2-shot process.
+ */
+export interface Add2ShotCloseUpImageOptions {
+ /**
+ * The type of the image : `ImageType.CLOSE_UP`;
+ */
+ type: ImageType.CLOSE_UP;
+ /**
+ * The picture to add to the inspection.
+ */
+ picture: MonkPicture;
+ /**
+ * The ID of the inspection to add the image to.
+ */
+ inspectionId: string;
+ /**
+ * This parameter is used to link two close up pictures together. In the 2-shot process, you first upload a zoomed-out
+ * picture of the damage, and then a close-up zoomed picture. In order to link these two pictures together, you need
+ * to specify the same `siblingKey` parameter when uploading them. This key must be unique per pair of pictures in the
+ * same inspection.
+ */
+ siblingKey: string;
+ /**
+ * Boolean indicating if this picture is the first picture or the second picture of the 2-shot process.
+ */
+ firstShot: boolean;
+ /**
+ * Additional options used to enable certain compliance checks on the picture.
+ */
+ compliances?: ComplianceOptions;
+}
+
+/**
+ * Union type describing the different options that can be specified when adding a picture to an inspection.
+ */
+export type AddImageOptions = AddBeautyShotImageOptions | Add2ShotCloseUpImageOptions;
+
+const MULTIPART_KEY_IMAGE = 'image';
+const MULTIPART_KEY_JSON = 'json';
+
+function createBeautyShotImageData(
+ options: AddBeautyShotImageOptions,
+ filetype: string,
+): { filename: string; body: ApiImagePost } {
+ const filename = `${options.sightId}-${options.inspectionId}-${Date.now()}.${filetype}`;
+ const label = sights[options.sightId] ? labels[sights[options.sightId].label] : undefined;
+
+ const body: ApiImagePost = {
+ acquisition: {
+ strategy: 'upload_multipart_form_keys',
+ file_key: MULTIPART_KEY_IMAGE,
+ },
+ compliances: {
+ image_quality_assessment: options.compliances?.iqa ? {} : undefined,
+ },
+ image_type: ImageType.BEAUTY_SHOT,
+ tasks: options.tasks,
+ additional_data: {
+ sight_id: options.sightId,
+ label,
+ created_at: new Date(),
+ },
+ };
+
+ return { filename, body };
+}
+
+function createCloseUpImageData(
+ options: Add2ShotCloseUpImageOptions,
+ filetype: string,
+): { filename: string; body: ApiImagePost } {
+ const prefix = options.firstShot ? 'closeup-part' : 'closeup-damage';
+ const filename = `${prefix}-${options.inspectionId}-${Date.now()}.${filetype}`;
+ const label = {
+ en: options.firstShot ? 'Close Up (part)' : 'Close Up (damage)',
+ fr: options.firstShot ? 'Photo Zoomée (partie)' : 'Photo Zoomée (dégât)',
+ de: options.firstShot ? 'Gezoomtes Foto (Teil)' : 'Close Up (Schaden)',
+ };
+
+ const body: ApiImagePost = {
+ acquisition: {
+ strategy: 'upload_multipart_form_keys',
+ file_key: MULTIPART_KEY_IMAGE,
+ },
+ compliances: {
+ image_quality_assessment: options.compliances?.iqa ? {} : undefined,
+ },
+ image_type: ImageType.CLOSE_UP,
+ image_subtype: options.firstShot ? ImageSubtype.CLOSE_UP_PART : ImageSubtype.CLOSE_UP_DAMAGE,
+ image_sibling_key: options.siblingKey,
+ tasks: [TaskName.DAMAGE_DETECTION],
+ additional_data: {
+ label,
+ created_at: new Date(),
+ },
+ };
+
+ return { filename, body };
+}
+
+async function createImageFormData(
+ options: AddImageOptions | Add2ShotCloseUpImageOptions,
+): Promise {
+ const extensions = getFileExtensions(options.picture.mimetype);
+ if (!extensions) {
+ throw new Error(`Unknown picture mimetype : ${options.picture.mimetype}`);
+ }
+ const filetype = extensions[0];
+ const { filename, body } =
+ options.type === ImageType.BEAUTY_SHOT
+ ? createBeautyShotImageData(options, filetype)
+ : createCloseUpImageData(options, filetype);
+
+ const blob = await ky.get(options.picture.uri).blob();
+ const file = new File([blob], filename, { type: filetype });
+
+ const data = new FormData();
+ data.append(MULTIPART_KEY_JSON, JSON.stringify(body));
+ data.append(MULTIPART_KEY_IMAGE, file);
+
+ return data;
+}
+
+/**
+ * Add a new image to an inspection. The resulting action of this request will contain the details of the image that has
+ * been created in the API.
+ *
+ * @param options Upload options for the image.
+ * @param config The API config.
+ */
+export const addImage: MonkAPIRequest<
+ [options: AddImageOptions],
+ MonkCreatedOneImageAction,
+ ApiImage
+> = async (options: AddImageOptions, config: MonkAPIConfig) => {
+ const kyOptions = getDefaultOptions(config);
+ const formData = await createImageFormData(options);
+ const response = await ky.post(`inspections/${options.inspectionId}/images`, {
+ ...kyOptions,
+ body: formData,
+ });
+ const body = await response.json();
+ return {
+ action: {
+ type: MonkActionType.CREATED_ONE_IMAGE,
+ payload: {
+ inspectionId: options.inspectionId,
+ image: mapApiImage(body, options.inspectionId),
+ },
+ },
+ response,
+ body,
+ };
+};
diff --git a/packages/public/network/src/api/index.ts b/packages/public/network/src/api/index.ts
index 7f707c2fd..d7faba55a 100644
--- a/packages/public/network/src/api/index.ts
+++ b/packages/public/network/src/api/index.ts
@@ -1,3 +1,13 @@
export { type MonkAPIConfig } from './config';
-export { MonkApi } from './requests';
+export { type MonkApiResponse, type MonkAPIRequest } from './types';
export { useMonkApi } from './react';
+export { MonkNetworkError } from './error';
+export { MonkApi } from './api';
+
+export {
+ type ComplianceOptions,
+ type AddBeautyShotImageOptions,
+ type Add2ShotCloseUpImageOptions,
+ type AddImageOptions,
+} from './image';
+export { type UpdateProgressStatus } from './task';
diff --git a/packages/public/network/src/api/inspection/index.ts b/packages/public/network/src/api/inspection/index.ts
new file mode 100644
index 000000000..c3dff2f3f
--- /dev/null
+++ b/packages/public/network/src/api/inspection/index.ts
@@ -0,0 +1 @@
+export * from './requests';
diff --git a/packages/public/network/src/api/requests/inspections/mappers.ts b/packages/public/network/src/api/inspection/mappers.ts
similarity index 81%
rename from packages/public/network/src/api/requests/inspections/mappers.ts
rename to packages/public/network/src/api/inspection/mappers.ts
index 75b17c9a0..ee816ca59 100644
--- a/packages/public/network/src/api/requests/inspections/mappers.ts
+++ b/packages/public/network/src/api/inspection/mappers.ts
@@ -1,16 +1,11 @@
import { MonkState } from '@monkvision/common';
import {
- CentersOnElement,
- ComplianceResult,
- ComplianceResults,
CurrencyCode,
CustomSeverityValue,
Damage,
DamageType,
Image,
ImageRegion,
- ImageSubtype,
- ImageType,
Inspection,
MileageUnit,
MonkEntityType,
@@ -35,8 +30,6 @@ import {
} from '@monkvision/types';
import {
ApiCommentSeverityValue,
- ApiComplianceResultBase,
- ApiImageComplianceResults,
ApiImageRegion,
ApiInspectionGet,
ApiPartSeverityValue,
@@ -44,7 +37,8 @@ import {
ApiRenderedOutput,
ApiSeverityResult,
ApiView,
-} from '../../apiModels';
+} from '../models';
+import { mapApiImage } from '../image/mappers';
function mapDamages(response: ApiInspectionGet): { damages: Damage[]; damageIds: string[] } {
const damages: Damage[] = [];
@@ -111,48 +105,6 @@ function mapView(view: ApiView): { view: View; renderedOutputs: RenderedOutput[]
};
}
-function mapBaseComplianceResult(result: ApiComplianceResultBase): ComplianceResult {
- return {
- status: result.status as ProgressStatus,
- isCompliant: result.is_compliant,
- reasons: result.reasons,
- };
-}
-
-function mapCompliances(
- compliances: ApiImageComplianceResults | undefined,
-): ComplianceResults | undefined {
- if (!compliances) {
- return undefined;
- }
- return {
- imageQualityAssessment: compliances.image_quality_assessment
- ? {
- ...mapBaseComplianceResult(compliances.image_quality_assessment),
- details: compliances.image_quality_assessment.details
- ? {
- blurrinessScore: compliances.image_quality_assessment.details.blurriness_score,
- underexposureScore:
- compliances.image_quality_assessment.details.underexposure_score,
- overexposureScore: compliances.image_quality_assessment.details.overexposure_score,
- }
- : undefined,
- }
- : undefined,
- coverage360: compliances.coverage_360
- ? mapBaseComplianceResult(compliances.coverage_360)
- : undefined,
- zoomLevel: compliances.zoom_level
- ? {
- ...mapBaseComplianceResult(compliances.zoom_level),
- details: compliances.zoom_level.details
- ? { zoomScore: compliances.zoom_level.details.zoom_score }
- : undefined,
- }
- : undefined,
- };
-}
-
function mapImages(response: ApiInspectionGet): {
views: View[];
renderedOutputs: RenderedOutput[];
@@ -189,31 +141,9 @@ function mapImages(response: ApiInspectionGet): {
imageIds.push(image.id);
images.push({
- id: image.id,
- entityType: MonkEntityType.IMAGE,
- inspectionId: response.id,
- label: image.additional_data?.label,
- path: image.path,
- width: image.image_width,
- height: image.image_height,
- size: image.binary_size,
- mimetype: image.mimetype,
- type: image.image_type as ImageType,
- subtype: image.image_subtype as ImageSubtype | undefined,
- viewpoint: image.viewpoint,
- detailedViewpoint: image.detailed_viewpoint
- ? {
- isExterior: image.detailed_viewpoint.is_exterior,
- distance: image.detailed_viewpoint.distance,
- centersOn: image.detailed_viewpoint.centers_on as
- | (VehiclePart | CentersOnElement)[]
- | undefined,
- }
- : undefined,
- compliances: mapCompliances(image.compliances),
+ ...mapApiImage(image, response.id),
renderedOutputs: imageRenderedOutputs,
views: imageViews,
- additionalData: image.additional_data,
});
});
@@ -424,7 +354,7 @@ function mapInspection(
};
}
-export function mapGetInspectionResponse(response: ApiInspectionGet): Partial {
+export function mapApiInspectionGet(response: ApiInspectionGet): Partial {
const { images, renderedOutputs, views, imageIds, renderedOutputIds, viewIds } =
mapImages(response);
const { damages, damageIds } = mapDamages(response);
diff --git a/packages/public/network/src/api/inspection/requests.ts b/packages/public/network/src/api/inspection/requests.ts
new file mode 100644
index 000000000..c58b34285
--- /dev/null
+++ b/packages/public/network/src/api/inspection/requests.ts
@@ -0,0 +1,31 @@
+import ky from 'ky';
+import { MonkActionType, MonkGotOneInspectionAction } from '@monkvision/common';
+import { getDefaultOptions, MonkAPIConfig } from '../config';
+import { ApiInspectionGet } from '../models';
+import { mapApiInspectionGet } from './mappers';
+import { MonkAPIRequest } from '../types';
+
+/**
+ * Fetch the details of an inspection using its ID. The resulting action of this request will contain the list of
+ * every entity that has been fetched using this API call.
+ *
+ * @param id The ID of the inspection.
+ * @param config The API config.
+ */
+export const getInspection: MonkAPIRequest<
+ [id: string],
+ MonkGotOneInspectionAction,
+ ApiInspectionGet
+> = async (id: string, config: MonkAPIConfig) => {
+ const options = getDefaultOptions(config);
+ const response = await ky.get(`inspections/${id}`, options);
+ const body = await response.json();
+ return {
+ action: {
+ type: MonkActionType.GOT_ONE_INSPECTION,
+ payload: mapApiInspectionGet(body),
+ },
+ response,
+ body,
+ };
+};
diff --git a/packages/public/network/src/api/apiModels/README.md b/packages/public/network/src/api/models/README.md
similarity index 100%
rename from packages/public/network/src/api/apiModels/README.md
rename to packages/public/network/src/api/models/README.md
diff --git a/packages/public/network/src/api/apiModels/common.ts b/packages/public/network/src/api/models/common.ts
similarity index 97%
rename from packages/public/network/src/api/apiModels/common.ts
rename to packages/public/network/src/api/models/common.ts
index db0ca72ae..c6d710c5d 100644
--- a/packages/public/network/src/api/apiModels/common.ts
+++ b/packages/public/network/src/api/models/common.ts
@@ -171,3 +171,11 @@ export interface ApiLabelPrediction {
}
export type ApiAdditionalData = Record;
+
+export interface ApiIdColumn {
+ id: string;
+}
+
+export interface ApiError {
+ message: string;
+}
diff --git a/packages/public/network/src/api/apiModels/compliance.ts b/packages/public/network/src/api/models/compliance.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/compliance.ts
rename to packages/public/network/src/api/models/compliance.ts
diff --git a/packages/public/network/src/api/apiModels/damage.ts b/packages/public/network/src/api/models/damage.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/damage.ts
rename to packages/public/network/src/api/models/damage.ts
diff --git a/packages/public/network/src/api/apiModels/image.ts b/packages/public/network/src/api/models/image.ts
similarity index 56%
rename from packages/public/network/src/api/apiModels/image.ts
rename to packages/public/network/src/api/models/image.ts
index 950cc7ca9..5d9404509 100644
--- a/packages/public/network/src/api/apiModels/image.ts
+++ b/packages/public/network/src/api/models/image.ts
@@ -3,6 +3,7 @@ import type { ApiAdditionalData, ApiCenterOnElement, ApiLabelPrediction } from '
import type { ApiRenderedOutputs } from './renderedOutput';
import type { ApiImageComplianceResults } from './compliance';
import type { ApiViews } from './view';
+import { ApiBusinessTaskName } from './task';
export type ApiImageType = 'unknown' | 'beauty_shot' | 'close_up';
@@ -24,11 +25,11 @@ export interface ApiViewpointComponent {
}
export interface ApiImageAdditionalData extends ApiAdditionalData {
- sightId?: string;
+ sight_id?: string;
label?: TranslationObject;
}
-export interface ApiImageWithViews {
+export interface ApiImage {
additional_data?: ApiImageAdditionalData;
binary_size: number;
compliances?: ApiImageComplianceResults;
@@ -38,13 +39,52 @@ export interface ApiImageWithViews {
image_height: number;
image_subtype?: ApiImageSubType;
image_type: ApiImageType;
+ image_sibling_key?: string;
image_width: number;
mimetype: string;
name?: string;
path: string;
- rendered_outputs?: ApiRenderedOutputs;
viewpoint?: ApiLabelPrediction;
+}
+
+export interface ApiImageWithViews extends ApiImage {
+ rendered_outputs?: ApiRenderedOutputs;
views?: ApiViews;
}
export type ApiImages = ApiImageWithViews[];
+
+export interface ApiAcquisitionUrl {
+ strategy: 'download_from_url';
+ url: string;
+}
+
+export interface ApiAcquisitionForm {
+ strategy: 'upload_multipart_form_keys';
+ file_key: string;
+}
+
+export type ApiAcquisition = ApiAcquisitionUrl | ApiAcquisitionForm;
+
+export type ApiComplianceParameters = Record;
+
+export interface ApiCoverage360Parameters {
+ sight_id: string;
+}
+
+export interface ApiCompliance {
+ image_quality_assessment?: ApiComplianceParameters;
+ coverage_360?: ApiCoverage360Parameters;
+ zoom_level?: ApiComplianceParameters;
+}
+
+export interface ApiImagePost {
+ acquisition: ApiAcquisition;
+ tasks?: ApiBusinessTaskName[];
+ name?: string;
+ image_type?: ApiImageType;
+ image_subtype?: ApiImageSubType;
+ image_sibling_key?: string;
+ compliances?: ApiCompliance;
+ additional_data?: ApiImageAdditionalData;
+}
diff --git a/packages/public/network/src/api/apiModels/index.ts b/packages/public/network/src/api/models/index.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/index.ts
rename to packages/public/network/src/api/models/index.ts
diff --git a/packages/public/network/src/api/apiModels/inspection.ts b/packages/public/network/src/api/models/inspection.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/inspection.ts
rename to packages/public/network/src/api/models/inspection.ts
diff --git a/packages/public/network/src/api/apiModels/part.ts b/packages/public/network/src/api/models/part.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/part.ts
rename to packages/public/network/src/api/models/part.ts
diff --git a/packages/public/network/src/api/apiModels/pricingV2.ts b/packages/public/network/src/api/models/pricingV2.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/pricingV2.ts
rename to packages/public/network/src/api/models/pricingV2.ts
diff --git a/packages/public/network/src/api/apiModels/refacto_mapper_test.json b/packages/public/network/src/api/models/refacto_mapper_test.json
similarity index 100%
rename from packages/public/network/src/api/apiModels/refacto_mapper_test.json
rename to packages/public/network/src/api/models/refacto_mapper_test.json
diff --git a/packages/public/network/src/api/apiModels/renderedOutput.ts b/packages/public/network/src/api/models/renderedOutput.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/renderedOutput.ts
rename to packages/public/network/src/api/models/renderedOutput.ts
diff --git a/packages/public/network/src/api/apiModels/severityResult.ts b/packages/public/network/src/api/models/severityResult.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/severityResult.ts
rename to packages/public/network/src/api/models/severityResult.ts
diff --git a/packages/public/network/src/api/apiModels/task.ts b/packages/public/network/src/api/models/task.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/task.ts
rename to packages/public/network/src/api/models/task.ts
diff --git a/packages/public/network/src/api/apiModels/vehicle.ts b/packages/public/network/src/api/models/vehicle.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/vehicle.ts
rename to packages/public/network/src/api/models/vehicle.ts
diff --git a/packages/public/network/src/api/apiModels/view.ts b/packages/public/network/src/api/models/view.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/view.ts
rename to packages/public/network/src/api/models/view.ts
diff --git a/packages/public/network/src/api/apiModels/wheelAnalysis.ts b/packages/public/network/src/api/models/wheelAnalysis.ts
similarity index 100%
rename from packages/public/network/src/api/apiModels/wheelAnalysis.ts
rename to packages/public/network/src/api/models/wheelAnalysis.ts
diff --git a/packages/public/network/src/api/react.ts b/packages/public/network/src/api/react.ts
index c1426eda2..3d78d6211 100644
--- a/packages/public/network/src/api/react.ts
+++ b/packages/public/network/src/api/react.ts
@@ -1,33 +1,26 @@
-import {
- useMonkState,
- MonkAction,
- MonkActionType,
- MonkUpdateStateAction,
-} from '@monkvision/common';
import { Dispatch } from 'react';
+import { MonkAction, useMonkState } from '@monkvision/common';
import { MonkAPIConfig } from './config';
-import { MonkAPIRequest, MonkAPIResponse } from './requests/types';
-import { MonkApi } from './requests';
+import { MonkAPIRequest, MonkApiResponse } from './types';
+import { ApiIdColumn } from './models';
+import { MonkApi } from './api';
-function reactifyRequest(
- request: MonkAPIRequest,
+function reactifyRequest(
+ request: MonkAPIRequest ,
config: MonkAPIConfig,
dispatch: Dispatch,
-): (...args: T) => Promise> {
- return async (...args: T) => {
+): (...args: A) => Promise> {
+ return async (...args: A) => {
const result = await request(...args, config);
- const action: MonkUpdateStateAction = {
- type: MonkActionType.UPDATE_STATE,
- payload: result.payload,
- };
- dispatch(action);
+ dispatch(result.action);
return result;
};
}
/**
- * Custom hook that returns a MonkApi object in which all the requests don't need a config param (since it is provided)
- * as a prop to this hook, and automatically update the state (using the MonkState hook) when a state has been received.
+ * Custom hook that returns a MonkApi object in which all the requests don't need a config param (since it is provided
+ * as a prop to this hook), and automatically update the state (using the MonkState hook) when a state has been
+ * received.
*
* **Note: This hook needs to have the MonkContext set up and defined in order to work properly.**
*
@@ -37,6 +30,44 @@ export function useMonkApi(config: MonkAPIConfig) {
const { dispatch } = useMonkState();
return {
+ /**
+ * Fetch the details of an inspection using its ID. The resulting action of this request will contain the list of
+ * every entity that has been fetched using this API call.
+ *
+ * @param id The ID of the inspection.
+ */
getInspection: reactifyRequest(MonkApi.getInspection, config, dispatch),
+ /**
+ * Add a new image to an inspection. The resulting action of this request will contain the details of the image that
+ * has been created in the API.
+ *
+ * @param options Upload options for the image.
+ */
+ addImage: reactifyRequest(MonkApi.addImage, config, dispatch),
+ /**
+ * Update the progress status of an inspection task.
+ *
+ * **Note : This API call is known to sometimes fail for unknown reasons. In order to fix this, we added a retry config
+ * to this API request : when failing, this request will retry itself up to 4 times (5 API calls in total), with
+ * exponentially increasing delay between each request (max delay : 1.5s).**
+ *
+ * @param inspectionId The ID of the inspection.
+ * @param name The name of the task to update the progress status of.
+ * @param status The new progress status of the task.
+ */
+ updateTaskStatus: reactifyRequest(MonkApi.updateTaskStatus, config, dispatch),
+ /**
+ * Start some inspection tasks that were in the NOT_STARTED status. This function actually makes one API call for each
+ * task provided using the `updateTaskStatus`.
+ *
+ * **Note : This API call is known to sometimes fail for unknown reasons. Please take note of the details provided in
+ * the TSDoc of the `updateTaskStatus` function.**
+ *
+ * @param inspectionId The ID of the inspection.
+ * @param names The names of the task to start.
+ *
+ * @see updateTaskStatus
+ */
+ startInspectionTasks: reactifyRequest(MonkApi.startInspectionTasks, config, dispatch),
};
}
diff --git a/packages/public/network/src/api/requests/index.ts b/packages/public/network/src/api/requests/index.ts
deleted file mode 100644
index ccc2e0267..000000000
--- a/packages/public/network/src/api/requests/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as inspections from './inspections';
-
-/**
- * Object containing requests used to communicate with the Monk API.
- */
-export const MonkApi = {
- ...inspections,
-};
diff --git a/packages/public/network/src/api/requests/inspections/requests.ts b/packages/public/network/src/api/requests/inspections/requests.ts
deleted file mode 100644
index 32b522608..000000000
--- a/packages/public/network/src/api/requests/inspections/requests.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import ky from 'ky';
-import { getKyConfig, MonkAPIConfig } from '../../config';
-import { MonkAPIRequest } from '../types';
-import { ApiInspectionGet } from '../../apiModels';
-import { mapGetInspectionResponse } from './mappers';
-
-/**
- * Utility function that fetches an inspection data from the API.
- */
-export const getInspection: MonkAPIRequest<[id: string], ApiInspectionGet> = async (
- id: string,
- config: MonkAPIConfig,
-) => {
- const { baseUrl, headers } = getKyConfig(config);
- const response = await ky.get(`${baseUrl}/inspections/${id}`, {
- headers,
- });
- const body = await response.json();
- return {
- payload: {
- entities: mapGetInspectionResponse(body),
- },
- response,
- body,
- };
-};
diff --git a/packages/public/network/src/api/requests/types.ts b/packages/public/network/src/api/requests/types.ts
deleted file mode 100644
index 6f9d06e04..000000000
--- a/packages/public/network/src/api/requests/types.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { MonkUpdateStatePayload } from '@monkvision/common';
-import { MonkAPIConfig } from '../config';
-
-/**
- * The default response format that every utility function returns when communicating with the Monk API.
- */
-export interface MonkAPIResponse {
- /**
- * The payload containing the information needed to update the state.
- */
- payload: MonkUpdateStatePayload;
- /**
- * The raw response object obtained from the fetch method when making the request.
- */
- response: Response;
- /**
- * The body of the response.
- */
- body: T;
-}
-
-/**
- * Generic type definition for a utility function that makes a request to the Monk API.
- */
-export type MonkAPIRequest = (
- ...args: [...T, MonkAPIConfig]
-) => Promise>;
diff --git a/packages/public/network/src/api/task/index.ts b/packages/public/network/src/api/task/index.ts
new file mode 100644
index 000000000..c3dff2f3f
--- /dev/null
+++ b/packages/public/network/src/api/task/index.ts
@@ -0,0 +1 @@
+export * from './requests';
diff --git a/packages/public/network/src/api/task/requests.ts b/packages/public/network/src/api/task/requests.ts
new file mode 100644
index 000000000..812e1db25
--- /dev/null
+++ b/packages/public/network/src/api/task/requests.ts
@@ -0,0 +1,87 @@
+import ky from 'ky';
+import { MonkActionType, MonkUpdatedManyTasksAction } from '@monkvision/common';
+import { ProgressStatus, TaskName } from '@monkvision/types';
+import { getDefaultOptions, MonkAPIConfig } from '../config';
+import { ApiIdColumn } from '../models';
+import { MonkAPIRequest } from '../types';
+
+/**
+ * The different progress statuses that can be specified when updating the status of a task.
+ */
+export type UpdateProgressStatus =
+ | ProgressStatus.NOT_STARTED
+ | ProgressStatus.TODO
+ | ProgressStatus.DONE
+ | ProgressStatus.VALIDATED;
+
+/**
+ * Update the progress status of an inspection task.
+ *
+ * **Note : This API call is known to sometimes fail for unknown reasons. In order to fix this, we added a retry config
+ * to this API request : when failing, this request will retry itself up to 4 times (5 API calls in total), with
+ * exponentially increasing delay between each request (max delay : 1.5s).**
+ *
+ * @param inspectionId The ID of the inspection.
+ * @param name The name of the task to update the progress status of.
+ * @param status The new progress status of the task.
+ * @param config The API config.
+ */
+export const updateTaskStatus: MonkAPIRequest<
+ [inspectionId: string, name: TaskName, status: UpdateProgressStatus],
+ MonkUpdatedManyTasksAction
+> = async (
+ inspectionId: string,
+ name: TaskName,
+ status: UpdateProgressStatus,
+ config: MonkAPIConfig,
+) => {
+ const kyOptions = getDefaultOptions(config);
+ const response = await ky.patch(`inspections/${inspectionId}/tasks/${name}`, {
+ ...kyOptions,
+ json: { status },
+ retry: {
+ methods: ['patch'],
+ limit: 4,
+ backoffLimit: 1500,
+ },
+ });
+ const body = await response.json();
+ return {
+ action: {
+ type: MonkActionType.UPDATED_MANY_TASKS,
+ payload: [{ id: body.id, status }],
+ },
+ response,
+ body,
+ };
+};
+
+/**
+ * Start some inspection tasks that were in the NOT_STARTED status. This function actually makes one API call for each
+ * task provided using the `updateTaskStatus`.
+ *
+ * **Note : This API call is known to sometimes fail for unknown reasons. Please take note of the details provided in
+ * the TSDoc of the `updateTaskStatus` function.**
+ *
+ * @param inspectionId The ID of the inspection.
+ * @param names The names of the task to start.
+ * @param config The API config.
+ *
+ * @see updateTaskStatus
+ */
+export const startInspectionTasks: MonkAPIRequest<
+ [inspectionId: string, names: TaskName[]],
+ MonkUpdatedManyTasksAction
+> = async (inspectionId: string, names: TaskName[], config: MonkAPIConfig) => {
+ const responses = await Promise.all(
+ names.map((name) => updateTaskStatus(inspectionId, name, ProgressStatus.TODO, config)),
+ );
+ return {
+ action: {
+ type: MonkActionType.UPDATED_MANY_TASKS,
+ payload: responses.map((res) => res.action.payload[0]),
+ },
+ response: responses[0].response,
+ body: responses[0].body,
+ };
+};
diff --git a/packages/public/network/src/api/types.ts b/packages/public/network/src/api/types.ts
new file mode 100644
index 000000000..2d1fa815c
--- /dev/null
+++ b/packages/public/network/src/api/types.ts
@@ -0,0 +1,32 @@
+import { KyResponse } from 'ky';
+import { MonkAction } from '@monkvision/common';
+import { ApiIdColumn } from './models';
+import { MonkAPIConfig } from './config';
+
+/**
+ * Type definition for the response of a Monk Api request.
+ */
+export interface MonkApiResponse {
+ /**
+ * The MonkAction to be dispatched in the MonkState if you want to synchronize the local state with the distant state
+ * after this API call has been made.
+ */
+ action: T;
+ /**
+ * The raw HTTP response object.
+ */
+ response: KyResponse;
+ /**
+ * The body of the response.
+ */
+ body: K;
+}
+
+/**
+ * Generic type definition for a utility function that makes a request to the Monk API.
+ */
+export type MonkAPIRequest<
+ A extends unknown[],
+ T extends MonkAction,
+ K extends object = ApiIdColumn,
+> = (...args: [...A, MonkAPIConfig]) => Promise>;
diff --git a/packages/public/network/test/api/config.test.ts b/packages/public/network/test/api/config.test.ts
index aa8e20462..aecc35b8a 100644
--- a/packages/public/network/test/api/config.test.ts
+++ b/packages/public/network/test/api/config.test.ts
@@ -1,5 +1,6 @@
import packageJson from '../../package.json';
-import { getKyConfig, MonkAPIConfig, sdkVersion } from '../../src/api/config';
+import { getDefaultOptions, MonkAPIConfig, sdkVersion } from '../../src/api/config';
+import { beforeError } from '../../src/api/error';
describe('Network package API global config utils', () => {
describe('sdkVersion global constant', () => {
@@ -8,67 +9,57 @@ describe('Network package API global config utils', () => {
});
});
- describe('getKyConfig function', () => {
+ describe('getDefaultOptions function', () => {
const baseConfig: MonkAPIConfig = {
apiDomain: 'testapidomain',
authToken: 'Bearer testtoken',
};
- it('should set the baseURL property', () => {
- expect(getKyConfig(baseConfig)).toEqual(
- expect.objectContaining({
- baseUrl: `https://${baseConfig.apiDomain}`,
- }),
- );
+ it('should return the proper prefixUrl', () => {
+ expect(getDefaultOptions(baseConfig).prefixUrl).toEqual(`https://${baseConfig.apiDomain}`);
});
- it('should remove the ending slash from the baseURL property', () => {
- expect(getKyConfig({ ...baseConfig, apiDomain: `${baseConfig.apiDomain}/` })).toEqual(
- expect.objectContaining({
- baseUrl: `https://${baseConfig.apiDomain}`,
- }),
- );
+ it('should remove the ending slash from the prefixUrl property', () => {
+ expect(
+ getDefaultOptions({ ...baseConfig, apiDomain: `${baseConfig.apiDomain}/` }).prefixUrl,
+ ).toEqual(`https://${baseConfig.apiDomain}`);
});
it('should set the Access-Control-Allow-Origin header', () => {
- expect(getKyConfig(baseConfig)).toEqual(
+ expect(getDefaultOptions(baseConfig).headers).toEqual(
expect.objectContaining({
- headers: expect.objectContaining({
- 'Access-Control-Allow-Origin': '*',
- }),
+ 'Access-Control-Allow-Origin': '*',
}),
);
});
it('should set the Authorization header', () => {
- expect(getKyConfig(baseConfig)).toEqual(
+ expect(getDefaultOptions(baseConfig).headers).toEqual(
expect.objectContaining({
- headers: expect.objectContaining({
- Authorization: baseConfig.authToken,
- }),
+ Authorization: baseConfig.authToken,
}),
);
});
it('should add the "Bearer " prefix to the token if it is missing', () => {
const authToken = 'testtokentest';
- expect(getKyConfig({ ...baseConfig, authToken })).toEqual(
+ expect(getDefaultOptions({ ...baseConfig, authToken }).headers).toEqual(
expect.objectContaining({
- headers: expect.objectContaining({
- Authorization: `Bearer ${authToken}`,
- }),
+ Authorization: `Bearer ${authToken}`,
}),
);
});
it('should set the X-Monk-SDK-Version header', () => {
- expect(getKyConfig(baseConfig)).toEqual(
+ expect(getDefaultOptions(baseConfig).headers).toEqual(
expect.objectContaining({
- headers: expect.objectContaining({
- 'X-Monk-SDK-Version': packageJson.version,
- }),
+ 'X-Monk-SDK-Version': packageJson.version,
}),
);
});
+
+ it('should return the proper beforeError hook', () => {
+ expect(getDefaultOptions(baseConfig).hooks?.beforeError).toContain(beforeError);
+ });
});
});
diff --git a/packages/public/network/test/api/error.test.ts b/packages/public/network/test/api/error.test.ts
new file mode 100644
index 000000000..06107066c
--- /dev/null
+++ b/packages/public/network/test/api/error.test.ts
@@ -0,0 +1,72 @@
+import { HTTPError } from 'ky';
+import { beforeError, MonkNetworkError } from '../../src/api/error';
+
+function createMockError(status: number, message: string): HTTPError {
+ return {
+ name: 'test-name',
+ message: 'test-message',
+ response: {
+ status,
+ json: jest.fn(() => Promise.resolve({ message })),
+ },
+ } as unknown as HTTPError;
+}
+
+describe('Network Api Error utils', () => {
+ describe('beforeError Ky hook', () => {
+ const beforeErrorTestCases = [
+ {
+ status: 401,
+ messages: ['Authorization header is required'],
+ expectedName: MonkNetworkError.MISSING_TOKEN,
+ expectedMessage: 'Missing authentication token in request headers',
+ },
+ {
+ status: 401,
+ messages: [
+ 'Token payload schema unknown',
+ 'Token decode failed',
+ 'Token audience invalid, please check audience',
+ 'Token issuer invalid, please check issuer',
+ 'Wrong authorization header format, should be in the format : Bearer TOKEN',
+ 'Invalid authentication token in request.',
+ ],
+ expectedName: MonkNetworkError.INVALID_TOKEN,
+ expectedMessage: 'Invalid authentication token in request',
+ },
+ {
+ status: 401,
+ messages: ['Token is expired'],
+ expectedName: MonkNetworkError.EXPIRED_TOKEN,
+ expectedMessage: 'Authentication token is expired',
+ },
+ ];
+
+ beforeErrorTestCases.forEach((testCase) => {
+ it(`should be able to detect ${testCase.expectedName} errors`, async () => {
+ for (let i = 0; i < testCase.messages.length; i++) {
+ const message = testCase.messages[i];
+ // eslint-disable-next-line no-await-in-loop
+ const result = await beforeError(createMockError(testCase.status, message));
+ expect(result).toEqual(
+ expect.objectContaining({
+ name: testCase.expectedName,
+ message: `${testCase.expectedMessage}.`,
+ }),
+ );
+ }
+ });
+ });
+
+ it('should leave the name and message untouched if the error is not recognized', async () => {
+ const result = await beforeError(createMockError(111, 'test'));
+ const error = createMockError(111, 'test');
+ expect(result).toEqual(
+ expect.objectContaining({
+ name: error.name,
+ message: error.message,
+ }),
+ );
+ });
+ });
+});
diff --git a/packages/public/network/test/api/image/apiImage.data.json b/packages/public/network/test/api/image/apiImage.data.json
new file mode 100644
index 000000000..2a3042f41
--- /dev/null
+++ b/packages/public/network/test/api/image/apiImage.data.json
@@ -0,0 +1,58 @@
+{
+ "additional_data": {
+ "created_at": "2024-02-06T10:28:56.504Z",
+ "label": {
+ "de": "Hinten Seitlich Niedrig Links",
+ "en": "Rear Lateral Low Left",
+ "fr": "Arrière latéral gauche - vue basse",
+ "key": "rear-lateral-low-left"
+ },
+ "sight_id": "haccord-W-Bn3bU1"
+ },
+ "binary_size": 108009,
+ "compliances": {
+ "compliance_status": "non_compliant",
+ "coverage_360": {
+ "is_compliant": false,
+ "reasons": ["test_reason"],
+ "status": "DONE"
+ },
+ "image_quality_assessment": {
+ "is_compliant": false,
+ "reasons": ["test_reason_2"],
+ "status": "DONE",
+ "details": {
+ "blurriness_score": 0.8,
+ "overexposure_score": 0.6,
+ "underexposure_score": 0.4
+ }
+ },
+ "zoom_level": {
+ "is_compliant": false,
+ "reasons": ["test_reason_3"],
+ "status": "DONE",
+ "details": {
+ "zoom_score": 0.1
+ }
+ }
+ },
+ "detailed_viewpoint": {
+ "is_exterior": true,
+ "distance": 99,
+ "centers_on": ["front", "front_left"]
+ },
+ "has_vehicle": true,
+ "id": "6564fa84-7ae9-bccd-650e-58fb7dcf924b",
+ "image_height": 720,
+ "image_subtype": "test_image_subtype",
+ "image_type": "beauty_shot",
+ "image_sibling_key": "test_sibling_key",
+ "image_width": 1280,
+ "mimetype": "image/jpeg",
+ "name": "test_name",
+ "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/None-f3f8b6cc-21ea-47a8-8484-e327072c43a2.jpeg?generation=1707215333711124&alt=media",
+ "viewpoint": {
+ "prediction": "test_prediction",
+ "confidence": 0.9
+ }
+}
diff --git a/packages/public/network/test/api/image/apiImage.data.ts b/packages/public/network/test/api/image/apiImage.data.ts
new file mode 100644
index 000000000..6c711acb1
--- /dev/null
+++ b/packages/public/network/test/api/image/apiImage.data.ts
@@ -0,0 +1,64 @@
+export default {
+ additionalData: {
+ created_at: '2024-02-06T10:28:56.504Z',
+ label: {
+ de: 'Hinten Seitlich Niedrig Links',
+ en: 'Rear Lateral Low Left',
+ fr: 'Arrière latéral gauche - vue basse',
+ key: 'rear-lateral-low-left',
+ },
+ sight_id: 'haccord-W-Bn3bU1',
+ },
+ size: 108009,
+ compliances: {
+ coverage360: {
+ isCompliant: false,
+ reasons: ['test_reason'],
+ status: 'DONE',
+ },
+ imageQualityAssessment: {
+ isCompliant: false,
+ reasons: ['test_reason_2'],
+ status: 'DONE',
+ details: {
+ blurrinessScore: 0.8,
+ overexposureScore: 0.6,
+ underexposureScore: 0.4,
+ },
+ },
+ zoomLevel: {
+ isCompliant: false,
+ reasons: ['test_reason_3'],
+ status: 'DONE',
+ details: {
+ zoomScore: 0.1,
+ },
+ },
+ },
+ detailedViewpoint: {
+ isExterior: true,
+ distance: 99,
+ centersOn: ['front', 'front_left'],
+ },
+ entityType: 'IMAGE',
+ id: '6564fa84-7ae9-bccd-650e-58fb7dcf924b',
+ height: 720,
+ label: {
+ de: 'Hinten Seitlich Niedrig Links',
+ en: 'Rear Lateral Low Left',
+ fr: 'Arrière latéral gauche - vue basse',
+ key: 'rear-lateral-low-left',
+ },
+ subtype: 'test_image_subtype',
+ type: 'beauty_shot',
+ siblingKey: 'test_sibling_key',
+ width: 1280,
+ mimetype: 'image/jpeg',
+ path: 'https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/None-f3f8b6cc-21ea-47a8-8484-e327072c43a2.jpeg?generation=1707215333711124&alt=media',
+ viewpoint: {
+ prediction: 'test_prediction',
+ confidence: 0.9,
+ },
+ views: [],
+ renderedOutputs: [],
+};
diff --git a/packages/public/network/test/api/image/mappers.test.ts b/packages/public/network/test/api/image/mappers.test.ts
new file mode 100644
index 000000000..5aa65e549
--- /dev/null
+++ b/packages/public/network/test/api/image/mappers.test.ts
@@ -0,0 +1,17 @@
+import data from './apiImage.data.json';
+import apiImageParsed from './apiImage.data';
+import { ApiImage } from '../../../src/api/models';
+import { mapApiImage } from '../../../src/api/image/mappers';
+
+describe('Image API Mappers', () => {
+ describe('ApiImage mapper', () => {
+ it('should properly map the ApiImage object', () => {
+ const inspectionId = 'test-id';
+ const result = mapApiImage(data as unknown as ApiImage, inspectionId);
+ expect(result).toEqual({
+ ...apiImageParsed,
+ inspectionId,
+ });
+ });
+ });
+});
diff --git a/packages/public/network/test/api/image/requests.test.ts b/packages/public/network/test/api/image/requests.test.ts
new file mode 100644
index 000000000..8902fa206
--- /dev/null
+++ b/packages/public/network/test/api/image/requests.test.ts
@@ -0,0 +1,174 @@
+jest.mock('@monkvision/common', () => ({
+ ...jest.requireActual('@monkvision/common'),
+ getFileExtensions: jest.fn(() => ['aaa', 'bbb']),
+}));
+jest.mock('../../../src/api/config', () => ({
+ getDefaultOptions: jest.fn(() => ({ prefixUrl: 'getDefaultOptionsTest' })),
+}));
+jest.mock('../../../src/api/inspection/mappers', () => ({
+ mapApiImage: jest.fn(() => ({ test: 'hello' })),
+}));
+
+import { labels, sights } from '@monkvision/sights';
+import ky from 'ky';
+import { ImageSubtype, ImageType, TaskName } from '@monkvision/types';
+import { getFileExtensions, MonkActionType } from '@monkvision/common';
+import { getDefaultOptions } from '../../../src/api/config';
+import {
+ AddBeautyShotImageOptions,
+ Add2ShotCloseUpImageOptions,
+ addImage,
+} from '../../../src/api/image';
+import { mapApiImage } from '../../../src/api/image/mappers';
+
+const apiConfig = { apiDomain: 'apiDomain', authToken: 'authToken' };
+
+function createBeautyShotImageOptions(): AddBeautyShotImageOptions {
+ return {
+ type: ImageType.BEAUTY_SHOT,
+ picture: {
+ uri: 'test-uri',
+ height: 720,
+ width: 1280,
+ mimetype: 'image/jpeg',
+ },
+ inspectionId: 'test-inspection-id',
+ sightId: 'test-sight-id',
+ tasks: [TaskName.DAMAGE_DETECTION, TaskName.WHEEL_ANALYSIS],
+ compliances: {
+ iqa: true,
+ },
+ };
+}
+
+function createCloseUpImageOptions(): Add2ShotCloseUpImageOptions {
+ return {
+ type: ImageType.CLOSE_UP,
+ picture: {
+ uri: 'test-uri',
+ height: 720,
+ width: 1280,
+ mimetype: 'image/jpeg',
+ },
+ inspectionId: 'test-inspection-id',
+ siblingKey: 'test-sibling-key',
+ firstShot: true,
+ compliances: {
+ iqa: true,
+ },
+ };
+}
+
+describe('Image requests', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('addImage request', () => {
+ it('should make a request to the proper URL and map the resulting response', async () => {
+ const options = createBeautyShotImageOptions();
+ const result = await addImage(options, apiConfig);
+ const response = await (ky.post as jest.Mock).mock.results[0].value;
+ const body = await response.json();
+
+ expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig);
+ expect(ky.post).toHaveBeenCalledWith(
+ `inspections/${options.inspectionId}/images`,
+ expect.objectContaining(getDefaultOptions(apiConfig)),
+ );
+ expect(result).toEqual({
+ action: {
+ type: MonkActionType.CREATED_ONE_IMAGE,
+ payload: {
+ inspectionId: options.inspectionId,
+ image: mapApiImage(body, options.inspectionId),
+ },
+ },
+ response,
+ body,
+ });
+ });
+
+ it('should properly create the formdata for a beautyshot', async () => {
+ const fileMock = { test: 'hello' } as unknown as File;
+ const fileConstructorSpy = jest.spyOn(global, 'File').mockImplementationOnce(() => fileMock);
+ const options = createBeautyShotImageOptions();
+ await addImage(options, apiConfig);
+
+ expect(ky.post).toHaveBeenCalled();
+ const formData = (ky.post as jest.Mock).mock.calls[0][1].body as FormData;
+ expect(typeof formData?.get('json')).toBe('string');
+ expect(JSON.parse(formData.get('json') as string)).toEqual({
+ acquisition: {
+ strategy: 'upload_multipart_form_keys',
+ file_key: 'image',
+ },
+ compliances: {
+ image_quality_assessment: options.compliances?.iqa ? {} : undefined,
+ },
+ image_type: ImageType.BEAUTY_SHOT,
+ tasks: options.tasks,
+ additional_data: {
+ sight_id: options.sightId,
+ created_at: expect.any(String),
+ ...(sights[options.sightId] ? { label: labels[sights[options.sightId].label] } : {}),
+ },
+ });
+ expect(getFileExtensions).toHaveBeenCalledWith(options.picture.mimetype);
+ const filetype = (getFileExtensions as jest.Mock).mock.results[0].value[0];
+ expect(ky.get).toHaveBeenCalledWith(options.picture.uri);
+ const blob = await (ky.get as jest.Mock).mock.results[0].value.blob();
+ expect(fileConstructorSpy).toHaveBeenCalledWith(
+ [blob],
+ expect.stringMatching(
+ new RegExp(`${options.sightId}-${options.inspectionId}-\\d{13}.${filetype}`),
+ ),
+ { type: filetype },
+ );
+ });
+
+ it('should properly create the formdata for a closeup', async () => {
+ const fileMock = { test: 'hello' } as unknown as File;
+ const fileConstructorSpy = jest.spyOn(global, 'File').mockImplementationOnce(() => fileMock);
+ const options = createCloseUpImageOptions();
+ await addImage(options, apiConfig);
+
+ expect(ky.post).toHaveBeenCalled();
+ const formData = (ky.post as jest.Mock).mock.calls[0][1].body as FormData;
+ expect(typeof formData?.get('json')).toBe('string');
+ expect(JSON.parse(formData.get('json') as string)).toEqual({
+ acquisition: {
+ strategy: 'upload_multipart_form_keys',
+ file_key: 'image',
+ },
+ compliances: {
+ image_quality_assessment: options.compliances?.iqa ? {} : undefined,
+ },
+ image_type: ImageType.CLOSE_UP,
+ image_subtype: options.firstShot
+ ? ImageSubtype.CLOSE_UP_PART
+ : ImageSubtype.CLOSE_UP_DAMAGE,
+ image_sibling_key: options.siblingKey,
+ tasks: [TaskName.DAMAGE_DETECTION],
+ additional_data: {
+ label: {
+ en: options.firstShot ? 'Close Up (part)' : 'Close Up (damage)',
+ fr: options.firstShot ? 'Photo Zoomée (partie)' : 'Photo Zoomée (dégât)',
+ de: options.firstShot ? 'Gezoomtes Foto (Teil)' : 'Close Up (Schaden)',
+ },
+ created_at: expect.any(String),
+ },
+ });
+ expect(getFileExtensions).toHaveBeenCalledWith(options.picture.mimetype);
+ const filetype = (getFileExtensions as jest.Mock).mock.results[0].value[0];
+ expect(ky.get).toHaveBeenCalledWith(options.picture.uri);
+ const blob = await (ky.get as jest.Mock).mock.results[0].value.blob();
+ const prefix = options.firstShot ? 'closeup-part' : 'closeup-damage';
+ expect(fileConstructorSpy).toHaveBeenCalledWith(
+ [blob],
+ expect.stringMatching(new RegExp(`${prefix}-${options.inspectionId}-\\d{13}.${filetype}`)),
+ { type: filetype },
+ );
+ });
+ });
+});
diff --git a/packages/public/network/test/api/inspection/apiInspectionGet.data.json b/packages/public/network/test/api/inspection/apiInspectionGet.data.json
new file mode 100644
index 000000000..e06f4efc0
--- /dev/null
+++ b/packages/public/network/test/api/inspection/apiInspectionGet.data.json
@@ -0,0 +1,4140 @@
+{
+ "accident_nature": null,
+ "additional_data": {
+ "damage_detection_version": "v2",
+ "environment": {
+ "custom_inspection": true
+ },
+ "use_dynamic_crops": true
+ },
+ "compliances": null,
+ "created_at": "2023-12-13T13:27:09.065973+00:00",
+ "creator_id": "google-oauth2|109722589040992162710",
+ "damages": [
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_size_cm": 50.021743434811555,
+ "damage_type": "scratch",
+ "id": "80bac861-01b6-9381-80d0-6a1e0690bfc6",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "DAMAGE",
+ "part_ids": ["2d917d5d-6636-5ea7-2dfb-df22611072e0"],
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 22919.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=216&y=504&width=531&height=739&uuid=72c9a923-d181-4584-8d29-f6290a41d140"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_size_cm": 3.17945481147886,
+ "damage_type": "scratch",
+ "id": "c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "DAMAGE",
+ "part_ids": ["56c74ad5-c077-1d08-56ad-e8aac751314f"],
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 40.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=719&y=797&width=757&height=826&uuid=bcd1e0e8-c6bd-4ea6-addc-746853672836"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:17.816097+00:00",
+ "created_by": "close_up",
+ "damage_type": "paint_peeling",
+ "id": "be6cb3ae-8bfd-5efb-be06-11d18cdb72bc",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "DAMAGE",
+ "part_ids": ["833efbf2-dda5-8c31-8354-598dda83a076"],
+ "related_images": [
+ {
+ "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
+ "base_image_type": "close_up",
+ "image_type": "close_up",
+ "mimetype": "image/jpeg",
+ "object_type": "IMAGE",
+ "order": 0,
+ "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:18.033002+00:00",
+ "created_by": "close_up",
+ "damage_type": "misshape",
+ "id": "3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "DAMAGE",
+ "part_ids": ["833efbf2-dda5-8c31-8354-598dda83a076"],
+ "related_images": [
+ {
+ "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
+ "base_image_type": "close_up",
+ "image_type": "close_up",
+ "mimetype": "image/jpeg",
+ "object_type": "IMAGE",
+ "order": 0,
+ "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media"
+ }
+ ]
+ }
+ ],
+ "deleted_at": null,
+ "id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "images": [
+ {
+ "additional_data": {
+ "category": "exterior",
+ "sight_id": "ffocus18-S3kgFOBb",
+ "label": {
+ "de": "Hinten Seitlich Niedrig Links",
+ "en": "Rear Lateral Low Left",
+ "fr": "Arrière Gauche Latéral - vue basse"
+ }
+ },
+ "binary_size": 141539,
+ "has_vehicle": true,
+ "id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "image_height": 1080,
+ "image_type": "beauty_shot",
+ "image_width": 1920,
+ "mimetype": "image/jpeg",
+ "name": "rear_lateral_low_left.jpeg",
+ "object_type": "IMAGE",
+ "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg?generation=1702474029543236&alt=media",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "rendering of detected parts"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "87650e8f-b8c9-6c6b-870f-acf0bfef402c",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.688404_c49980a3-4c96-44ec-bdb5-76e25391efc3.jpg"
+ },
+ {
+ "additional_data": {
+ "description": "rendering of detected damages"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "d44c1e3d-fcab-dd30-d426-bc42fb8df177",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.710278_24b3ec1c-5282-4451-8985-afd589513069.jpg"
+ }
+ ],
+ "viewpoint": {
+ "confidence": 0.9847987294197083,
+ "prediction": "left"
+ },
+ "views": [
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "08ec65ae-3fb6-e4a3-0886-c7d13890c8e4",
+ "id": "ab2f9850-0165-3458-ab45-3a2f0643181f",
+ "image_region": {
+ "id": "2f8da631-b95a-f569-2fe7-044ebe7cd92e",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 205,
+ "width": 543,
+ "xmin": 88,
+ "ymin": 113
+ },
+ "polygons": [
+ [
+ [606, 137],
+ [598, 131],
+ [576, 126],
+ [491, 124],
+ [489, 122],
+ [369, 122],
+ [367, 124],
+ [332, 124],
+ [330, 126],
+ [289, 128],
+ [243, 146],
+ [202, 155],
+ [152, 176],
+ [134, 189],
+ [115, 211],
+ [113, 218],
+ [113, 253],
+ [119, 233],
+ [139, 213],
+ [150, 216],
+ [160, 233],
+ [167, 237],
+ [176, 237],
+ [187, 233],
+ [202, 233],
+ [210, 242],
+ [210, 246],
+ [204, 253],
+ [176, 268],
+ [171, 266],
+ [152, 266],
+ [141, 274],
+ [139, 300],
+ [128, 309],
+ [141, 309],
+ [143, 307],
+ [191, 309],
+ [193, 307],
+ [219, 307],
+ [221, 305],
+ [276, 305],
+ [278, 303],
+ [289, 303],
+ [291, 305],
+ [319, 305],
+ [321, 303],
+ [487, 303],
+ [489, 300],
+ [519, 300],
+ [526, 298],
+ [537, 290],
+ [552, 261],
+ [558, 237],
+ [582, 194],
+ [589, 168],
+ [606, 146]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "d8195eaa-8800-3c56-d873-fcd58f261011",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=88&y=12&width=631&height=419&uuid=465f6cb3-fe92-4911-b219-4e09e37f2e4a"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "cae4e3f4-c675-af59-ca8e-418bc153831e",
+ "id": "4d503f11-7b27-a0aa-4d3a-9d6e7c018ced",
+ "image_region": {
+ "id": "ed4041cc-5953-dd1d-ed2a-e3b35e75f15a",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 158,
+ "width": 244,
+ "xmin": 552,
+ "ymin": 139
+ },
+ "polygons": [
+ [
+ [563, 281],
+ [572, 290],
+ [576, 290],
+ [585, 281],
+ [598, 281],
+ [600, 279],
+ [632, 285],
+ [669, 283],
+ [702, 276],
+ [715, 276],
+ [743, 283],
+ [774, 283],
+ [785, 276],
+ [785, 263],
+ [776, 244],
+ [763, 224],
+ [743, 205],
+ [730, 196],
+ [713, 176],
+ [680, 155],
+ [665, 148],
+ [639, 146],
+ [632, 152],
+ [619, 189],
+ [600, 209],
+ [589, 229],
+ [569, 255],
+ [563, 272]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "0bd88b55-6e6f-d766-0bb2-292a6949fb21",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=127&width=796&height=309&uuid=8ee0b33a-83d6-4d3f-9624-f333c2ab63f7"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "299b478c-4091-0165-29f1-e5f347b72d22",
+ "id": "afbadee2-e194-3b07-afd0-7c9de6b21740",
+ "image_region": {
+ "id": "066fa03c-3975-d8f2-0605-02433e53f4b5",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 59,
+ "width": 66,
+ "xmin": 1630,
+ "ymin": 330
+ },
+ "polygons": [
+ [
+ [1635, 335],
+ [1633, 342],
+ [1639, 353],
+ [1639, 361],
+ [1661, 385],
+ [1670, 388],
+ [1683, 383],
+ [1694, 370],
+ [1692, 361],
+ [1685, 355],
+ [1674, 351],
+ [1666, 340],
+ [1646, 333]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "4df4bd91-b34d-5da2-4d9e-1feeb46b71e5",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1624&y=330&width=1702&height=389&uuid=f5422914-4b91-4c3f-af25-bbcf6ce39b70"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "fff6bd07-f76a-1597-ff9c-1f78f04c39d0",
+ "id": "7145ebd3-58f4-2328-712f-49ac5fd20f6f",
+ "image_region": {
+ "id": "e88f242c-6f51-4f78-e8e5-86536877633f",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 45,
+ "width": 66,
+ "xmin": 1,
+ "ymin": 203
+ },
+ "polygons": [
+ [
+ [65, 205],
+ [4, 244],
+ [8, 246],
+ [23, 233],
+ [43, 224]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "fd4a9f9b-7e8b-59e2-fd20-3de479ad75a5",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1&y=201&width=67&height=250&uuid=b5bf1613-2d11-4b5d-81ff-25a8c37635f3"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "833efbf2-dda5-8c31-8354-598dda83a076",
+ "id": "b0b552b2-f1b6-1bcc-b0df-f0cdf690378b",
+ "image_region": {
+ "id": "0c7155ae-2228-cda4-0c1b-f7d1250ee1e3",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 258,
+ "width": 973,
+ "xmin": 158,
+ "ymin": 45
+ },
+ "polygons": [
+ [
+ [202, 126],
+ [269, 122],
+ [271, 120],
+ [287, 120],
+ [317, 113],
+ [334, 113],
+ [363, 107],
+ [415, 107],
+ [417, 105],
+ [456, 105],
+ [471, 109],
+ [508, 111],
+ [539, 118],
+ [567, 118],
+ [569, 120],
+ [606, 122],
+ [628, 131],
+ [667, 137],
+ [698, 152],
+ [722, 170],
+ [765, 211],
+ [785, 224],
+ [948, 239],
+ [965, 246],
+ [998, 272],
+ [1026, 285],
+ [1041, 285],
+ [1050, 290],
+ [1065, 292],
+ [1054, 287],
+ [1059, 281],
+ [1078, 281],
+ [1028, 272],
+ [985, 233],
+ [983, 224],
+ [993, 216],
+ [1004, 216],
+ [1020, 222],
+ [1039, 220],
+ [1039, 218],
+ [1035, 218],
+ [1030, 213],
+ [1030, 207],
+ [1037, 200],
+ [1070, 198],
+ [1078, 189],
+ [1087, 185],
+ [1070, 170],
+ [1059, 165],
+ [1037, 172],
+ [1015, 163],
+ [998, 148],
+ [998, 139],
+ [987, 128],
+ [983, 118],
+ [965, 105],
+ [941, 96],
+ [880, 81],
+ [830, 78],
+ [800, 72],
+ [780, 72],
+ [778, 70],
+ [739, 68],
+ [717, 63],
+ [696, 63],
+ [676, 59],
+ [598, 59],
+ [595, 57],
+ [478, 59],
+ [476, 61],
+ [408, 65],
+ [406, 68],
+ [380, 70],
+ [334, 78],
+ [271, 96]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "015826ca-e188-6e37-0132-84b5e6ae4270",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=158&y=0&width=1131&height=538&uuid=67101981-8f78-46b5-a637-f1719056b000"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "02ef856b-c9bf-3932-0285-2714ce991575",
+ "id": "ca7c9b47-530d-6eb3-ca16-3938542b42f4",
+ "image_region": {
+ "id": "101de385-7efa-78b0-1077-41fa79dc54f7",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 83,
+ "width": 110,
+ "xmin": 1595,
+ "ymin": 419
+ },
+ "polygons": [
+ [
+ [1696, 427],
+ [1692, 422],
+ [1685, 422],
+ [1674, 429],
+ [1659, 433],
+ [1620, 455],
+ [1605, 470],
+ [1600, 479],
+ [1602, 488],
+ [1618, 499],
+ [1635, 499],
+ [1666, 483],
+ [1687, 477],
+ [1700, 464],
+ [1700, 448]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "57d9e4d8-030a-c10e-57b3-46a7042ced49",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1595&y=419&width=1705&height=502&uuid=de52a81b-ffab-4999-84ae-90082a59e42e"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "3a4f083c-10fb-b483-3a25-aa4317dd98c4",
+ "id": "aba1ed91-94b8-c4d3-abcb-4fee939ee894",
+ "image_region": {
+ "id": "d7332a17-018a-2558-d759-886806ac091f",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 43,
+ "width": 136,
+ "xmin": 1026,
+ "ymin": 183
+ },
+ "polygons": [
+ [
+ [1033, 213],
+ [1041, 218],
+ [1085, 216],
+ [1107, 224],
+ [1113, 224],
+ [1133, 216],
+ [1150, 213],
+ [1157, 198],
+ [1154, 192],
+ [1144, 185],
+ [1115, 185],
+ [1113, 187],
+ [1089, 185],
+ [1070, 200],
+ [1037, 202],
+ [1033, 207]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "1ffaa2e9-4a52-083a-1f90-00964d74247d",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1026&y=154&width=1162&height=255&uuid=bc4222b3-d65e-4c38-a7d3-3f6886607e33"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "437b7a70-311c-6ac9-4311-d80f363a468e",
+ "id": "25ac94fe-126f-2614-25c6-368115490a53",
+ "image_region": {
+ "id": "61d2ce60-c1f8-bde9-61b8-6c1fc6de91ae",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 38,
+ "width": 169,
+ "xmin": 368,
+ "ymin": 397
+ },
+ "polygons": [
+ [
+ [376, 411],
+ [378, 418],
+ [389, 427],
+ [415, 433],
+ [458, 433],
+ [482, 427],
+ [517, 427],
+ [524, 425],
+ [530, 418],
+ [530, 411],
+ [526, 405],
+ [511, 401],
+ [463, 409],
+ [461, 407],
+ [432, 405],
+ [413, 398],
+ [389, 398],
+ [380, 403]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "cc7caa83-c7a7-375f-cc16-08fcc0811b18",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=368&y=353&width=537&height=479&uuid=e5054412-4407-4e4d-86cf-8c7d4720caf7"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "80bac861-01b6-9381-80d0-6a1e0690bfc6",
+ "id": "78340423-5a5d-6f33-785e-a65c5d7b4374",
+ "image_region": {
+ "id": "c3cec615-59af-c49b-c3a4-646a5e89e8dc",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 231,
+ "width": 315,
+ "xmin": 216,
+ "ymin": 506
+ },
+ "polygons": [
+ [
+ [503, 550],
+ [498, 544],
+ [496, 543],
+ [488, 542],
+ [487, 541],
+ [477, 542],
+ [475, 544],
+ [468, 537],
+ [468, 536],
+ [466, 534],
+ [465, 532],
+ [465, 529],
+ [464, 528],
+ [464, 525],
+ [461, 522],
+ [460, 518],
+ [451, 509],
+ [449, 509],
+ [443, 506],
+ [436, 506],
+ [434, 507],
+ [432, 509],
+ [424, 526],
+ [414, 536],
+ [402, 542],
+ [397, 547],
+ [386, 551],
+ [381, 556],
+ [378, 556],
+ [374, 558],
+ [368, 563],
+ [364, 565],
+ [361, 568],
+ [355, 570],
+ [351, 574],
+ [347, 575],
+ [341, 578],
+ [337, 582],
+ [335, 583],
+ [332, 583],
+ [331, 584],
+ [328, 584],
+ [324, 586],
+ [321, 586],
+ [317, 588],
+ [314, 591],
+ [312, 592],
+ [309, 592],
+ [301, 596],
+ [294, 603],
+ [292, 609],
+ [284, 618],
+ [282, 622],
+ [282, 624],
+ [280, 626],
+ [280, 627],
+ [273, 634],
+ [271, 638],
+ [269, 647],
+ [267, 649],
+ [267, 652],
+ [263, 659],
+ [263, 668],
+ [264, 670],
+ [273, 679],
+ [275, 680],
+ [283, 679],
+ [285, 677],
+ [292, 677],
+ [293, 678],
+ [299, 679],
+ [304, 684],
+ [304, 687],
+ [302, 689],
+ [301, 693],
+ [297, 697],
+ [291, 700],
+ [285, 706],
+ [281, 707],
+ [273, 715],
+ [271, 716],
+ [268, 716],
+ [266, 718],
+ [257, 720],
+ [254, 723],
+ [246, 724],
+ [245, 725],
+ [242, 725],
+ [241, 726],
+ [228, 726],
+ [227, 727],
+ [217, 727],
+ [216, 728],
+ [216, 730],
+ [218, 732],
+ [234, 732],
+ [235, 733],
+ [240, 733],
+ [244, 735],
+ [247, 735],
+ [250, 737],
+ [255, 737],
+ [256, 736],
+ [260, 736],
+ [261, 735],
+ [266, 735],
+ [268, 733],
+ [276, 731],
+ [281, 726],
+ [285, 725],
+ [286, 724],
+ [288, 724],
+ [295, 717],
+ [299, 715],
+ [301, 715],
+ [308, 708],
+ [312, 707],
+ [321, 698],
+ [324, 697],
+ [326, 695],
+ [327, 692],
+ [338, 681],
+ [340, 676],
+ [350, 666],
+ [350, 664],
+ [352, 661],
+ [353, 657],
+ [355, 656],
+ [365, 646],
+ [370, 644],
+ [372, 642],
+ [380, 638],
+ [383, 638],
+ [386, 636],
+ [389, 636],
+ [390, 635],
+ [393, 635],
+ [394, 634],
+ [397, 634],
+ [398, 633],
+ [402, 633],
+ [404, 631],
+ [407, 631],
+ [410, 629],
+ [413, 629],
+ [414, 628],
+ [419, 628],
+ [420, 627],
+ [424, 627],
+ [425, 626],
+ [430, 626],
+ [439, 621],
+ [443, 621],
+ [444, 620],
+ [447, 620],
+ [448, 619],
+ [452, 619],
+ [453, 618],
+ [462, 617],
+ [463, 616],
+ [466, 616],
+ [467, 615],
+ [470, 615],
+ [471, 614],
+ [474, 614],
+ [475, 613],
+ [482, 613],
+ [483, 612],
+ [487, 612],
+ [488, 613],
+ [492, 613],
+ [493, 614],
+ [496, 614],
+ [497, 615],
+ [500, 615],
+ [501, 616],
+ [503, 616],
+ [504, 617],
+ [508, 617],
+ [509, 618],
+ [523, 618],
+ [524, 617],
+ [527, 617],
+ [531, 614],
+ [529, 611],
+ [529, 606],
+ [526, 602],
+ [526, 601],
+ [513, 588],
+ [513, 585],
+ [512, 584],
+ [512, 576],
+ [511, 575],
+ [511, 570],
+ [506, 561],
+ [505, 554]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of damage view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "5a8a4569-ae52-aeb3-5ae0-e716a97482f4",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=216&y=504&width=531&height=739&uuid=72c9a923-d181-4584-8d29-f6290a41d140"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "535c6a2a-f1f3-f91b-5336-c855f6d5d55c",
+ "id": "667da482-f302-4e5d-6617-06fdf424621a",
+ "image_region": {
+ "id": "d9fa80fc-f40d-a5cb-d990-2283f32b898c",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 9,
+ "width": 23,
+ "xmin": 1290,
+ "ymin": 287
+ },
+ "polygons": [
+ [
+ [1291, 287],
+ [1291, 294],
+ [1298, 296],
+ [1313, 294],
+ [1311, 287]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "2760d5fe-df9e-bb67-270a-7781d8b89720",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1290&y=283&width=1313&height=300&uuid=79754efd-a945-4c92-b1b2-48b1ab05b845"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a",
+ "id": "3bdc7c4e-75cc-4386-3bb6-de3172ea6fc1",
+ "image_region": {
+ "id": "74ae4157-385a-4d1a-74c4-e3283f7c615d",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 59,
+ "width": 138,
+ "xmin": 978,
+ "ymin": 215
+ },
+ "polygons": [
+ [
+ [985, 224],
+ [987, 233],
+ [1000, 246],
+ [1033, 272],
+ [1041, 272],
+ [1054, 266],
+ [1076, 266],
+ [1087, 259],
+ [1087, 255],
+ [1094, 248],
+ [1100, 246],
+ [1111, 235],
+ [1109, 226],
+ [1085, 218],
+ [1041, 220],
+ [1026, 224],
+ [1020, 224],
+ [1004, 218],
+ [993, 218]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "1567aac5-c8e4-7584-150d-08bacfc259c3",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=978&y=193&width=1116&height=296&uuid=86b3e176-a99d-4e8e-80e4-18aaad8682df"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "0f6665a0-47fe-6e8e-0f0c-c7df40d842c9",
+ "id": "47ba5f70-96de-6fad-47d0-fd0f91f843ea",
+ "image_region": {
+ "id": "b72b8933-0384-c224-b741-2b4c04a2ee63",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 26,
+ "width": 19,
+ "xmin": 1517,
+ "ymin": 380
+ },
+ "polygons": [
+ [
+ [1526, 381],
+ [1518, 388],
+ [1518, 398],
+ [1529, 405],
+ [1535, 396],
+ [1535, 390]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "1df49927-6747-6719-1d9e-3b5860614b5e",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1510&y=380&width=1543&height=406&uuid=234a6c06-8755-4250-a3a7-5e572485908d"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "a6b41fba-df6f-2d6f-a6de-bdc5d8490128",
+ "id": "d838da37-94f5-586a-d852-784893d3742d",
+ "image_region": {
+ "id": "79f5b017-68a2-340b-799f-12686f84184c",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 433,
+ "width": 468,
+ "xmin": 839,
+ "ymin": 588
+ },
+ "polygons": [
+ [
+ [928, 647],
+ [889, 688],
+ [870, 723],
+ [861, 747],
+ [861, 792],
+ [870, 821],
+ [872, 845],
+ [909, 912],
+ [924, 930],
+ [946, 949],
+ [985, 973],
+ [1037, 993],
+ [1074, 1001],
+ [1120, 1001],
+ [1122, 999],
+ [1146, 997],
+ [1194, 980],
+ [1218, 960],
+ [1241, 936],
+ [1263, 908],
+ [1278, 871],
+ [1287, 825],
+ [1287, 792],
+ [1278, 775],
+ [1270, 747],
+ [1252, 721],
+ [1244, 712],
+ [1231, 688],
+ [1189, 649],
+ [1170, 636],
+ [1148, 625],
+ [1122, 616],
+ [1087, 612],
+ [1085, 610],
+ [1037, 607],
+ [1035, 610],
+ [1022, 610],
+ [989, 616],
+ [952, 631]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "b69e0f13-edf3-0474-b6f4-ad6cead52833",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=785&y=588&width=1361&height=1021&uuid=12d45d89-b837-4823-8e9d-82bb96027530"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "bf94b068-56d5-7a8a-bffe-121751f356cd",
+ "id": "f5adeae9-5e35-ec2d-f5c7-48965913c06a",
+ "image_region": {
+ "id": "77ae40e0-e9da-fd44-77c4-e29feefcd103",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 596,
+ "width": 366,
+ "xmin": 1355,
+ "ymin": 345
+ },
+ "polygons": [
+ [
+ [1685, 398],
+ [1681, 398],
+ [1666, 409],
+ [1659, 409],
+ [1635, 422],
+ [1620, 422],
+ [1616, 414],
+ [1631, 398],
+ [1637, 396],
+ [1644, 390],
+ [1631, 377],
+ [1624, 379],
+ [1609, 372],
+ [1598, 372],
+ [1576, 383],
+ [1563, 394],
+ [1559, 394],
+ [1548, 405],
+ [1561, 414],
+ [1576, 416],
+ [1589, 425],
+ [1589, 431],
+ [1583, 446],
+ [1563, 457],
+ [1542, 464],
+ [1524, 475],
+ [1526, 477],
+ [1537, 477],
+ [1559, 472],
+ [1568, 475],
+ [1576, 481],
+ [1579, 490],
+ [1574, 494],
+ [1548, 483],
+ [1535, 488],
+ [1535, 496],
+ [1555, 514],
+ [1552, 520],
+ [1546, 520],
+ [1529, 503],
+ [1518, 503],
+ [1502, 509],
+ [1465, 507],
+ [1457, 509],
+ [1452, 514],
+ [1452, 522],
+ [1479, 544],
+ [1492, 549],
+ [1502, 549],
+ [1509, 557],
+ [1505, 566],
+ [1498, 570],
+ [1474, 568],
+ [1463, 573],
+ [1457, 579],
+ [1457, 583],
+ [1463, 588],
+ [1470, 586],
+ [1500, 588],
+ [1515, 592],
+ [1539, 605],
+ [1552, 601],
+ [1563, 581],
+ [1576, 601],
+ [1574, 627],
+ [1581, 642],
+ [1589, 649],
+ [1607, 653],
+ [1633, 638],
+ [1642, 638],
+ [1650, 651],
+ [1650, 660],
+ [1635, 673],
+ [1624, 675],
+ [1622, 673],
+ [1605, 673],
+ [1583, 684],
+ [1566, 697],
+ [1555, 699],
+ [1546, 697],
+ [1529, 686],
+ [1513, 690],
+ [1507, 699],
+ [1526, 718],
+ [1526, 729],
+ [1515, 745],
+ [1515, 751],
+ [1520, 758],
+ [1552, 755],
+ [1572, 768],
+ [1583, 779],
+ [1585, 786],
+ [1583, 792],
+ [1570, 801],
+ [1561, 803],
+ [1555, 810],
+ [1537, 803],
+ [1529, 803],
+ [1524, 810],
+ [1522, 821],
+ [1522, 836],
+ [1526, 845],
+ [1524, 851],
+ [1513, 860],
+ [1457, 858],
+ [1448, 862],
+ [1435, 875],
+ [1431, 884],
+ [1405, 908],
+ [1396, 912],
+ [1387, 912],
+ [1372, 906],
+ [1372, 910],
+ [1378, 914],
+ [1394, 914],
+ [1424, 906],
+ [1448, 895],
+ [1452, 890],
+ [1476, 882],
+ [1513, 860],
+ [1561, 845],
+ [1594, 827],
+ [1609, 814],
+ [1633, 786],
+ [1637, 777],
+ [1672, 745],
+ [1689, 721],
+ [1703, 692],
+ [1703, 666],
+ [1694, 644],
+ [1694, 607],
+ [1700, 597],
+ [1700, 583],
+ [1692, 560],
+ [1692, 546],
+ [1694, 544],
+ [1692, 522],
+ [1694, 520],
+ [1694, 483],
+ [1698, 466],
+ [1703, 459],
+ [1703, 446],
+ [1705, 444],
+ [1705, 420],
+ [1694, 405]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "fc3a666a-8ea9-f60e-fc50-c415898fda49",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1141&y=345&width=1935&height=941&uuid=43c2d2b3-a647-44f5-baa5-eb1553504971"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac",
+ "id": "3179a7a6-8703-f8dc-3113-05d98025d49b",
+ "image_region": {
+ "id": "0ddd377c-7244-154a-0db7-95037562390d",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 29,
+ "width": 14,
+ "xmin": 731,
+ "ymin": 797
+ },
+ "polygons": [
+ [
+ [736, 819],
+ [734, 820],
+ [731, 820],
+ [731, 825],
+ [732, 826],
+ [736, 826],
+ [738, 824],
+ [738, 821]
+ ],
+ [
+ [731, 798],
+ [732, 802],
+ [737, 807],
+ [741, 809],
+ [745, 807],
+ [745, 801],
+ [741, 797],
+ [732, 797]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of damage view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "9d797036-7e3b-de8f-9d13-d249791df2c8",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=719&y=797&width=757&height=826&uuid=bcd1e0e8-c6bd-4ea6-addc-746853672836"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "ce9382ec-d9f3-17df-cef9-2093ded53b98",
+ "id": "72d12f4f-8de3-2ed7-72bb-8d308ac50290",
+ "image_region": {
+ "id": "5d8d0505-7557-1643-5de7-a77a72713a04",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 725,
+ "width": 1232,
+ "xmin": 463,
+ "ymin": 102
+ },
+ "polygons": [
+ [
+ [519, 760],
+ [717, 766],
+ [761, 775],
+ [776, 782],
+ [791, 795],
+ [789, 784],
+ [793, 773],
+ [793, 745],
+ [806, 677],
+ [815, 651],
+ [826, 629],
+ [843, 605],
+ [876, 573],
+ [900, 555],
+ [930, 540],
+ [974, 525],
+ [1000, 522],
+ [1015, 518],
+ [1076, 518],
+ [1091, 522],
+ [1109, 522],
+ [1128, 527],
+ [1181, 544],
+ [1237, 573],
+ [1272, 601],
+ [1285, 605],
+ [1372, 536],
+ [1398, 518],
+ [1450, 494],
+ [1494, 481],
+ [1518, 477],
+ [1542, 462],
+ [1563, 455],
+ [1581, 446],
+ [1587, 431],
+ [1587, 425],
+ [1576, 418],
+ [1561, 416],
+ [1546, 405],
+ [1561, 388],
+ [1566, 388],
+ [1598, 370],
+ [1609, 370],
+ [1624, 377],
+ [1631, 374],
+ [1626, 359],
+ [1620, 353],
+ [1618, 346],
+ [1631, 342],
+ [1633, 335],
+ [1639, 331],
+ [1629, 318],
+ [1620, 314],
+ [1594, 309],
+ [1572, 300],
+ [1561, 298],
+ [1520, 300],
+ [1494, 276],
+ [1483, 272],
+ [1459, 270],
+ [1457, 274],
+ [1446, 281],
+ [1433, 281],
+ [1426, 274],
+ [1426, 270],
+ [1437, 259],
+ [1428, 253],
+ [1415, 255],
+ [1413, 266],
+ [1392, 287],
+ [1361, 305],
+ [1355, 305],
+ [1339, 311],
+ [1322, 311],
+ [1300, 316],
+ [1272, 316],
+ [1259, 311],
+ [1248, 311],
+ [1235, 296],
+ [1235, 281],
+ [1231, 274],
+ [1222, 274],
+ [1211, 281],
+ [1207, 287],
+ [1209, 294],
+ [1207, 303],
+ [1100, 303],
+ [1061, 292],
+ [1044, 292],
+ [1020, 285],
+ [998, 274],
+ [965, 248],
+ [948, 242],
+ [785, 226],
+ [765, 213],
+ [709, 161],
+ [667, 139],
+ [643, 135],
+ [639, 144],
+ [659, 144],
+ [680, 152],
+ [713, 174],
+ [730, 194],
+ [743, 202],
+ [765, 224],
+ [778, 244],
+ [787, 263],
+ [787, 276],
+ [774, 285],
+ [743, 285],
+ [715, 279],
+ [702, 279],
+ [669, 285],
+ [643, 285],
+ [641, 287],
+ [600, 281],
+ [598, 283],
+ [585, 283],
+ [578, 287],
+ [578, 292],
+ [582, 292],
+ [595, 305],
+ [600, 320],
+ [595, 385],
+ [593, 388],
+ [591, 418],
+ [580, 468],
+ [578, 520],
+ [574, 536],
+ [572, 564],
+ [561, 605],
+ [561, 649],
+ [556, 657],
+ [545, 703],
+ [535, 723],
+ [528, 747]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "23585679-dd88-b9fc-2332-f406daae95bb",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=463&y=3&width=1695&height=926&uuid=951e4ca7-78fe-4c88-b2da-ad66ea5a19a4"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3",
+ "id": "260594c3-2b62-74c0-266f-36bc2c445887",
+ "image_region": {
+ "id": "53358a7f-f12b-f039-535f-2800f60ddc7e",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 19,
+ "width": 16,
+ "xmin": 1275,
+ "ymin": 278
+ },
+ "polygons": [
+ [
+ [1283, 279],
+ [1278, 283],
+ [1276, 292],
+ [1281, 296],
+ [1287, 296],
+ [1291, 285],
+ [1289, 281]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "f958525f-3602-3369-f932-f02031241f2e",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1271&y=278&width=1295&height=297&uuid=62e1711e-2119-4a1b-9399-ed26fcf67f58"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "640b0825-60c5-887d-6461-aa5a67e3a43a",
+ "id": "858717fe-a458-fbb3-85ed-b581a37ed7f4",
+ "image_region": {
+ "id": "5cfad77b-c11e-364e-5c90-7504c6381a09",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 478,
+ "width": 397,
+ "xmin": 1269,
+ "ymin": 453
+ },
+ "polygons": [
+ [
+ [1568, 477],
+ [1550, 475],
+ [1537, 479],
+ [1520, 477],
+ [1450, 496],
+ [1420, 512],
+ [1415, 512],
+ [1381, 531],
+ [1287, 605],
+ [1296, 623],
+ [1318, 644],
+ [1341, 679],
+ [1355, 708],
+ [1359, 712],
+ [1370, 745],
+ [1378, 760],
+ [1378, 779],
+ [1385, 801],
+ [1385, 823],
+ [1378, 843],
+ [1378, 880],
+ [1372, 899],
+ [1374, 906],
+ [1387, 910],
+ [1396, 910],
+ [1405, 906],
+ [1428, 884],
+ [1433, 875],
+ [1448, 860],
+ [1457, 856],
+ [1492, 856],
+ [1494, 858],
+ [1513, 858],
+ [1518, 856],
+ [1524, 845],
+ [1520, 836],
+ [1520, 821],
+ [1522, 810],
+ [1529, 801],
+ [1537, 801],
+ [1555, 808],
+ [1561, 801],
+ [1581, 792],
+ [1583, 786],
+ [1581, 779],
+ [1552, 758],
+ [1520, 760],
+ [1515, 755],
+ [1513, 745],
+ [1524, 729],
+ [1524, 718],
+ [1505, 699],
+ [1513, 688],
+ [1529, 684],
+ [1546, 694],
+ [1555, 697],
+ [1566, 694],
+ [1583, 681],
+ [1605, 671],
+ [1622, 671],
+ [1624, 673],
+ [1635, 671],
+ [1648, 660],
+ [1648, 651],
+ [1642, 640],
+ [1633, 640],
+ [1607, 655],
+ [1589, 651],
+ [1581, 644],
+ [1574, 636],
+ [1572, 627],
+ [1574, 601],
+ [1563, 583],
+ [1555, 601],
+ [1539, 607],
+ [1509, 592],
+ [1487, 588],
+ [1463, 590],
+ [1455, 583],
+ [1455, 579],
+ [1468, 568],
+ [1474, 566],
+ [1498, 568],
+ [1507, 560],
+ [1502, 551],
+ [1492, 551],
+ [1479, 546],
+ [1450, 522],
+ [1450, 514],
+ [1457, 507],
+ [1465, 505],
+ [1502, 507],
+ [1518, 501],
+ [1529, 501],
+ [1537, 507],
+ [1526, 496],
+ [1515, 492],
+ [1529, 483],
+ [1542, 479],
+ [1561, 479],
+ [1570, 485],
+ [1574, 485],
+ [1574, 481]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "707c5d25-0878-8683-7016-ff5a0f5eaac4",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1149&y=453&width=1786&height=931&uuid=6db3ece5-ef73-446e-b224-8fa0abb038ee"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "56c74ad5-c077-1d08-56ad-e8aac751314f",
+ "id": "461084a6-d7e8-4430-467a-26d9d0ce6877",
+ "image_region": {
+ "id": "54fe0ad2-e714-e3ab-5494-a8ade032cfec",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 155,
+ "width": 847,
+ "xmin": 0,
+ "ymin": 740
+ },
+ "polygons": [
+ [
+ [0, 762],
+ [0, 766],
+ [6, 771],
+ [21, 771],
+ [23, 773],
+ [50, 773],
+ [52, 775],
+ [87, 777],
+ [102, 782],
+ [174, 786],
+ [176, 788],
+ [204, 788],
+ [219, 792],
+ [271, 795],
+ [274, 797],
+ [302, 799],
+ [334, 808],
+ [337, 812],
+ [350, 812],
+ [367, 816],
+ [465, 819],
+ [467, 823],
+ [548, 825],
+ [574, 832],
+ [589, 832],
+ [611, 838],
+ [632, 840],
+ [641, 845],
+ [661, 847],
+ [667, 851],
+ [687, 853],
+ [743, 871],
+ [750, 875],
+ [756, 875],
+ [789, 888],
+ [804, 886],
+ [806, 882],
+ [806, 866],
+ [791, 797],
+ [776, 784],
+ [761, 777],
+ [717, 768],
+ [558, 764],
+ [556, 762],
+ [498, 762],
+ [495, 760],
+ [437, 760],
+ [435, 758],
+ [378, 758],
+ [376, 755],
+ [317, 755],
+ [315, 753],
+ [256, 753],
+ [254, 751],
+ [195, 751],
+ [193, 749],
+ [134, 749],
+ [132, 747],
+ [54, 747],
+ [52, 749],
+ [30, 751],
+ [34, 755],
+ [26, 762],
+ [10, 755]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "123a4495-771b-8a5d-1250-e6ea703da61a",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=500&width=847&height=1135&uuid=898411ea-6207-4837-9d0e-b9c9498c27be"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "3ed12b36-252c-98ed-3ebb-8949220ab4aa",
+ "id": "8eab6f21-2c6b-59a7-8ec1-cd5e2b4d75e0",
+ "image_region": {
+ "id": "605778f4-e70d-3ad5-603d-da8be02b1692",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 522,
+ "width": 569,
+ "xmin": 800,
+ "ymin": 529
+ },
+ "polygons": [
+ [
+ [865, 638],
+ [843, 677],
+ [830, 714],
+ [828, 766],
+ [826, 768],
+ [826, 827],
+ [843, 871],
+ [865, 906],
+ [920, 967],
+ [948, 984],
+ [972, 1006],
+ [985, 1012],
+ [991, 1012],
+ [1011, 1021],
+ [1026, 1023],
+ [1035, 1028],
+ [1083, 1032],
+ [1100, 1036],
+ [1122, 1036],
+ [1146, 1030],
+ [1157, 1030],
+ [1178, 1017],
+ [1185, 1017],
+ [1209, 1004],
+ [1237, 999],
+ [1254, 993],
+ [1263, 982],
+ [1287, 967],
+ [1313, 936],
+ [1322, 921],
+ [1324, 897],
+ [1344, 864],
+ [1344, 851],
+ [1339, 836],
+ [1337, 775],
+ [1328, 738],
+ [1315, 705],
+ [1315, 697],
+ [1320, 686],
+ [1318, 677],
+ [1302, 671],
+ [1283, 653],
+ [1274, 634],
+ [1257, 623],
+ [1237, 605],
+ [1183, 579],
+ [1146, 568],
+ [1137, 568],
+ [1096, 553],
+ [1020, 555],
+ [1017, 557],
+ [1000, 557],
+ [998, 560],
+ [976, 562],
+ [924, 586],
+ [896, 607]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "5ed2f756-0753-6abc-5eb8-5529007546fb",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=737&y=529&width=1432&height=1051&uuid=5b79a55e-69d7-4343-a3ce-8fd512d5058f"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "f78b2228-70ce-da31-f7e1-805777e8f676",
+ "id": "71dbdd16-1c49-864a-71b1-7f691b6faa0d",
+ "image_region": {
+ "id": "26d25157-5182-f302-26b8-f32856a4df45",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 47,
+ "width": 95,
+ "xmin": 1085,
+ "ymin": 213
+ },
+ "polygons": [
+ [
+ [1174, 235],
+ [1161, 229],
+ [1150, 216],
+ [1141, 216],
+ [1111, 226],
+ [1113, 235],
+ [1100, 248],
+ [1094, 250],
+ [1089, 255],
+ [1089, 259],
+ [1109, 255],
+ [1122, 255],
+ [1124, 257],
+ [1126, 255],
+ [1146, 255],
+ [1154, 259],
+ [1167, 253],
+ [1176, 244]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "c5b13d52-feef-1524-c5db-9f2df9c93963",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1085&y=201&width=1180&height=272&uuid=bd9cdd26-6ac4-42c0-8af7-012cc61460fc"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "68d766cb-9bbf-c196-68bd-c4b49c99edd1",
+ "id": "8074a493-4434-7b65-801e-06ec43125722",
+ "image_region": {
+ "id": "18534349-8753-18d6-1839-e13680753491",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 172,
+ "width": 244,
+ "xmin": 552,
+ "ymin": 125
+ },
+ "polygons": [
+ [
+ [563, 281],
+ [572, 290],
+ [576, 290],
+ [585, 281],
+ [598, 281],
+ [600, 279],
+ [632, 285],
+ [669, 283],
+ [702, 276],
+ [715, 276],
+ [743, 283],
+ [774, 283],
+ [785, 276],
+ [785, 263],
+ [776, 244],
+ [763, 224],
+ [743, 205],
+ [730, 196],
+ [713, 176],
+ [680, 155],
+ [665, 148],
+ [639, 146],
+ [637, 144],
+ [641, 135],
+ [628, 133],
+ [626, 155],
+ [602, 183],
+ [602, 187],
+ [585, 220],
+ [582, 233],
+ [565, 263]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "311115d4-789e-625e-317b-b7ab7fb84e19",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=120&width=796&height=302&uuid=2f0c7d32-1f45-466f-83a6-40362240c624"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "2d917d5d-6636-5ea7-2dfb-df22611072e0",
+ "id": "439bd668-c016-75e3-43f1-7417c73059a4",
+ "image_region": {
+ "id": "df5893b6-895d-e82f-df32-31c98e7bc468",
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 718,
+ "width": 657,
+ "xmin": 0,
+ "ymin": 74
+ },
+ "polygons": [
+ [
+ [626, 131],
+ [606, 124],
+ [569, 122],
+ [567, 120],
+ [539, 120],
+ [508, 113],
+ [471, 111],
+ [456, 107],
+ [363, 109],
+ [334, 115],
+ [317, 115],
+ [287, 122],
+ [271, 122],
+ [269, 124],
+ [197, 128],
+ [171, 139],
+ [158, 152],
+ [132, 161],
+ [115, 174],
+ [82, 192],
+ [52, 220],
+ [23, 235],
+ [8, 248],
+ [2, 244],
+ [0, 246],
+ [0, 760],
+ [8, 753],
+ [19, 755],
+ [26, 760],
+ [32, 755],
+ [28, 751],
+ [30, 749],
+ [52, 747],
+ [54, 745],
+ [132, 745],
+ [134, 747],
+ [193, 747],
+ [195, 749],
+ [254, 749],
+ [256, 751],
+ [267, 751],
+ [269, 742],
+ [282, 731],
+ [313, 714],
+ [328, 710],
+ [337, 712],
+ [341, 718],
+ [343, 753],
+ [495, 758],
+ [504, 760],
+ [532, 714],
+ [541, 697],
+ [548, 675],
+ [550, 653],
+ [558, 629],
+ [561, 592],
+ [567, 577],
+ [567, 566],
+ [569, 564],
+ [572, 536],
+ [576, 520],
+ [578, 468],
+ [589, 418],
+ [591, 388],
+ [593, 385],
+ [593, 368],
+ [598, 346],
+ [595, 311],
+ [593, 305],
+ [582, 294],
+ [572, 292],
+ [563, 285],
+ [561, 272],
+ [576, 237],
+ [580, 233],
+ [582, 220],
+ [600, 187],
+ [600, 183],
+ [624, 155]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "8d051e26-285e-30e7-8d6f-bc592f781ca0",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=74&width=807&height=792&uuid=ef876343-5944-443f-b0ef-19e2a5b21dfb"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "additional_data": {
+ "category": "exterior",
+ "sight_id": "ffocus18-3TiCVAaN",
+ "label": {
+ "de": "Motorhaube",
+ "en": "Hood",
+ "fr": "Capot"
+ }
+ },
+ "binary_size": 160207,
+ "compliances": {
+ "compliance_issues": ["low_resolution"],
+ "compliance_status": "non_compliant",
+ "coverage_analysis": {
+ "compliance_issues": [],
+ "is_compliant_with_sight": true,
+ "probability_by_viewpoint": {
+ "front_left": 1.0
+ }
+ },
+ "image_analysis": {
+ "binary_size": 160207,
+ "blurriness": 0.28681227564811707,
+ "image_height": 1920,
+ "image_width": 1080,
+ "lens_flare": 0.22103191912174225,
+ "orientation": "landscape",
+ "overexposure": 0.11703607439994812,
+ "predicted_image_type": "unknown",
+ "underexposure": 0.021420717239379883,
+ "zoom": 0.7965673208236694
+ },
+ "should_retake": true,
+ "vehicle_analysis": {
+ "dirtiness": 0.22652102261781693,
+ "is_vehicle_present": true,
+ "ratio_of_vehicle_to_image_pixels": 0.4923013117283951,
+ "reflections": 0.5470188856124878,
+ "snowness": 0.09955208003520966,
+ "vehicle_type": "unknown",
+ "wetness": 0.44983914494514465
+ }
+ },
+ "has_vehicle": true,
+ "id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "image_height": 1080,
+ "image_type": "beauty_shot",
+ "image_width": 1920,
+ "mimetype": "image/jpeg",
+ "name": "hood.jpeg",
+ "object_type": "IMAGE",
+ "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg?generation=1702474029549060&alt=media",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "rendering of detected parts"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "d14655ab-f662-3898-d12c-f7d4f14414df",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.690063_21731085-0378-40c7-b729-17a56c6aee55.jpg"
+ },
+ {
+ "additional_data": {
+ "description": "rendering of detected damages"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "ffee30b3-6d49-58ea-ff84-92cc6a6f74ad",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.709410_5f163233-6d20-4990-bd54-0cf001b052bc.jpg"
+ }
+ ],
+ "viewpoint": {
+ "confidence": 0.4582550525665283,
+ "prediction": "front_left"
+ },
+ "views": [
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "4d9cb158-d7ed-c73e-4df6-1327d0cbeb79",
+ "id": "66937b3f-dc7a-f700-66f9-d940db5cdb47",
+ "image_region": {
+ "id": "e3717de5-9696-359d-e31b-df9a91b019da",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 36,
+ "width": 269,
+ "xmin": 947,
+ "ymin": 417
+ },
+ "polygons": [
+ [
+ [959, 425],
+ [978, 432],
+ [994, 432],
+ [996, 433],
+ [1013, 433],
+ [1015, 435],
+ [1115, 438],
+ [1139, 447],
+ [1145, 447],
+ [1162, 452],
+ [1186, 452],
+ [1199, 448],
+ [1204, 445],
+ [1197, 438],
+ [1191, 438],
+ [1170, 432],
+ [1162, 432],
+ [1132, 425],
+ [1108, 425],
+ [1107, 423],
+ [1065, 422],
+ [1063, 420],
+ [1045, 420],
+ [1043, 418],
+ [1006, 420],
+ [1005, 422],
+ [976, 422]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "337b4153-f26e-59f9-3311-e32cf54875be",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=947&y=335&width=1216&height=535&uuid=fb9e798e-dd63-4bc8-ba76-70c565f93cea"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "fff6bd07-f76a-1597-ff9c-1f78f04c39d0",
+ "id": "058c0533-0a74-dcb3-05e6-a74c0d52f0f4",
+ "image_region": {
+ "id": "fcf7022e-de54-0da3-fc9d-a051d97221e4",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 259,
+ "width": 119,
+ "xmin": 1220,
+ "ymin": 154
+ },
+ "polygons": [
+ [
+ [1227, 167],
+ [1226, 170],
+ [1231, 177],
+ [1237, 202],
+ [1259, 241],
+ [1266, 262],
+ [1284, 306],
+ [1284, 311],
+ [1293, 326],
+ [1299, 333],
+ [1306, 351],
+ [1333, 401],
+ [1335, 400],
+ [1331, 385],
+ [1333, 360],
+ [1330, 355],
+ [1331, 323],
+ [1299, 274],
+ [1293, 259],
+ [1269, 227],
+ [1249, 184],
+ [1234, 165]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "325ba3da-5e3e-40d0-3231-01a559186c97",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1107&y=154&width=1452&height=413&uuid=c6142566-5af5-49f3-bfe3-56cc3e79b57e"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "299b478c-4091-0165-29f1-e5f347b72d22",
+ "id": "740607eb-7996-67f0-746c-a5947eb04bb7",
+ "image_region": {
+ "id": "f2345957-6e6b-99d7-f25e-fb28694db590",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 618,
+ "width": 971,
+ "xmin": 441,
+ "ymin": 367
+ },
+ "polygons": [
+ [
+ [524, 425],
+ [522, 442],
+ [520, 443],
+ [520, 462],
+ [517, 475],
+ [503, 504],
+ [497, 524],
+ [495, 552],
+ [490, 572],
+ [490, 594],
+ [485, 606],
+ [485, 616],
+ [503, 636],
+ [510, 649],
+ [519, 683],
+ [519, 699],
+ [520, 701],
+ [522, 721],
+ [530, 755],
+ [547, 795],
+ [549, 807],
+ [560, 818],
+ [567, 838],
+ [577, 857],
+ [602, 889],
+ [622, 907],
+ [653, 925],
+ [679, 932],
+ [691, 937],
+ [711, 939],
+ [726, 946],
+ [743, 946],
+ [755, 941],
+ [782, 942],
+ [795, 937],
+ [810, 937],
+ [832, 946],
+ [850, 946],
+ [870, 941],
+ [892, 942],
+ [894, 944],
+ [912, 944],
+ [936, 937],
+ [968, 937],
+ [969, 939],
+ [984, 939],
+ [1015, 949],
+ [1040, 951],
+ [1056, 957],
+ [1075, 956],
+ [1100, 944],
+ [1134, 946],
+ [1154, 937],
+ [1169, 936],
+ [1186, 924],
+ [1204, 915],
+ [1217, 912],
+ [1253, 894],
+ [1271, 874],
+ [1293, 828],
+ [1308, 812],
+ [1310, 803],
+ [1318, 788],
+ [1323, 771],
+ [1333, 755],
+ [1343, 718],
+ [1345, 699],
+ [1350, 686],
+ [1358, 648],
+ [1360, 619],
+ [1363, 609],
+ [1365, 586],
+ [1367, 584],
+ [1368, 530],
+ [1367, 529],
+ [1363, 482],
+ [1358, 468],
+ [1358, 462],
+ [1350, 432],
+ [1336, 410],
+ [1335, 417],
+ [1326, 425],
+ [1313, 432],
+ [1256, 448],
+ [1191, 455],
+ [1189, 457],
+ [1152, 460],
+ [1150, 462],
+ [1088, 463],
+ [1087, 465],
+ [1070, 465],
+ [1068, 467],
+ [986, 467],
+ [984, 468],
+ [902, 468],
+ [901, 467],
+ [867, 467],
+ [865, 465],
+ [827, 465],
+ [825, 463],
+ [778, 462],
+ [777, 460],
+ [725, 458],
+ [723, 457],
+ [708, 457],
+ [706, 455],
+ [693, 455],
+ [691, 453],
+ [678, 453],
+ [676, 452],
+ [644, 448],
+ [631, 442],
+ [626, 442],
+ [614, 437],
+ [574, 430],
+ [554, 422],
+ [549, 417],
+ [545, 405],
+ [549, 395],
+ [542, 400],
+ [537, 411]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "f85c1c27-7bec-9d6c-f836-be587ccab12b",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=441&y=312&width=1412&height=1040&uuid=d03546db-19bc-45d3-822c-0dfb77ded67e"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "0f6665a0-47fe-6e8e-0f0c-c7df40d842c9",
+ "id": "be3948a9-777a-be37-be53-ead6705c9270",
+ "image_region": {
+ "id": "cff65dc0-757d-278d-cf9c-ffbf725b0bca",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 524,
+ "width": 278,
+ "xmin": 291,
+ "ymin": 312
+ },
+ "polygons": [
+ [
+ [550, 336],
+ [535, 351],
+ [519, 378],
+ [507, 391],
+ [498, 406],
+ [493, 411],
+ [493, 415],
+ [455, 473],
+ [453, 480],
+ [436, 512],
+ [416, 534],
+ [408, 539],
+ [373, 572],
+ [363, 584],
+ [363, 592],
+ [358, 606],
+ [341, 616],
+ [333, 632],
+ [331, 641],
+ [326, 648],
+ [319, 666],
+ [319, 674],
+ [312, 689],
+ [309, 708],
+ [306, 715],
+ [304, 763],
+ [324, 783],
+ [334, 788],
+ [339, 788],
+ [346, 781],
+ [348, 763],
+ [349, 761],
+ [349, 746],
+ [363, 701],
+ [373, 679],
+ [383, 668],
+ [400, 663],
+ [420, 668],
+ [433, 674],
+ [468, 710],
+ [478, 726],
+ [487, 755],
+ [493, 768],
+ [500, 802],
+ [505, 812],
+ [508, 813],
+ [532, 813],
+ [547, 807],
+ [545, 795],
+ [529, 755],
+ [520, 721],
+ [519, 701],
+ [517, 699],
+ [517, 683],
+ [508, 649],
+ [502, 636],
+ [485, 619],
+ [483, 606],
+ [488, 594],
+ [488, 572],
+ [493, 552],
+ [495, 524],
+ [502, 504],
+ [515, 475],
+ [519, 462],
+ [519, 443],
+ [520, 442],
+ [522, 425],
+ [535, 411],
+ [540, 400],
+ [550, 391],
+ [555, 380],
+ [555, 356],
+ [557, 351]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "03a8e4a9-d417-c1fd-03c2-46d6d331edba",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=81&y=312&width=779&height=836&uuid=47a952b4-0931-4993-b993-bf589f0f20b4"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "3a4f083c-10fb-b483-3a25-aa4317dd98c4",
+ "id": "d64d13af-dd3c-cc9e-d627-b1d0da1ae0d9",
+ "image_region": {
+ "id": "3ac998f2-2c0e-736c-3aa3-3a8d2b285f2b",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 344,
+ "width": 866,
+ "xmin": 508,
+ "ymin": 138
+ },
+ "polygons": [
+ [
+ [547, 405],
+ [550, 417],
+ [564, 425],
+ [587, 432],
+ [599, 432],
+ [614, 435],
+ [626, 440],
+ [631, 440],
+ [644, 447],
+ [663, 448],
+ [658, 448],
+ [641, 442],
+ [627, 428],
+ [601, 427],
+ [599, 425],
+ [601, 417],
+ [624, 415],
+ [638, 410],
+ [644, 410],
+ [646, 411],
+ [691, 417],
+ [721, 423],
+ [733, 423],
+ [738, 427],
+ [740, 432],
+ [751, 443],
+ [748, 448],
+ [741, 450],
+ [666, 450],
+ [723, 455],
+ [725, 457],
+ [777, 458],
+ [778, 460],
+ [825, 462],
+ [827, 463],
+ [901, 465],
+ [902, 467],
+ [1068, 465],
+ [1070, 463],
+ [1087, 463],
+ [1088, 462],
+ [1150, 460],
+ [1152, 458],
+ [1179, 457],
+ [1204, 452],
+ [1232, 450],
+ [1234, 448],
+ [1256, 447],
+ [1305, 433],
+ [1326, 423],
+ [1333, 417],
+ [1335, 411],
+ [1335, 405],
+ [1328, 396],
+ [1315, 368],
+ [1311, 365],
+ [1298, 333],
+ [1291, 326],
+ [1283, 311],
+ [1283, 306],
+ [1264, 262],
+ [1258, 241],
+ [1236, 202],
+ [1229, 177],
+ [1224, 169],
+ [1172, 159],
+ [1124, 157],
+ [1122, 155],
+ [1080, 155],
+ [1078, 154],
+ [837, 154],
+ [835, 155],
+ [815, 155],
+ [814, 157],
+ [741, 157],
+ [740, 159],
+ [721, 159],
+ [720, 160],
+ [684, 160],
+ [668, 164],
+ [661, 169],
+ [638, 216],
+ [634, 219],
+ [629, 232],
+ [621, 244],
+ [604, 283],
+ [601, 286],
+ [591, 309],
+ [572, 343]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "4c2bf631-5213-7c6c-4c41-544e5535502b",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=508&y=0&width=1374&height=634&uuid=26a62e63-16f8-4861-90c5-eabc18a6adfa"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "bf94b068-56d5-7a8a-bffe-121751f356cd",
+ "id": "6805436b-c50b-756f-686f-e114c22d5928",
+ "image_region": {
+ "id": "ea5e1605-ba1b-9cf1-ea34-b47abd3db0b6",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 537,
+ "width": 1339,
+ "xmin": 266,
+ "ymin": 542
+ },
+ "polygons": [
+ [
+ [1563, 723],
+ [1559, 715],
+ [1551, 666],
+ [1548, 654],
+ [1537, 636],
+ [1536, 627],
+ [1516, 587],
+ [1497, 567],
+ [1489, 567],
+ [1497, 594],
+ [1502, 601],
+ [1506, 612],
+ [1526, 622],
+ [1534, 636],
+ [1531, 653],
+ [1516, 669],
+ [1514, 674],
+ [1516, 676],
+ [1524, 676],
+ [1534, 681],
+ [1554, 703],
+ [1554, 716],
+ [1549, 726],
+ [1549, 735],
+ [1553, 740],
+ [1553, 761],
+ [1548, 773],
+ [1534, 785],
+ [1522, 783],
+ [1519, 778],
+ [1506, 731],
+ [1506, 723],
+ [1501, 710],
+ [1502, 721],
+ [1506, 728],
+ [1507, 751],
+ [1509, 753],
+ [1507, 785],
+ [1506, 787],
+ [1507, 808],
+ [1506, 810],
+ [1504, 832],
+ [1497, 855],
+ [1497, 864],
+ [1492, 877],
+ [1470, 909],
+ [1454, 925],
+ [1444, 932],
+ [1422, 952],
+ [1393, 967],
+ [1375, 964],
+ [1358, 956],
+ [1345, 944],
+ [1338, 930],
+ [1336, 910],
+ [1343, 892],
+ [1345, 879],
+ [1353, 848],
+ [1351, 830],
+ [1350, 825],
+ [1323, 820],
+ [1313, 817],
+ [1310, 813],
+ [1294, 828],
+ [1273, 874],
+ [1248, 899],
+ [1217, 914],
+ [1196, 920],
+ [1169, 937],
+ [1154, 939],
+ [1134, 947],
+ [1100, 946],
+ [1075, 957],
+ [1056, 959],
+ [1040, 952],
+ [1015, 951],
+ [984, 941],
+ [969, 941],
+ [968, 939],
+ [936, 939],
+ [912, 946],
+ [894, 946],
+ [892, 944],
+ [870, 942],
+ [850, 947],
+ [832, 947],
+ [810, 939],
+ [795, 939],
+ [782, 944],
+ [755, 942],
+ [743, 947],
+ [726, 947],
+ [711, 941],
+ [691, 939],
+ [679, 934],
+ [668, 932],
+ [653, 927],
+ [627, 912],
+ [601, 889],
+ [571, 848],
+ [559, 818],
+ [547, 808],
+ [532, 815],
+ [508, 815],
+ [507, 813],
+ [505, 837],
+ [510, 859],
+ [510, 887],
+ [515, 907],
+ [515, 925],
+ [510, 939],
+ [497, 954],
+ [475, 969],
+ [458, 974],
+ [445, 969],
+ [428, 957],
+ [393, 922],
+ [384, 907],
+ [364, 880],
+ [354, 853],
+ [349, 827],
+ [348, 776],
+ [348, 781],
+ [339, 790],
+ [334, 790],
+ [324, 785],
+ [304, 765],
+ [299, 780],
+ [299, 792],
+ [297, 793],
+ [299, 875],
+ [302, 887],
+ [304, 919],
+ [311, 946],
+ [319, 962],
+ [319, 971],
+ [329, 996],
+ [333, 1014],
+ [338, 1024],
+ [338, 1038],
+ [349, 1078],
+ [1512, 1078],
+ [1517, 1059],
+ [1526, 1041],
+ [1531, 1014],
+ [1536, 1004],
+ [1537, 994],
+ [1544, 982],
+ [1544, 977],
+ [1549, 967],
+ [1551, 954],
+ [1558, 932],
+ [1559, 915],
+ [1563, 910],
+ [1566, 813],
+ [1569, 797],
+ [1569, 781],
+ [1568, 780],
+ [1566, 751],
+ [1563, 738]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "2a5bdb7c-a086-b288-2a31-7903a7a09ecf",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=266&y=309&width=1605&height=1312&uuid=254f827d-79cb-45fb-9b11-037fda961c4c"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "6d6b4bcd-e710-686c-6d01-e9b2e036442b",
+ "id": "65beb0eb-d1ea-7d19-65d4-1294d6cc515e",
+ "image_region": {
+ "id": "8b9c92c0-07dc-4b41-8bf6-30bf00fa6706",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 338,
+ "width": 180,
+ "xmin": 341,
+ "ymin": 649
+ },
+ "polygons": [
+ [
+ [390, 666],
+ [378, 674],
+ [368, 693],
+ [351, 746],
+ [349, 805],
+ [351, 807],
+ [351, 827],
+ [356, 853],
+ [366, 880],
+ [386, 907],
+ [395, 922],
+ [428, 956],
+ [452, 971],
+ [462, 972],
+ [475, 967],
+ [497, 952],
+ [508, 939],
+ [514, 925],
+ [514, 907],
+ [508, 887],
+ [508, 859],
+ [503, 837],
+ [505, 813],
+ [498, 802],
+ [492, 768],
+ [485, 755],
+ [477, 726],
+ [462, 703],
+ [433, 676],
+ [410, 666],
+ [401, 666],
+ [400, 664]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "8e49cf30-378e-1cf1-8e23-6d4f30a830b6",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=206&y=649&width=656&height=987&uuid=3e69cdc5-2371-4eb9-86ff-cedcfde8addc"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "a2bd7491-a911-ffdb-a2d7-d6eeae37d39c",
+ "id": "5084700a-043a-6074-50ee-d275031c4c33",
+ "image_region": {
+ "id": "4ff541ec-7cc5-3075-4f9f-e3937be31c32",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 548,
+ "width": 267,
+ "xmin": 1297,
+ "ymin": 299
+ },
+ "polygons": [
+ [
+ [1333, 324],
+ [1331, 355],
+ [1335, 360],
+ [1335, 375],
+ [1333, 376],
+ [1335, 396],
+ [1336, 398],
+ [1335, 403],
+ [1351, 432],
+ [1367, 492],
+ [1367, 512],
+ [1368, 514],
+ [1368, 529],
+ [1370, 530],
+ [1368, 584],
+ [1367, 586],
+ [1365, 609],
+ [1362, 619],
+ [1360, 648],
+ [1351, 686],
+ [1346, 699],
+ [1345, 718],
+ [1335, 755],
+ [1325, 771],
+ [1320, 788],
+ [1311, 803],
+ [1310, 812],
+ [1313, 815],
+ [1323, 818],
+ [1350, 823],
+ [1355, 815],
+ [1368, 770],
+ [1393, 720],
+ [1408, 703],
+ [1442, 679],
+ [1462, 671],
+ [1475, 671],
+ [1482, 674],
+ [1492, 684],
+ [1499, 701],
+ [1499, 706],
+ [1506, 716],
+ [1507, 731],
+ [1521, 778],
+ [1526, 783],
+ [1534, 783],
+ [1546, 773],
+ [1551, 761],
+ [1551, 740],
+ [1548, 735],
+ [1548, 726],
+ [1553, 716],
+ [1553, 703],
+ [1534, 683],
+ [1524, 678],
+ [1516, 678],
+ [1512, 674],
+ [1514, 669],
+ [1529, 653],
+ [1532, 641],
+ [1531, 631],
+ [1526, 624],
+ [1507, 616],
+ [1504, 612],
+ [1501, 601],
+ [1496, 594],
+ [1487, 567],
+ [1496, 566],
+ [1482, 552],
+ [1454, 530],
+ [1437, 504],
+ [1392, 415],
+ [1363, 375],
+ [1355, 358],
+ [1343, 343]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "ce9f0c5d-2b3e-d5b1-cef5-ae222c18f9f6",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1066&y=299&width=1795&height=847&uuid=8ace9da8-c7fa-4ed3-8671-4185fff3235b"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "17fd8970-6229-d165-1797-2b0f650ffd22",
+ "id": "0383f405-66a7-0af0-03e9-567a618126b7",
+ "image_region": {
+ "id": "c3e9c375-57cf-5fee-c383-610a50e973a9",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 36,
+ "width": 47,
+ "xmin": 1267,
+ "ymin": 1019
+ },
+ "polygons": [
+ [
+ [1269, 1031],
+ [1269, 1036],
+ [1278, 1044],
+ [1289, 1048],
+ [1301, 1054],
+ [1306, 1054],
+ [1310, 1051],
+ [1313, 1038],
+ [1310, 1029],
+ [1305, 1024],
+ [1289, 1021],
+ [1276, 1024]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "02d8800f-7866-58aa-02b2-22707f4074ed",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1267&y=1019&width=1314&height=1055&uuid=93b4e516-ebf0-4434-83e3-1b60842006c7"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "833efbf2-dda5-8c31-8354-598dda83a076",
+ "id": "29ed50ba-aa7d-351a-2987-f2c5ad5b195d",
+ "image_region": {
+ "id": "17cf2eb5-ba82-c186-17a5-8ccabda4edc1",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 123,
+ "width": 628,
+ "xmin": 632,
+ "ymin": 49
+ },
+ "polygons": [
+ [
+ [661, 155],
+ [661, 162],
+ [666, 164],
+ [684, 159],
+ [720, 159],
+ [721, 157],
+ [740, 157],
+ [741, 155],
+ [814, 155],
+ [815, 154],
+ [835, 154],
+ [837, 152],
+ [1078, 152],
+ [1080, 154],
+ [1122, 154],
+ [1124, 155],
+ [1172, 157],
+ [1174, 159],
+ [1204, 162],
+ [1219, 167],
+ [1226, 167],
+ [1232, 164],
+ [1229, 150],
+ [1224, 140],
+ [1204, 112],
+ [1187, 95],
+ [1167, 82],
+ [1135, 68],
+ [1119, 65],
+ [1105, 65],
+ [1103, 63],
+ [1068, 62],
+ [1067, 60],
+ [1036, 60],
+ [1035, 58],
+ [1011, 58],
+ [1010, 57],
+ [922, 57],
+ [921, 55],
+ [849, 57],
+ [847, 58],
+ [835, 58],
+ [822, 62],
+ [807, 62],
+ [772, 68],
+ [762, 68],
+ [731, 77],
+ [718, 83],
+ [701, 98],
+ [669, 139]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "79c36708-b835-5fdb-79a9-c577bf13739c",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=632&y=0&width=1260&height=346&uuid=98eb6dce-d25c-448e-9d1e-abe957165ce0"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "element_id": "4c70355e-6197-3c0b-4c1a-972166b1104c",
+ "id": "e7a16ffb-a22c-8705-e7cb-cd84a50aab42",
+ "image_region": {
+ "id": "f117fd69-5b6d-6d90-f17d-5f165c4b41d7",
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "object_type": "IMAGE_REGION",
+ "specification": {
+ "bounding_box": {
+ "height": 322,
+ "width": 186,
+ "xmin": 1330,
+ "ymin": 658
+ },
+ "polygons": [
+ [
+ [1475, 673],
+ [1455, 674],
+ [1442, 681],
+ [1408, 704],
+ [1400, 713],
+ [1390, 728],
+ [1367, 778],
+ [1358, 810],
+ [1351, 823],
+ [1355, 848],
+ [1346, 879],
+ [1345, 892],
+ [1338, 910],
+ [1338, 922],
+ [1340, 930],
+ [1346, 944],
+ [1358, 954],
+ [1375, 962],
+ [1393, 966],
+ [1422, 951],
+ [1469, 909],
+ [1489, 880],
+ [1494, 869],
+ [1497, 847],
+ [1504, 823],
+ [1504, 810],
+ [1506, 808],
+ [1504, 787],
+ [1506, 785],
+ [1507, 753],
+ [1506, 751],
+ [1504, 728],
+ [1501, 721],
+ [1496, 696],
+ [1491, 684],
+ [1482, 676]
+ ]
+ ]
+ }
+ },
+ "object_type": "VIEW",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "crop of part view"
+ },
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "662bc970-70d1-83de-6641-6b0f77f7af99",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1209&y=658&width=1637&height=980&uuid=5f32b9a4-4f03-41df-893a-5816fdde5030"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "additional_data": {
+ "category": "exterior",
+ "label": {
+ "de": "Dach",
+ "en": "Roof",
+ "fr": "Toit"
+ }
+ },
+ "binary_size": 145095,
+ "detailed_viewpoint": {
+ "centers_on": ["roof"]
+ },
+ "id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
+ "image_height": 1080,
+ "image_type": "close_up",
+ "image_width": 1920,
+ "mimetype": "image/jpeg",
+ "name": "close_up_roof.jpeg",
+ "object_type": "IMAGE",
+ "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media",
+ "rendered_outputs": [
+ {
+ "additional_data": {
+ "description": "rendering of detected parts"
+ },
+ "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "00b314cf-c28d-d75e-00d9-b6b0c5abfb19",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.672125_e3d7e8dc-ffa7-44cf-a19d-6b08b80b100c.jpg"
+ },
+ {
+ "additional_data": {
+ "description": "rendering of detected damages"
+ },
+ "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
+ "created_at": "2023-12-13T13:27:15.750698+00:00",
+ "id": "b67370af-bedc-69ad-b619-d2d0b9fa45ea",
+ "object_type": "RENDERED_OUTPUT",
+ "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.701367_a449d728-ceb7-49f1-994a-701ac8f4645f.jpg"
+ }
+ ],
+ "views": []
+ }
+ ],
+ "inspection_type": null,
+ "inspector_id": null,
+ "object_type": "INSPECTION",
+ "owner_id": "google-oauth2|109722589040992162710",
+ "parts": [
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "640b0825-60c5-887d-6461-aa5a67e3a43a",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "bumper_back",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 67370.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1149&y=453&width=1786&height=931&uuid=6db3ece5-ef73-446e-b224-8fa0abb038ee"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "bf94b068-56d5-7a8a-bffe-121751f356cd",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "bumper_front",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 208376.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=266&y=309&width=1605&height=1312&uuid=254f827d-79cb-45fb-9b11-037fda961c4c"
+ },
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 63099.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 1,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1141&y=345&width=1935&height=941&uuid=43c2d2b3-a647-44f5-baa5-eb1553504971"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "68d766cb-9bbf-c196-68bd-c4b49c99edd1",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "door_back_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 20874.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=120&width=796&height=302&uuid=2f0c7d32-1f45-466f-83a6-40362240c624"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": ["80bac861-01b6-9381-80d0-6a1e0690bfc6"],
+ "id": "2d917d5d-6636-5ea7-2dfb-df22611072e0",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "door_front_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 349380.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=74&width=807&height=792&uuid=ef876343-5944-443f-b0ef-19e2a5b21dfb"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "ce9382ec-d9f3-17df-cef9-2093ded53b98",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "fender_back_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 315816.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=463&y=3&width=1695&height=926&uuid=951e4ca7-78fe-4c88-b2da-ad66ea5a19a4"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "0f6665a0-47fe-6e8e-0f0c-c7df40d842c9",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "fender_front_right",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 40201.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=81&y=312&width=779&height=836&uuid=47a952b4-0931-4993-b993-bf589f0f20b4"
+ },
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 274.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 1,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1510&y=380&width=1543&height=406&uuid=234a6c06-8755-4250-a3a7-5e572485908d"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "02ef856b-c9bf-3932-0285-2714ce991575",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "grill_radiator",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 4590.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1595&y=419&width=1705&height=502&uuid=de52a81b-ffab-4999-84ae-90082a59e42e"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "437b7a70-311c-6ac9-4311-d80f363a468e",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "handle_front_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 3931.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=368&y=353&width=537&height=479&uuid=e5054412-4407-4e4d-86cf-8c7d4720caf7"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "299b478c-4091-0165-29f1-e5f347b72d22",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "hood",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 385971.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=441&y=312&width=1412&height=1040&uuid=d03546db-19bc-45d3-822c-0dfb77ded67e"
+ },
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 1957.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 1,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1624&y=330&width=1702&height=389&uuid=f5422914-4b91-4c3f-af25-bbcf6ce39b70"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "a6b41fba-df6f-2d6f-a6de-bdc5d8490128",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "hubcap_back_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 129960.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=785&y=588&width=1361&height=1021&uuid=12d45d89-b837-4823-8e9d-82bb96027530"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "mirror_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 4617.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=978&y=193&width=1116&height=296&uuid=86b3e176-a99d-4e8e-80e4-18aaad8682df"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "535c6a2a-f1f3-f91b-5336-c855f6d5d55c",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "mirror_right",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 169.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1290&y=283&width=1313&height=300&uuid=79754efd-a945-4c92-b1b2-48b1ab05b845"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "mirror_support",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 182.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1271&y=278&width=1295&height=297&uuid=62e1711e-2119-4a1b-9399-ed26fcf67f58"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "fff6bd07-f76a-1597-ff9c-1f78f04c39d0",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "pillar",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 5341.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1107&y=154&width=1452&height=413&uuid=c6142566-5af5-49f3-bfe3-56cc3e79b57e"
+ },
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 167.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 1,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1&y=201&width=67&height=250&uuid=b5bf1613-2d11-4b5d-81ff-25a8c37635f3"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": ["c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac"],
+ "id": "56c74ad5-c077-1d08-56ad-e8aac751314f",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "rocker_panel_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 45738.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=500&width=847&height=1135&uuid=898411ea-6207-4837-9d0e-b9c9498c27be"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [
+ "be6cb3ae-8bfd-5efb-be06-11d18cdb72bc",
+ "3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b"
+ ],
+ "id": "833efbf2-dda5-8c31-8354-598dda83a076",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "roof",
+ "related_images": [
+ {
+ "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
+ "base_image_type": "close_up",
+ "image_type": "close_up",
+ "mimetype": "image/jpeg",
+ "object_type": "IMAGE",
+ "order": 0,
+ "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media"
+ },
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 68908.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 1,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=158&y=0&width=1131&height=538&uuid=67101981-8f78-46b5-a637-f1719056b000"
+ },
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 46823.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 2,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=632&y=0&width=1260&height=346&uuid=98eb6dce-d25c-448e-9d1e-abe957165ce0"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "3ed12b36-252c-98ed-3ebb-8949220ab4aa",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "wheel_back_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 196941.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=737&y=529&width=1432&height=1051&uuid=5b79a55e-69d7-4343-a3ce-8fd512d5058f"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "cae4e3f4-c675-af59-ca8e-418bc153831e",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "window_back_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 19720.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=127&width=796&height=309&uuid=8ee0b33a-83d6-4d3f-9624-f333c2ab63f7"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "08ec65ae-3fb6-e4a3-0886-c7d13890c8e4",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "window_front_left",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 71554.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=88&y=12&width=631&height=419&uuid=465f6cb3-fe92-4911-b219-4e09e37f2e4a"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "f78b2228-70ce-da31-f7e1-805777e8f676",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "windshield_back",
+ "related_images": [
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 2248.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1085&y=201&width=1180&height=272&uuid=bd9cdd26-6ac4-42c0-8af7-012cc61460fc"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "3a4f083c-10fb-b483-3a25-aa4317dd98c4",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "windshield_front",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 200714.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=508&y=0&width=1374&height=634&uuid=26a62e63-16f8-4861-90c5-eabc18a6adfa"
+ },
+ {
+ "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
+ "base_image_type": "beauty_shot",
+ "item_area": 3161.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 1,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1026&y=154&width=1162&height=255&uuid=bc4222b3-d65e-4c38-a7d3-3f6886607e33"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "a2bd7491-a911-ffdb-a2d7-d6eeae37d39c",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "fender_front_left",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 41287.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1066&y=299&width=1795&height=847&uuid=8ace9da8-c7fa-4ed3-8671-4185fff3235b"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "17fd8970-6229-d165-1797-2b0f650ffd22",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "fog_light_front_left",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 1013.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1267&y=1019&width=1314&height=1055&uuid=93b4e516-ebf0-4434-83e3-1b60842006c7"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "4c70355e-6197-3c0b-4c1a-972166b1104c",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "head_light_left",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 34315.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1209&y=658&width=1637&height=980&uuid=5f32b9a4-4f03-41df-893a-5816fdde5030"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "6d6b4bcd-e710-686c-6d01-e9b2e036442b",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "head_light_right",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 36407.0,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=206&y=649&width=656&height=987&uuid=3e69cdc5-2371-4eb9-86ff-cedcfde8addc"
+ }
+ ]
+ },
+ {
+ "created_at": "2023-12-13T13:27:15.023995+00:00",
+ "created_by": "algo",
+ "damage_ids": [],
+ "id": "4d9cb158-d7ed-c73e-4df6-1327d0cbeb79",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "object_type": "PART",
+ "part_type": "wiper_front",
+ "related_images": [
+ {
+ "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
+ "base_image_type": "beauty_shot",
+ "item_area": 3536.5,
+ "mimetype": "image/jpeg",
+ "object_type": "RENDERED_OUTPUT",
+ "order": 0,
+ "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=947&y=335&width=1216&height=535&uuid=fb9e798e-dd63-4bc8-ba76-70c565f93cea"
+ }
+ ]
+ }
+ ],
+ "pdf_generation_ready": true,
+ "pricing": {
+ "details": {},
+ "total_price": 0
+ },
+ "related_inspection_id": null,
+ "severity_results": [
+ {
+ "created_by": "algo",
+ "id": "ddf61a7f-8f66-65d9-dd9c-b8008840499e",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "is_user_modified": false,
+ "label": "door_front_left",
+ "object_type": "SEVERITY_RESULT",
+ "related_item_id": "2d917d5d-6636-5ea7-2dfb-df22611072e0",
+ "related_item_type": "part",
+ "value": {
+ "custom_severity": {
+ "level": 3,
+ "pricing": 282,
+ "repair_operation": {
+ "ADDITIONAL": false,
+ "PAINT": 1,
+ "REPLACE": false,
+ "T1": 0,
+ "T2": 0
+ }
+ }
+ }
+ },
+ {
+ "created_by": "algo",
+ "id": "ca8b97ee-a50f-919b-cae1-3591a229bddc",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "is_user_modified": false,
+ "label": "rocker_panel_left",
+ "object_type": "SEVERITY_RESULT",
+ "related_item_id": "56c74ad5-c077-1d08-56ad-e8aac751314f",
+ "related_item_type": "part",
+ "value": {
+ "custom_severity": {
+ "level": 1,
+ "pricing": 264,
+ "repair_operation": {
+ "ADDITIONAL": false,
+ "PAINT": 1,
+ "REPLACE": false,
+ "T1": 0,
+ "T2": 0
+ }
+ }
+ }
+ },
+ {
+ "created_by": "algo",
+ "id": "5dfffeae-6b5f-a699-5d95-5cd16c798ade",
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "is_user_modified": false,
+ "label": "roof",
+ "object_type": "SEVERITY_RESULT",
+ "related_item_id": "833efbf2-dda5-8c31-8354-598dda83a076",
+ "related_item_type": "part",
+ "value": {
+ "custom_severity": {
+ "level": 1,
+ "pricing": 480,
+ "repair_operation": {
+ "ADDITIONAL": false,
+ "PAINT": 0,
+ "REPLACE": false,
+ "T1": 0,
+ "T2": 0
+ }
+ }
+ }
+ }
+ ],
+ "tasks": [
+ {
+ "arguments": {
+ "damage_score_threshold": 0.3,
+ "generate_subimages_damages": {
+ "damage_view_part_interpolation": 0.0,
+ "generate_tight": false,
+ "margin": 0,
+ "quality": 0.9,
+ "ratio": 1.3333333333333333
+ },
+ "generate_subimages_parts": {
+ "damage_view_part_interpolation": 0.0,
+ "generate_tight": false,
+ "margin": 0,
+ "quality": 0.9,
+ "ratio": 1.3333333333333333
+ },
+ "generate_visual_output": {
+ "generate_damages": true,
+ "generate_parts": true
+ }
+ },
+ "confidence_post_execution": true,
+ "confidence_pre_execution_status": "NOT_STARTED",
+ "created_at": "2023-12-13T13:27:09.065973+00:00",
+ "done_at": "2023-12-13T13:27:18.366089+00:00",
+ "id": "fd605401-c5b6-8400-fd0a-f67ec290a847",
+ "images": [
+ {
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1"
+ },
+ {
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c"
+ },
+ {
+ "image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4"
+ }
+ ],
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "name": "damage_detection",
+ "object_type": "TASK",
+ "status": "DONE"
+ },
+ {
+ "arguments": {},
+ "confidence_pre_execution_status": "NOT_STARTED",
+ "created_at": "2023-12-13T13:27:09.112670+00:00",
+ "id": "f3ce86b5-d3a8-27dc-f3a4-24cad48e0b9b",
+ "images": [
+ {
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1"
+ },
+ {
+ "image_id": "a842f4bf-5404-215b-a828-56c053220d1c"
+ },
+ {
+ "image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4"
+ }
+ ],
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "name": "compliances",
+ "object_type": "TASK",
+ "status": "IN_PROGRESS"
+ },
+ {
+ "arguments": {
+ "use_longshots": true
+ },
+ "confidence_pre_execution_status": "NOT_STARTED",
+ "created_at": "2023-12-13T13:27:09.084506+00:00",
+ "done_at": "2023-12-13T13:27:17.475157+00:00",
+ "id": "89216d57-0d6d-dd29-894b-cf280a4bf16e",
+ "images": [
+ {
+ "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1"
+ }
+ ],
+ "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
+ "name": "wheel_analysis",
+ "object_type": "TASK",
+ "status": "DONE"
+ }
+ ],
+ "usage_duration": null,
+ "vehicle": {
+ "additional_data": {
+ "brand": {
+ "confidence": 0.8455246090888977,
+ "prediction": "Toyota"
+ },
+ "model": {
+ "confidence": 0.8455246090888977,
+ "prediction": "Aygo"
+ },
+ "plate": {
+ "confidence": -1.0,
+ "prediction": ""
+ },
+ "vehicle_type": "car"
+ },
+ "brand": "Toyota",
+ "car_registration": null,
+ "color": null,
+ "created_at": "2023-12-13T13:27:09.065973+00:00",
+ "date_of_circulation": null,
+ "deleted_at": null,
+ "duplicate_keys": null,
+ "expertise_requested": null,
+ "exterior_cleanliness": null,
+ "id": "051b7118-915f-4bec-0571-d367967967ab",
+ "interior_cleanliness": null,
+ "market_value": {},
+ "mileage": {},
+ "model": "Aygo",
+ "object_type": "VEHICLE",
+ "owner_info": null,
+ "plate": "",
+ "trade_in_offer": null,
+ "vehicle_quotation": null,
+ "vehicle_type": "car",
+ "vin": "1HGEJ8244YL06777"
+ },
+ "wheel_analysis": []
+}
diff --git a/packages/public/network/test/api/inspection/apiInspectionGet.data.ts b/packages/public/network/test/api/inspection/apiInspectionGet.data.ts
new file mode 100644
index 000000000..433eee0ad
--- /dev/null
+++ b/packages/public/network/test/api/inspection/apiInspectionGet.data.ts
@@ -0,0 +1,3473 @@
+export default {
+ damages: [
+ {
+ id: '80bac861-01b6-9381-80d0-6a1e0690bfc6',
+ entityType: 'DAMAGE',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'scratch',
+ size: 50.021743434811555,
+ parts: ['2d917d5d-6636-5ea7-2dfb-df22611072e0'],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac',
+ entityType: 'DAMAGE',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'scratch',
+ size: 3.17945481147886,
+ parts: ['56c74ad5-c077-1d08-56ad-e8aac751314f'],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'be6cb3ae-8bfd-5efb-be06-11d18cdb72bc',
+ entityType: 'DAMAGE',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'paint_peeling',
+ parts: ['833efbf2-dda5-8c31-8354-598dda83a076'],
+ relatedImages: ['8ad728c4-90ee-7983-8abd-8abb97c855c4'],
+ },
+ {
+ id: '3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b',
+ entityType: 'DAMAGE',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'misshape',
+ parts: ['833efbf2-dda5-8c31-8354-598dda83a076'],
+ relatedImages: ['8ad728c4-90ee-7983-8abd-8abb97c855c4'],
+ },
+ ],
+ images: [
+ {
+ id: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ entityType: 'IMAGE',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ path: 'https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg?generation=1702474029543236&alt=media',
+ width: 1920,
+ height: 1080,
+ size: 141539,
+ mimetype: 'image/jpeg',
+ type: 'beauty_shot',
+ viewpoint: {
+ confidence: 0.9847987294197083,
+ prediction: 'left',
+ },
+ label: {
+ de: 'Hinten Seitlich Niedrig Links',
+ en: 'Rear Lateral Low Left',
+ fr: 'Arrière Gauche Latéral - vue basse',
+ },
+ renderedOutputs: [
+ '87650e8f-b8c9-6c6b-870f-acf0bfef402c',
+ 'd44c1e3d-fcab-dd30-d426-bc42fb8df177',
+ ],
+ views: [
+ 'ab2f9850-0165-3458-ab45-3a2f0643181f',
+ '4d503f11-7b27-a0aa-4d3a-9d6e7c018ced',
+ 'afbadee2-e194-3b07-afd0-7c9de6b21740',
+ '7145ebd3-58f4-2328-712f-49ac5fd20f6f',
+ 'b0b552b2-f1b6-1bcc-b0df-f0cdf690378b',
+ 'ca7c9b47-530d-6eb3-ca16-3938542b42f4',
+ 'aba1ed91-94b8-c4d3-abcb-4fee939ee894',
+ '25ac94fe-126f-2614-25c6-368115490a53',
+ '78340423-5a5d-6f33-785e-a65c5d7b4374',
+ '667da482-f302-4e5d-6617-06fdf424621a',
+ '3bdc7c4e-75cc-4386-3bb6-de3172ea6fc1',
+ '47ba5f70-96de-6fad-47d0-fd0f91f843ea',
+ 'd838da37-94f5-586a-d852-784893d3742d',
+ 'f5adeae9-5e35-ec2d-f5c7-48965913c06a',
+ '3179a7a6-8703-f8dc-3113-05d98025d49b',
+ '72d12f4f-8de3-2ed7-72bb-8d308ac50290',
+ '260594c3-2b62-74c0-266f-36bc2c445887',
+ '858717fe-a458-fbb3-85ed-b581a37ed7f4',
+ '461084a6-d7e8-4430-467a-26d9d0ce6877',
+ '8eab6f21-2c6b-59a7-8ec1-cd5e2b4d75e0',
+ '71dbdd16-1c49-864a-71b1-7f691b6faa0d',
+ '8074a493-4434-7b65-801e-06ec43125722',
+ '439bd668-c016-75e3-43f1-7417c73059a4',
+ ],
+ additionalData: {
+ category: 'exterior',
+ sight_id: 'ffocus18-S3kgFOBb',
+ label: {
+ de: 'Hinten Seitlich Niedrig Links',
+ en: 'Rear Lateral Low Left',
+ fr: 'Arrière Gauche Latéral - vue basse',
+ },
+ },
+ },
+ {
+ id: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ entityType: 'IMAGE',
+ path: 'https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg?generation=1702474029549060&alt=media',
+ width: 1920,
+ height: 1080,
+ size: 160207,
+ mimetype: 'image/jpeg',
+ type: 'beauty_shot',
+ viewpoint: {
+ confidence: 0.4582550525665283,
+ prediction: 'front_left',
+ },
+ compliances: {},
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ label: {
+ de: 'Motorhaube',
+ en: 'Hood',
+ fr: 'Capot',
+ },
+ renderedOutputs: [
+ 'd14655ab-f662-3898-d12c-f7d4f14414df',
+ 'ffee30b3-6d49-58ea-ff84-92cc6a6f74ad',
+ ],
+ views: [
+ '66937b3f-dc7a-f700-66f9-d940db5cdb47',
+ '058c0533-0a74-dcb3-05e6-a74c0d52f0f4',
+ '740607eb-7996-67f0-746c-a5947eb04bb7',
+ 'be3948a9-777a-be37-be53-ead6705c9270',
+ 'd64d13af-dd3c-cc9e-d627-b1d0da1ae0d9',
+ '6805436b-c50b-756f-686f-e114c22d5928',
+ '65beb0eb-d1ea-7d19-65d4-1294d6cc515e',
+ '5084700a-043a-6074-50ee-d275031c4c33',
+ '0383f405-66a7-0af0-03e9-567a618126b7',
+ '29ed50ba-aa7d-351a-2987-f2c5ad5b195d',
+ 'e7a16ffb-a22c-8705-e7cb-cd84a50aab42',
+ ],
+ additionalData: {
+ category: 'exterior',
+ sight_id: 'ffocus18-3TiCVAaN',
+ label: {
+ de: 'Motorhaube',
+ en: 'Hood',
+ fr: 'Capot',
+ },
+ },
+ },
+ {
+ id: '8ad728c4-90ee-7983-8abd-8abb97c855c4',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ entityType: 'IMAGE',
+ path: 'https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media',
+ width: 1920,
+ height: 1080,
+ size: 145095,
+ mimetype: 'image/jpeg',
+ type: 'close_up',
+ detailedViewpoint: {
+ centersOn: ['roof'],
+ },
+ label: {
+ de: 'Dach',
+ en: 'Roof',
+ fr: 'Toit',
+ },
+ renderedOutputs: [
+ '00b314cf-c28d-d75e-00d9-b6b0c5abfb19',
+ 'b67370af-bedc-69ad-b619-d2d0b9fa45ea',
+ ],
+ views: [],
+ additionalData: {
+ category: 'exterior',
+ label: {
+ de: 'Dach',
+ en: 'Roof',
+ fr: 'Toit',
+ },
+ },
+ },
+ ],
+ inspections: [
+ {
+ id: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ entityType: 'INSPECTION',
+ tasks: [
+ 'fd605401-c5b6-8400-fd0a-f67ec290a847',
+ 'f3ce86b5-d3a8-27dc-f3a4-24cad48e0b9b',
+ '89216d57-0d6d-dd29-894b-cf280a4bf16e',
+ ],
+ images: [
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ '8ad728c4-90ee-7983-8abd-8abb97c855c4',
+ ],
+ damages: [
+ '80bac861-01b6-9381-80d0-6a1e0690bfc6',
+ 'c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac',
+ 'be6cb3ae-8bfd-5efb-be06-11d18cdb72bc',
+ '3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b',
+ ],
+ parts: [
+ '640b0825-60c5-887d-6461-aa5a67e3a43a',
+ 'bf94b068-56d5-7a8a-bffe-121751f356cd',
+ '68d766cb-9bbf-c196-68bd-c4b49c99edd1',
+ '2d917d5d-6636-5ea7-2dfb-df22611072e0',
+ 'ce9382ec-d9f3-17df-cef9-2093ded53b98',
+ '0f6665a0-47fe-6e8e-0f0c-c7df40d842c9',
+ '02ef856b-c9bf-3932-0285-2714ce991575',
+ '437b7a70-311c-6ac9-4311-d80f363a468e',
+ '299b478c-4091-0165-29f1-e5f347b72d22',
+ 'a6b41fba-df6f-2d6f-a6de-bdc5d8490128',
+ 'c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a',
+ '535c6a2a-f1f3-f91b-5336-c855f6d5d55c',
+ 'fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3',
+ 'fff6bd07-f76a-1597-ff9c-1f78f04c39d0',
+ '56c74ad5-c077-1d08-56ad-e8aac751314f',
+ '833efbf2-dda5-8c31-8354-598dda83a076',
+ '3ed12b36-252c-98ed-3ebb-8949220ab4aa',
+ 'cae4e3f4-c675-af59-ca8e-418bc153831e',
+ '08ec65ae-3fb6-e4a3-0886-c7d13890c8e4',
+ 'f78b2228-70ce-da31-f7e1-805777e8f676',
+ '3a4f083c-10fb-b483-3a25-aa4317dd98c4',
+ 'a2bd7491-a911-ffdb-a2d7-d6eeae37d39c',
+ '17fd8970-6229-d165-1797-2b0f650ffd22',
+ '4c70355e-6197-3c0b-4c1a-972166b1104c',
+ '6d6b4bcd-e710-686c-6d01-e9b2e036442b',
+ '4d9cb158-d7ed-c73e-4df6-1327d0cbeb79',
+ ],
+ wheelAnalysis: [],
+ severityResults: [
+ 'ddf61a7f-8f66-65d9-dd9c-b8008840499e',
+ 'ca8b97ee-a50f-919b-cae1-3591a229bddc',
+ '5dfffeae-6b5f-a699-5d95-5cd16c798ade',
+ ],
+ pricing: {
+ details: {},
+ totalPrice: 0,
+ },
+ additionalData: {
+ damage_detection_version: 'v2',
+ environment: {
+ custom_inspection: true,
+ },
+ use_dynamic_crops: true,
+ },
+ },
+ ],
+ parts: [
+ {
+ id: '640b0825-60c5-887d-6461-aa5a67e3a43a',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'bumper_back',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'bf94b068-56d5-7a8a-bffe-121751f356cd',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'bumper_front',
+ damages: [],
+ relatedImages: [
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ ],
+ },
+ {
+ id: '68d766cb-9bbf-c196-68bd-c4b49c99edd1',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'door_back_left',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: '2d917d5d-6636-5ea7-2dfb-df22611072e0',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'door_front_left',
+ damages: ['80bac861-01b6-9381-80d0-6a1e0690bfc6'],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'ce9382ec-d9f3-17df-cef9-2093ded53b98',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'fender_back_left',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: '0f6665a0-47fe-6e8e-0f0c-c7df40d842c9',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'fender_front_right',
+ damages: [],
+ relatedImages: [
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ ],
+ },
+ {
+ id: '02ef856b-c9bf-3932-0285-2714ce991575',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'grill_radiator',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: '437b7a70-311c-6ac9-4311-d80f363a468e',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'handle_front_left',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: '299b478c-4091-0165-29f1-e5f347b72d22',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'hood',
+ damages: [],
+ relatedImages: [
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ ],
+ },
+ {
+ id: 'a6b41fba-df6f-2d6f-a6de-bdc5d8490128',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'hubcap_back_left',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'mirror_left',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: '535c6a2a-f1f3-f91b-5336-c855f6d5d55c',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'mirror_right',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'mirror_support',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'fff6bd07-f76a-1597-ff9c-1f78f04c39d0',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'pillar',
+ damages: [],
+ relatedImages: [
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ ],
+ },
+ {
+ id: '56c74ad5-c077-1d08-56ad-e8aac751314f',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'rocker_panel_left',
+ damages: ['c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac'],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: '833efbf2-dda5-8c31-8354-598dda83a076',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'roof',
+ damages: ['be6cb3ae-8bfd-5efb-be06-11d18cdb72bc', '3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b'],
+ relatedImages: [
+ '8ad728c4-90ee-7983-8abd-8abb97c855c4',
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ ],
+ },
+ {
+ id: '3ed12b36-252c-98ed-3ebb-8949220ab4aa',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'wheel_back_left',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'cae4e3f4-c675-af59-ca8e-418bc153831e',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'window_back_left',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: '08ec65ae-3fb6-e4a3-0886-c7d13890c8e4',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'window_front_left',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: 'f78b2228-70ce-da31-f7e1-805777e8f676',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'windshield_back',
+ damages: [],
+ relatedImages: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ {
+ id: '3a4f083c-10fb-b483-3a25-aa4317dd98c4',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'windshield_front',
+ damages: [],
+ relatedImages: [
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ ],
+ },
+ {
+ id: 'a2bd7491-a911-ffdb-a2d7-d6eeae37d39c',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'fender_front_left',
+ damages: [],
+ relatedImages: ['a842f4bf-5404-215b-a828-56c053220d1c'],
+ },
+ {
+ id: '17fd8970-6229-d165-1797-2b0f650ffd22',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'fog_light_front_left',
+ damages: [],
+ relatedImages: ['a842f4bf-5404-215b-a828-56c053220d1c'],
+ },
+ {
+ id: '4c70355e-6197-3c0b-4c1a-972166b1104c',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'head_light_left',
+ damages: [],
+ relatedImages: ['a842f4bf-5404-215b-a828-56c053220d1c'],
+ },
+ {
+ id: '6d6b4bcd-e710-686c-6d01-e9b2e036442b',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'head_light_right',
+ damages: [],
+ relatedImages: ['a842f4bf-5404-215b-a828-56c053220d1c'],
+ },
+ {
+ id: '4d9cb158-d7ed-c73e-4df6-1327d0cbeb79',
+ entityType: 'PART',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ type: 'wiper_front',
+ damages: [],
+ relatedImages: ['a842f4bf-5404-215b-a828-56c053220d1c'],
+ },
+ ],
+ renderedOutputs: [
+ {
+ id: '87650e8f-b8c9-6c6b-870f-acf0bfef402c',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.688404_c49980a3-4c96-44ec-bdb5-76e25391efc3.jpg',
+ additionalData: {
+ description: 'rendering of detected parts',
+ },
+ },
+ {
+ id: 'd44c1e3d-fcab-dd30-d426-bc42fb8df177',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.710278_24b3ec1c-5282-4451-8985-afd589513069.jpg',
+ additionalData: {
+ description: 'rendering of detected damages',
+ },
+ },
+ {
+ id: 'd8195eaa-8800-3c56-d873-fcd58f261011',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=88&y=12&width=631&height=419&uuid=465f6cb3-fe92-4911-b219-4e09e37f2e4a',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '0bd88b55-6e6f-d766-0bb2-292a6949fb21',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=127&width=796&height=309&uuid=8ee0b33a-83d6-4d3f-9624-f333c2ab63f7',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '4df4bd91-b34d-5da2-4d9e-1feeb46b71e5',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1624&y=330&width=1702&height=389&uuid=f5422914-4b91-4c3f-af25-bbcf6ce39b70',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'fd4a9f9b-7e8b-59e2-fd20-3de479ad75a5',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1&y=201&width=67&height=250&uuid=b5bf1613-2d11-4b5d-81ff-25a8c37635f3',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '015826ca-e188-6e37-0132-84b5e6ae4270',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=158&y=0&width=1131&height=538&uuid=67101981-8f78-46b5-a637-f1719056b000',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '57d9e4d8-030a-c10e-57b3-46a7042ced49',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1595&y=419&width=1705&height=502&uuid=de52a81b-ffab-4999-84ae-90082a59e42e',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '1ffaa2e9-4a52-083a-1f90-00964d74247d',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1026&y=154&width=1162&height=255&uuid=bc4222b3-d65e-4c38-a7d3-3f6886607e33',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'cc7caa83-c7a7-375f-cc16-08fcc0811b18',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=368&y=353&width=537&height=479&uuid=e5054412-4407-4e4d-86cf-8c7d4720caf7',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '5a8a4569-ae52-aeb3-5ae0-e716a97482f4',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=216&y=504&width=531&height=739&uuid=72c9a923-d181-4584-8d29-f6290a41d140',
+ additionalData: {
+ description: 'crop of damage view',
+ },
+ },
+ {
+ id: '2760d5fe-df9e-bb67-270a-7781d8b89720',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1290&y=283&width=1313&height=300&uuid=79754efd-a945-4c92-b1b2-48b1ab05b845',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '1567aac5-c8e4-7584-150d-08bacfc259c3',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=978&y=193&width=1116&height=296&uuid=86b3e176-a99d-4e8e-80e4-18aaad8682df',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '1df49927-6747-6719-1d9e-3b5860614b5e',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1510&y=380&width=1543&height=406&uuid=234a6c06-8755-4250-a3a7-5e572485908d',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'b69e0f13-edf3-0474-b6f4-ad6cead52833',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=785&y=588&width=1361&height=1021&uuid=12d45d89-b837-4823-8e9d-82bb96027530',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'fc3a666a-8ea9-f60e-fc50-c415898fda49',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1141&y=345&width=1935&height=941&uuid=43c2d2b3-a647-44f5-baa5-eb1553504971',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '9d797036-7e3b-de8f-9d13-d249791df2c8',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=719&y=797&width=757&height=826&uuid=bcd1e0e8-c6bd-4ea6-addc-746853672836',
+ additionalData: {
+ description: 'crop of damage view',
+ },
+ },
+ {
+ id: '23585679-dd88-b9fc-2332-f406daae95bb',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=463&y=3&width=1695&height=926&uuid=951e4ca7-78fe-4c88-b2da-ad66ea5a19a4',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'f958525f-3602-3369-f932-f02031241f2e',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1271&y=278&width=1295&height=297&uuid=62e1711e-2119-4a1b-9399-ed26fcf67f58',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '707c5d25-0878-8683-7016-ff5a0f5eaac4',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1149&y=453&width=1786&height=931&uuid=6db3ece5-ef73-446e-b224-8fa0abb038ee',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '123a4495-771b-8a5d-1250-e6ea703da61a',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=500&width=847&height=1135&uuid=898411ea-6207-4837-9d0e-b9c9498c27be',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '5ed2f756-0753-6abc-5eb8-5529007546fb',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=737&y=529&width=1432&height=1051&uuid=5b79a55e-69d7-4343-a3ce-8fd512d5058f',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'c5b13d52-feef-1524-c5db-9f2df9c93963',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1085&y=201&width=1180&height=272&uuid=bd9cdd26-6ac4-42c0-8af7-012cc61460fc',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '311115d4-789e-625e-317b-b7ab7fb84e19',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=120&width=796&height=302&uuid=2f0c7d32-1f45-466f-83a6-40362240c624',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '8d051e26-285e-30e7-8d6f-bc592f781ca0',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=74&width=807&height=792&uuid=ef876343-5944-443f-b0ef-19e2a5b21dfb',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'd14655ab-f662-3898-d12c-f7d4f14414df',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.690063_21731085-0378-40c7-b729-17a56c6aee55.jpg',
+ additionalData: {
+ description: 'rendering of detected parts',
+ },
+ },
+ {
+ id: 'ffee30b3-6d49-58ea-ff84-92cc6a6f74ad',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.709410_5f163233-6d20-4990-bd54-0cf001b052bc.jpg',
+ additionalData: {
+ description: 'rendering of detected damages',
+ },
+ },
+ {
+ id: '337b4153-f26e-59f9-3311-e32cf54875be',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=947&y=335&width=1216&height=535&uuid=fb9e798e-dd63-4bc8-ba76-70c565f93cea',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '325ba3da-5e3e-40d0-3231-01a559186c97',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1107&y=154&width=1452&height=413&uuid=c6142566-5af5-49f3-bfe3-56cc3e79b57e',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'f85c1c27-7bec-9d6c-f836-be587ccab12b',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=441&y=312&width=1412&height=1040&uuid=d03546db-19bc-45d3-822c-0dfb77ded67e',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '03a8e4a9-d417-c1fd-03c2-46d6d331edba',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=81&y=312&width=779&height=836&uuid=47a952b4-0931-4993-b993-bf589f0f20b4',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '4c2bf631-5213-7c6c-4c41-544e5535502b',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=508&y=0&width=1374&height=634&uuid=26a62e63-16f8-4861-90c5-eabc18a6adfa',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '2a5bdb7c-a086-b288-2a31-7903a7a09ecf',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=266&y=309&width=1605&height=1312&uuid=254f827d-79cb-45fb-9b11-037fda961c4c',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '8e49cf30-378e-1cf1-8e23-6d4f30a830b6',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=206&y=649&width=656&height=987&uuid=3e69cdc5-2371-4eb9-86ff-cedcfde8addc',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: 'ce9f0c5d-2b3e-d5b1-cef5-ae222c18f9f6',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1066&y=299&width=1795&height=847&uuid=8ace9da8-c7fa-4ed3-8671-4185fff3235b',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '02d8800f-7866-58aa-02b2-22707f4074ed',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1267&y=1019&width=1314&height=1055&uuid=93b4e516-ebf0-4434-83e3-1b60842006c7',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '79c36708-b835-5fdb-79a9-c577bf13739c',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=632&y=0&width=1260&height=346&uuid=98eb6dce-d25c-448e-9d1e-abe957165ce0',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '662bc970-70d1-83de-6641-6b0f77f7af99',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
+ path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1209&y=658&width=1637&height=980&uuid=5f32b9a4-4f03-41df-893a-5816fdde5030',
+ additionalData: {
+ description: 'crop of part view',
+ },
+ },
+ {
+ id: '00b314cf-c28d-d75e-00d9-b6b0c5abfb19',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '8ad728c4-90ee-7983-8abd-8abb97c855c4',
+ path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.672125_e3d7e8dc-ffa7-44cf-a19d-6b08b80b100c.jpg',
+ additionalData: {
+ description: 'rendering of detected parts',
+ },
+ },
+ {
+ id: 'b67370af-bedc-69ad-b619-d2d0b9fa45ea',
+ entityType: 'RENDERED_OUTPUT',
+ baseImageId: '8ad728c4-90ee-7983-8abd-8abb97c855c4',
+ path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.701367_a449d728-ceb7-49f1-994a-701ac8f4645f.jpg',
+ additionalData: {
+ description: 'rendering of detected damages',
+ },
+ },
+ ],
+ severityResults: [
+ {
+ id: 'ddf61a7f-8f66-65d9-dd9c-b8008840499e',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ entityType: 'SEVERITY_RESULT',
+ label: 'door_front_left',
+ isUserModified: false,
+ relatedItemId: '2d917d5d-6636-5ea7-2dfb-df22611072e0',
+ relatedItemType: 'part',
+ value: {
+ level: 3,
+ pricing: 282,
+ },
+ },
+ {
+ id: 'ca8b97ee-a50f-919b-cae1-3591a229bddc',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ entityType: 'SEVERITY_RESULT',
+ label: 'rocker_panel_left',
+ isUserModified: false,
+ relatedItemId: '56c74ad5-c077-1d08-56ad-e8aac751314f',
+ relatedItemType: 'part',
+ value: {
+ level: 1,
+ pricing: 264,
+ },
+ },
+ {
+ id: '5dfffeae-6b5f-a699-5d95-5cd16c798ade',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ entityType: 'SEVERITY_RESULT',
+ label: 'roof',
+ isUserModified: false,
+ relatedItemId: '833efbf2-dda5-8c31-8354-598dda83a076',
+ relatedItemType: 'part',
+ value: {
+ level: 1,
+ pricing: 480,
+ },
+ },
+ ],
+ tasks: [
+ {
+ id: 'fd605401-c5b6-8400-fd0a-f67ec290a847',
+ entityType: 'TASK',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ name: 'damage_detection',
+ status: 'DONE',
+ images: [
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ '8ad728c4-90ee-7983-8abd-8abb97c855c4',
+ ],
+ },
+ {
+ id: 'f3ce86b5-d3a8-27dc-f3a4-24cad48e0b9b',
+ entityType: 'TASK',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ name: 'compliances',
+ status: 'IN_PROGRESS',
+ images: [
+ '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
+ 'a842f4bf-5404-215b-a828-56c053220d1c',
+ '8ad728c4-90ee-7983-8abd-8abb97c855c4',
+ ],
+ },
+ {
+ id: '89216d57-0d6d-dd29-894b-cf280a4bf16e',
+ entityType: 'TASK',
+ inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
+ name: 'wheel_analysis',
+ status: 'DONE',
+ images: ['6a5e9a4c-8752-c1e6-6a34-38338074eda1'],
+ },
+ ],
+ vehicles: [
+ {
+ id: '051b7118-915f-4bec-0571-d367967967ab',
+ entityType: 'VEHICLE',
+ brand: 'Toyota',
+ model: 'Aygo',
+ plate: '',
+ vin: '1HGEJ8244YL06777',
+ color: null,
+ exteriorCleanliness: null,
+ interior_cleanliness: null,
+ dateOfCirculation: null,
+ duplicateKeys: null,
+ expertiseRequested: null,
+ carRegistration: null,
+ vehicleQuotation: null,
+ tradeInOffer: null,
+ ownerInfo: null,
+ additionalData: {
+ brand: {
+ confidence: 0.8455246090888977,
+ prediction: 'Toyota',
+ },
+ model: {
+ confidence: 0.8455246090888977,
+ prediction: 'Aygo',
+ },
+ plate: {
+ confidence: -1,
+ prediction: '',
+ },
+ vehicle_type: 'car',
+ },
+ },
+ ],
+ views: [
+ {
+ id: 'ab2f9850-0165-3458-ab45-3a2f0643181f',
+ entityType: 'VIEW',
+ elementId: '08ec65ae-3fb6-e4a3-0886-c7d13890c8e4',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 88,
+ yMin: 113,
+ width: 543,
+ height: 205,
+ },
+ polygons: [
+ [
+ [606, 137],
+ [598, 131],
+ [576, 126],
+ [491, 124],
+ [489, 122],
+ [369, 122],
+ [367, 124],
+ [332, 124],
+ [330, 126],
+ [289, 128],
+ [243, 146],
+ [202, 155],
+ [152, 176],
+ [134, 189],
+ [115, 211],
+ [113, 218],
+ [113, 253],
+ [119, 233],
+ [139, 213],
+ [150, 216],
+ [160, 233],
+ [167, 237],
+ [176, 237],
+ [187, 233],
+ [202, 233],
+ [210, 242],
+ [210, 246],
+ [204, 253],
+ [176, 268],
+ [171, 266],
+ [152, 266],
+ [141, 274],
+ [139, 300],
+ [128, 309],
+ [141, 309],
+ [143, 307],
+ [191, 309],
+ [193, 307],
+ [219, 307],
+ [221, 305],
+ [276, 305],
+ [278, 303],
+ [289, 303],
+ [291, 305],
+ [319, 305],
+ [321, 303],
+ [487, 303],
+ [489, 300],
+ [519, 300],
+ [526, 298],
+ [537, 290],
+ [552, 261],
+ [558, 237],
+ [582, 194],
+ [589, 168],
+ [606, 146],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['d8195eaa-8800-3c56-d873-fcd58f261011'],
+ },
+ {
+ id: '4d503f11-7b27-a0aa-4d3a-9d6e7c018ced',
+ entityType: 'VIEW',
+ elementId: 'cae4e3f4-c675-af59-ca8e-418bc153831e',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 552,
+ yMin: 139,
+ width: 244,
+ height: 158,
+ },
+ polygons: [
+ [
+ [563, 281],
+ [572, 290],
+ [576, 290],
+ [585, 281],
+ [598, 281],
+ [600, 279],
+ [632, 285],
+ [669, 283],
+ [702, 276],
+ [715, 276],
+ [743, 283],
+ [774, 283],
+ [785, 276],
+ [785, 263],
+ [776, 244],
+ [763, 224],
+ [743, 205],
+ [730, 196],
+ [713, 176],
+ [680, 155],
+ [665, 148],
+ [639, 146],
+ [632, 152],
+ [619, 189],
+ [600, 209],
+ [589, 229],
+ [569, 255],
+ [563, 272],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['0bd88b55-6e6f-d766-0bb2-292a6949fb21'],
+ },
+ {
+ id: 'afbadee2-e194-3b07-afd0-7c9de6b21740',
+ entityType: 'VIEW',
+ elementId: '299b478c-4091-0165-29f1-e5f347b72d22',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1630,
+ yMin: 330,
+ width: 66,
+ height: 59,
+ },
+ polygons: [
+ [
+ [1635, 335],
+ [1633, 342],
+ [1639, 353],
+ [1639, 361],
+ [1661, 385],
+ [1670, 388],
+ [1683, 383],
+ [1694, 370],
+ [1692, 361],
+ [1685, 355],
+ [1674, 351],
+ [1666, 340],
+ [1646, 333],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['4df4bd91-b34d-5da2-4d9e-1feeb46b71e5'],
+ },
+ {
+ id: '7145ebd3-58f4-2328-712f-49ac5fd20f6f',
+ entityType: 'VIEW',
+ elementId: 'fff6bd07-f76a-1597-ff9c-1f78f04c39d0',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1,
+ yMin: 203,
+ width: 66,
+ height: 45,
+ },
+ polygons: [
+ [
+ [65, 205],
+ [4, 244],
+ [8, 246],
+ [23, 233],
+ [43, 224],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['fd4a9f9b-7e8b-59e2-fd20-3de479ad75a5'],
+ },
+ {
+ id: 'b0b552b2-f1b6-1bcc-b0df-f0cdf690378b',
+ entityType: 'VIEW',
+ elementId: '833efbf2-dda5-8c31-8354-598dda83a076',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 158,
+ yMin: 45,
+ width: 973,
+ height: 258,
+ },
+ polygons: [
+ [
+ [202, 126],
+ [269, 122],
+ [271, 120],
+ [287, 120],
+ [317, 113],
+ [334, 113],
+ [363, 107],
+ [415, 107],
+ [417, 105],
+ [456, 105],
+ [471, 109],
+ [508, 111],
+ [539, 118],
+ [567, 118],
+ [569, 120],
+ [606, 122],
+ [628, 131],
+ [667, 137],
+ [698, 152],
+ [722, 170],
+ [765, 211],
+ [785, 224],
+ [948, 239],
+ [965, 246],
+ [998, 272],
+ [1026, 285],
+ [1041, 285],
+ [1050, 290],
+ [1065, 292],
+ [1054, 287],
+ [1059, 281],
+ [1078, 281],
+ [1028, 272],
+ [985, 233],
+ [983, 224],
+ [993, 216],
+ [1004, 216],
+ [1020, 222],
+ [1039, 220],
+ [1039, 218],
+ [1035, 218],
+ [1030, 213],
+ [1030, 207],
+ [1037, 200],
+ [1070, 198],
+ [1078, 189],
+ [1087, 185],
+ [1070, 170],
+ [1059, 165],
+ [1037, 172],
+ [1015, 163],
+ [998, 148],
+ [998, 139],
+ [987, 128],
+ [983, 118],
+ [965, 105],
+ [941, 96],
+ [880, 81],
+ [830, 78],
+ [800, 72],
+ [780, 72],
+ [778, 70],
+ [739, 68],
+ [717, 63],
+ [696, 63],
+ [676, 59],
+ [598, 59],
+ [595, 57],
+ [478, 59],
+ [476, 61],
+ [408, 65],
+ [406, 68],
+ [380, 70],
+ [334, 78],
+ [271, 96],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['015826ca-e188-6e37-0132-84b5e6ae4270'],
+ },
+ {
+ id: 'ca7c9b47-530d-6eb3-ca16-3938542b42f4',
+ entityType: 'VIEW',
+ elementId: '02ef856b-c9bf-3932-0285-2714ce991575',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1595,
+ yMin: 419,
+ width: 110,
+ height: 83,
+ },
+ polygons: [
+ [
+ [1696, 427],
+ [1692, 422],
+ [1685, 422],
+ [1674, 429],
+ [1659, 433],
+ [1620, 455],
+ [1605, 470],
+ [1600, 479],
+ [1602, 488],
+ [1618, 499],
+ [1635, 499],
+ [1666, 483],
+ [1687, 477],
+ [1700, 464],
+ [1700, 448],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['57d9e4d8-030a-c10e-57b3-46a7042ced49'],
+ },
+ {
+ id: 'aba1ed91-94b8-c4d3-abcb-4fee939ee894',
+ entityType: 'VIEW',
+ elementId: '3a4f083c-10fb-b483-3a25-aa4317dd98c4',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1026,
+ yMin: 183,
+ width: 136,
+ height: 43,
+ },
+ polygons: [
+ [
+ [1033, 213],
+ [1041, 218],
+ [1085, 216],
+ [1107, 224],
+ [1113, 224],
+ [1133, 216],
+ [1150, 213],
+ [1157, 198],
+ [1154, 192],
+ [1144, 185],
+ [1115, 185],
+ [1113, 187],
+ [1089, 185],
+ [1070, 200],
+ [1037, 202],
+ [1033, 207],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['1ffaa2e9-4a52-083a-1f90-00964d74247d'],
+ },
+ {
+ id: '25ac94fe-126f-2614-25c6-368115490a53',
+ entityType: 'VIEW',
+ elementId: '437b7a70-311c-6ac9-4311-d80f363a468e',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 368,
+ yMin: 397,
+ width: 169,
+ height: 38,
+ },
+ polygons: [
+ [
+ [376, 411],
+ [378, 418],
+ [389, 427],
+ [415, 433],
+ [458, 433],
+ [482, 427],
+ [517, 427],
+ [524, 425],
+ [530, 418],
+ [530, 411],
+ [526, 405],
+ [511, 401],
+ [463, 409],
+ [461, 407],
+ [432, 405],
+ [413, 398],
+ [389, 398],
+ [380, 403],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['cc7caa83-c7a7-375f-cc16-08fcc0811b18'],
+ },
+ {
+ id: '78340423-5a5d-6f33-785e-a65c5d7b4374',
+ entityType: 'VIEW',
+ elementId: '80bac861-01b6-9381-80d0-6a1e0690bfc6',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 216,
+ yMin: 506,
+ width: 315,
+ height: 231,
+ },
+ polygons: [
+ [
+ [503, 550],
+ [498, 544],
+ [496, 543],
+ [488, 542],
+ [487, 541],
+ [477, 542],
+ [475, 544],
+ [468, 537],
+ [468, 536],
+ [466, 534],
+ [465, 532],
+ [465, 529],
+ [464, 528],
+ [464, 525],
+ [461, 522],
+ [460, 518],
+ [451, 509],
+ [449, 509],
+ [443, 506],
+ [436, 506],
+ [434, 507],
+ [432, 509],
+ [424, 526],
+ [414, 536],
+ [402, 542],
+ [397, 547],
+ [386, 551],
+ [381, 556],
+ [378, 556],
+ [374, 558],
+ [368, 563],
+ [364, 565],
+ [361, 568],
+ [355, 570],
+ [351, 574],
+ [347, 575],
+ [341, 578],
+ [337, 582],
+ [335, 583],
+ [332, 583],
+ [331, 584],
+ [328, 584],
+ [324, 586],
+ [321, 586],
+ [317, 588],
+ [314, 591],
+ [312, 592],
+ [309, 592],
+ [301, 596],
+ [294, 603],
+ [292, 609],
+ [284, 618],
+ [282, 622],
+ [282, 624],
+ [280, 626],
+ [280, 627],
+ [273, 634],
+ [271, 638],
+ [269, 647],
+ [267, 649],
+ [267, 652],
+ [263, 659],
+ [263, 668],
+ [264, 670],
+ [273, 679],
+ [275, 680],
+ [283, 679],
+ [285, 677],
+ [292, 677],
+ [293, 678],
+ [299, 679],
+ [304, 684],
+ [304, 687],
+ [302, 689],
+ [301, 693],
+ [297, 697],
+ [291, 700],
+ [285, 706],
+ [281, 707],
+ [273, 715],
+ [271, 716],
+ [268, 716],
+ [266, 718],
+ [257, 720],
+ [254, 723],
+ [246, 724],
+ [245, 725],
+ [242, 725],
+ [241, 726],
+ [228, 726],
+ [227, 727],
+ [217, 727],
+ [216, 728],
+ [216, 730],
+ [218, 732],
+ [234, 732],
+ [235, 733],
+ [240, 733],
+ [244, 735],
+ [247, 735],
+ [250, 737],
+ [255, 737],
+ [256, 736],
+ [260, 736],
+ [261, 735],
+ [266, 735],
+ [268, 733],
+ [276, 731],
+ [281, 726],
+ [285, 725],
+ [286, 724],
+ [288, 724],
+ [295, 717],
+ [299, 715],
+ [301, 715],
+ [308, 708],
+ [312, 707],
+ [321, 698],
+ [324, 697],
+ [326, 695],
+ [327, 692],
+ [338, 681],
+ [340, 676],
+ [350, 666],
+ [350, 664],
+ [352, 661],
+ [353, 657],
+ [355, 656],
+ [365, 646],
+ [370, 644],
+ [372, 642],
+ [380, 638],
+ [383, 638],
+ [386, 636],
+ [389, 636],
+ [390, 635],
+ [393, 635],
+ [394, 634],
+ [397, 634],
+ [398, 633],
+ [402, 633],
+ [404, 631],
+ [407, 631],
+ [410, 629],
+ [413, 629],
+ [414, 628],
+ [419, 628],
+ [420, 627],
+ [424, 627],
+ [425, 626],
+ [430, 626],
+ [439, 621],
+ [443, 621],
+ [444, 620],
+ [447, 620],
+ [448, 619],
+ [452, 619],
+ [453, 618],
+ [462, 617],
+ [463, 616],
+ [466, 616],
+ [467, 615],
+ [470, 615],
+ [471, 614],
+ [474, 614],
+ [475, 613],
+ [482, 613],
+ [483, 612],
+ [487, 612],
+ [488, 613],
+ [492, 613],
+ [493, 614],
+ [496, 614],
+ [497, 615],
+ [500, 615],
+ [501, 616],
+ [503, 616],
+ [504, 617],
+ [508, 617],
+ [509, 618],
+ [523, 618],
+ [524, 617],
+ [527, 617],
+ [531, 614],
+ [529, 611],
+ [529, 606],
+ [526, 602],
+ [526, 601],
+ [513, 588],
+ [513, 585],
+ [512, 584],
+ [512, 576],
+ [511, 575],
+ [511, 570],
+ [506, 561],
+ [505, 554],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['5a8a4569-ae52-aeb3-5ae0-e716a97482f4'],
+ },
+ {
+ id: '667da482-f302-4e5d-6617-06fdf424621a',
+ entityType: 'VIEW',
+ elementId: '535c6a2a-f1f3-f91b-5336-c855f6d5d55c',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1290,
+ yMin: 287,
+ width: 23,
+ height: 9,
+ },
+ polygons: [
+ [
+ [1291, 287],
+ [1291, 294],
+ [1298, 296],
+ [1313, 294],
+ [1311, 287],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['2760d5fe-df9e-bb67-270a-7781d8b89720'],
+ },
+ {
+ id: '3bdc7c4e-75cc-4386-3bb6-de3172ea6fc1',
+ entityType: 'VIEW',
+ elementId: 'c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 978,
+ yMin: 215,
+ width: 138,
+ height: 59,
+ },
+ polygons: [
+ [
+ [985, 224],
+ [987, 233],
+ [1000, 246],
+ [1033, 272],
+ [1041, 272],
+ [1054, 266],
+ [1076, 266],
+ [1087, 259],
+ [1087, 255],
+ [1094, 248],
+ [1100, 246],
+ [1111, 235],
+ [1109, 226],
+ [1085, 218],
+ [1041, 220],
+ [1026, 224],
+ [1020, 224],
+ [1004, 218],
+ [993, 218],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['1567aac5-c8e4-7584-150d-08bacfc259c3'],
+ },
+ {
+ id: '47ba5f70-96de-6fad-47d0-fd0f91f843ea',
+ entityType: 'VIEW',
+ elementId: '0f6665a0-47fe-6e8e-0f0c-c7df40d842c9',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1517,
+ yMin: 380,
+ width: 19,
+ height: 26,
+ },
+ polygons: [
+ [
+ [1526, 381],
+ [1518, 388],
+ [1518, 398],
+ [1529, 405],
+ [1535, 396],
+ [1535, 390],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['1df49927-6747-6719-1d9e-3b5860614b5e'],
+ },
+ {
+ id: 'd838da37-94f5-586a-d852-784893d3742d',
+ entityType: 'VIEW',
+ elementId: 'a6b41fba-df6f-2d6f-a6de-bdc5d8490128',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 839,
+ yMin: 588,
+ width: 468,
+ height: 433,
+ },
+ polygons: [
+ [
+ [928, 647],
+ [889, 688],
+ [870, 723],
+ [861, 747],
+ [861, 792],
+ [870, 821],
+ [872, 845],
+ [909, 912],
+ [924, 930],
+ [946, 949],
+ [985, 973],
+ [1037, 993],
+ [1074, 1001],
+ [1120, 1001],
+ [1122, 999],
+ [1146, 997],
+ [1194, 980],
+ [1218, 960],
+ [1241, 936],
+ [1263, 908],
+ [1278, 871],
+ [1287, 825],
+ [1287, 792],
+ [1278, 775],
+ [1270, 747],
+ [1252, 721],
+ [1244, 712],
+ [1231, 688],
+ [1189, 649],
+ [1170, 636],
+ [1148, 625],
+ [1122, 616],
+ [1087, 612],
+ [1085, 610],
+ [1037, 607],
+ [1035, 610],
+ [1022, 610],
+ [989, 616],
+ [952, 631],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['b69e0f13-edf3-0474-b6f4-ad6cead52833'],
+ },
+ {
+ id: 'f5adeae9-5e35-ec2d-f5c7-48965913c06a',
+ entityType: 'VIEW',
+ elementId: 'bf94b068-56d5-7a8a-bffe-121751f356cd',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1355,
+ yMin: 345,
+ width: 366,
+ height: 596,
+ },
+ polygons: [
+ [
+ [1685, 398],
+ [1681, 398],
+ [1666, 409],
+ [1659, 409],
+ [1635, 422],
+ [1620, 422],
+ [1616, 414],
+ [1631, 398],
+ [1637, 396],
+ [1644, 390],
+ [1631, 377],
+ [1624, 379],
+ [1609, 372],
+ [1598, 372],
+ [1576, 383],
+ [1563, 394],
+ [1559, 394],
+ [1548, 405],
+ [1561, 414],
+ [1576, 416],
+ [1589, 425],
+ [1589, 431],
+ [1583, 446],
+ [1563, 457],
+ [1542, 464],
+ [1524, 475],
+ [1526, 477],
+ [1537, 477],
+ [1559, 472],
+ [1568, 475],
+ [1576, 481],
+ [1579, 490],
+ [1574, 494],
+ [1548, 483],
+ [1535, 488],
+ [1535, 496],
+ [1555, 514],
+ [1552, 520],
+ [1546, 520],
+ [1529, 503],
+ [1518, 503],
+ [1502, 509],
+ [1465, 507],
+ [1457, 509],
+ [1452, 514],
+ [1452, 522],
+ [1479, 544],
+ [1492, 549],
+ [1502, 549],
+ [1509, 557],
+ [1505, 566],
+ [1498, 570],
+ [1474, 568],
+ [1463, 573],
+ [1457, 579],
+ [1457, 583],
+ [1463, 588],
+ [1470, 586],
+ [1500, 588],
+ [1515, 592],
+ [1539, 605],
+ [1552, 601],
+ [1563, 581],
+ [1576, 601],
+ [1574, 627],
+ [1581, 642],
+ [1589, 649],
+ [1607, 653],
+ [1633, 638],
+ [1642, 638],
+ [1650, 651],
+ [1650, 660],
+ [1635, 673],
+ [1624, 675],
+ [1622, 673],
+ [1605, 673],
+ [1583, 684],
+ [1566, 697],
+ [1555, 699],
+ [1546, 697],
+ [1529, 686],
+ [1513, 690],
+ [1507, 699],
+ [1526, 718],
+ [1526, 729],
+ [1515, 745],
+ [1515, 751],
+ [1520, 758],
+ [1552, 755],
+ [1572, 768],
+ [1583, 779],
+ [1585, 786],
+ [1583, 792],
+ [1570, 801],
+ [1561, 803],
+ [1555, 810],
+ [1537, 803],
+ [1529, 803],
+ [1524, 810],
+ [1522, 821],
+ [1522, 836],
+ [1526, 845],
+ [1524, 851],
+ [1513, 860],
+ [1457, 858],
+ [1448, 862],
+ [1435, 875],
+ [1431, 884],
+ [1405, 908],
+ [1396, 912],
+ [1387, 912],
+ [1372, 906],
+ [1372, 910],
+ [1378, 914],
+ [1394, 914],
+ [1424, 906],
+ [1448, 895],
+ [1452, 890],
+ [1476, 882],
+ [1513, 860],
+ [1561, 845],
+ [1594, 827],
+ [1609, 814],
+ [1633, 786],
+ [1637, 777],
+ [1672, 745],
+ [1689, 721],
+ [1703, 692],
+ [1703, 666],
+ [1694, 644],
+ [1694, 607],
+ [1700, 597],
+ [1700, 583],
+ [1692, 560],
+ [1692, 546],
+ [1694, 544],
+ [1692, 522],
+ [1694, 520],
+ [1694, 483],
+ [1698, 466],
+ [1703, 459],
+ [1703, 446],
+ [1705, 444],
+ [1705, 420],
+ [1694, 405],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['fc3a666a-8ea9-f60e-fc50-c415898fda49'],
+ },
+ {
+ id: '3179a7a6-8703-f8dc-3113-05d98025d49b',
+ entityType: 'VIEW',
+ elementId: 'c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 731,
+ yMin: 797,
+ width: 14,
+ height: 29,
+ },
+ polygons: [
+ [
+ [736, 819],
+ [734, 820],
+ [731, 820],
+ [731, 825],
+ [732, 826],
+ [736, 826],
+ [738, 824],
+ [738, 821],
+ ],
+ [
+ [731, 798],
+ [732, 802],
+ [737, 807],
+ [741, 809],
+ [745, 807],
+ [745, 801],
+ [741, 797],
+ [732, 797],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['9d797036-7e3b-de8f-9d13-d249791df2c8'],
+ },
+ {
+ id: '72d12f4f-8de3-2ed7-72bb-8d308ac50290',
+ entityType: 'VIEW',
+ elementId: 'ce9382ec-d9f3-17df-cef9-2093ded53b98',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 463,
+ yMin: 102,
+ width: 1232,
+ height: 725,
+ },
+ polygons: [
+ [
+ [519, 760],
+ [717, 766],
+ [761, 775],
+ [776, 782],
+ [791, 795],
+ [789, 784],
+ [793, 773],
+ [793, 745],
+ [806, 677],
+ [815, 651],
+ [826, 629],
+ [843, 605],
+ [876, 573],
+ [900, 555],
+ [930, 540],
+ [974, 525],
+ [1000, 522],
+ [1015, 518],
+ [1076, 518],
+ [1091, 522],
+ [1109, 522],
+ [1128, 527],
+ [1181, 544],
+ [1237, 573],
+ [1272, 601],
+ [1285, 605],
+ [1372, 536],
+ [1398, 518],
+ [1450, 494],
+ [1494, 481],
+ [1518, 477],
+ [1542, 462],
+ [1563, 455],
+ [1581, 446],
+ [1587, 431],
+ [1587, 425],
+ [1576, 418],
+ [1561, 416],
+ [1546, 405],
+ [1561, 388],
+ [1566, 388],
+ [1598, 370],
+ [1609, 370],
+ [1624, 377],
+ [1631, 374],
+ [1626, 359],
+ [1620, 353],
+ [1618, 346],
+ [1631, 342],
+ [1633, 335],
+ [1639, 331],
+ [1629, 318],
+ [1620, 314],
+ [1594, 309],
+ [1572, 300],
+ [1561, 298],
+ [1520, 300],
+ [1494, 276],
+ [1483, 272],
+ [1459, 270],
+ [1457, 274],
+ [1446, 281],
+ [1433, 281],
+ [1426, 274],
+ [1426, 270],
+ [1437, 259],
+ [1428, 253],
+ [1415, 255],
+ [1413, 266],
+ [1392, 287],
+ [1361, 305],
+ [1355, 305],
+ [1339, 311],
+ [1322, 311],
+ [1300, 316],
+ [1272, 316],
+ [1259, 311],
+ [1248, 311],
+ [1235, 296],
+ [1235, 281],
+ [1231, 274],
+ [1222, 274],
+ [1211, 281],
+ [1207, 287],
+ [1209, 294],
+ [1207, 303],
+ [1100, 303],
+ [1061, 292],
+ [1044, 292],
+ [1020, 285],
+ [998, 274],
+ [965, 248],
+ [948, 242],
+ [785, 226],
+ [765, 213],
+ [709, 161],
+ [667, 139],
+ [643, 135],
+ [639, 144],
+ [659, 144],
+ [680, 152],
+ [713, 174],
+ [730, 194],
+ [743, 202],
+ [765, 224],
+ [778, 244],
+ [787, 263],
+ [787, 276],
+ [774, 285],
+ [743, 285],
+ [715, 279],
+ [702, 279],
+ [669, 285],
+ [643, 285],
+ [641, 287],
+ [600, 281],
+ [598, 283],
+ [585, 283],
+ [578, 287],
+ [578, 292],
+ [582, 292],
+ [595, 305],
+ [600, 320],
+ [595, 385],
+ [593, 388],
+ [591, 418],
+ [580, 468],
+ [578, 520],
+ [574, 536],
+ [572, 564],
+ [561, 605],
+ [561, 649],
+ [556, 657],
+ [545, 703],
+ [535, 723],
+ [528, 747],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['23585679-dd88-b9fc-2332-f406daae95bb'],
+ },
+ {
+ id: '260594c3-2b62-74c0-266f-36bc2c445887',
+ entityType: 'VIEW',
+ elementId: 'fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1275,
+ yMin: 278,
+ width: 16,
+ height: 19,
+ },
+ polygons: [
+ [
+ [1283, 279],
+ [1278, 283],
+ [1276, 292],
+ [1281, 296],
+ [1287, 296],
+ [1291, 285],
+ [1289, 281],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['f958525f-3602-3369-f932-f02031241f2e'],
+ },
+ {
+ id: '858717fe-a458-fbb3-85ed-b581a37ed7f4',
+ entityType: 'VIEW',
+ elementId: '640b0825-60c5-887d-6461-aa5a67e3a43a',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1269,
+ yMin: 453,
+ width: 397,
+ height: 478,
+ },
+ polygons: [
+ [
+ [1568, 477],
+ [1550, 475],
+ [1537, 479],
+ [1520, 477],
+ [1450, 496],
+ [1420, 512],
+ [1415, 512],
+ [1381, 531],
+ [1287, 605],
+ [1296, 623],
+ [1318, 644],
+ [1341, 679],
+ [1355, 708],
+ [1359, 712],
+ [1370, 745],
+ [1378, 760],
+ [1378, 779],
+ [1385, 801],
+ [1385, 823],
+ [1378, 843],
+ [1378, 880],
+ [1372, 899],
+ [1374, 906],
+ [1387, 910],
+ [1396, 910],
+ [1405, 906],
+ [1428, 884],
+ [1433, 875],
+ [1448, 860],
+ [1457, 856],
+ [1492, 856],
+ [1494, 858],
+ [1513, 858],
+ [1518, 856],
+ [1524, 845],
+ [1520, 836],
+ [1520, 821],
+ [1522, 810],
+ [1529, 801],
+ [1537, 801],
+ [1555, 808],
+ [1561, 801],
+ [1581, 792],
+ [1583, 786],
+ [1581, 779],
+ [1552, 758],
+ [1520, 760],
+ [1515, 755],
+ [1513, 745],
+ [1524, 729],
+ [1524, 718],
+ [1505, 699],
+ [1513, 688],
+ [1529, 684],
+ [1546, 694],
+ [1555, 697],
+ [1566, 694],
+ [1583, 681],
+ [1605, 671],
+ [1622, 671],
+ [1624, 673],
+ [1635, 671],
+ [1648, 660],
+ [1648, 651],
+ [1642, 640],
+ [1633, 640],
+ [1607, 655],
+ [1589, 651],
+ [1581, 644],
+ [1574, 636],
+ [1572, 627],
+ [1574, 601],
+ [1563, 583],
+ [1555, 601],
+ [1539, 607],
+ [1509, 592],
+ [1487, 588],
+ [1463, 590],
+ [1455, 583],
+ [1455, 579],
+ [1468, 568],
+ [1474, 566],
+ [1498, 568],
+ [1507, 560],
+ [1502, 551],
+ [1492, 551],
+ [1479, 546],
+ [1450, 522],
+ [1450, 514],
+ [1457, 507],
+ [1465, 505],
+ [1502, 507],
+ [1518, 501],
+ [1529, 501],
+ [1537, 507],
+ [1526, 496],
+ [1515, 492],
+ [1529, 483],
+ [1542, 479],
+ [1561, 479],
+ [1570, 485],
+ [1574, 485],
+ [1574, 481],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['707c5d25-0878-8683-7016-ff5a0f5eaac4'],
+ },
+ {
+ id: '461084a6-d7e8-4430-467a-26d9d0ce6877',
+ entityType: 'VIEW',
+ elementId: '56c74ad5-c077-1d08-56ad-e8aac751314f',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 0,
+ yMin: 740,
+ width: 847,
+ height: 155,
+ },
+ polygons: [
+ [
+ [0, 762],
+ [0, 766],
+ [6, 771],
+ [21, 771],
+ [23, 773],
+ [50, 773],
+ [52, 775],
+ [87, 777],
+ [102, 782],
+ [174, 786],
+ [176, 788],
+ [204, 788],
+ [219, 792],
+ [271, 795],
+ [274, 797],
+ [302, 799],
+ [334, 808],
+ [337, 812],
+ [350, 812],
+ [367, 816],
+ [465, 819],
+ [467, 823],
+ [548, 825],
+ [574, 832],
+ [589, 832],
+ [611, 838],
+ [632, 840],
+ [641, 845],
+ [661, 847],
+ [667, 851],
+ [687, 853],
+ [743, 871],
+ [750, 875],
+ [756, 875],
+ [789, 888],
+ [804, 886],
+ [806, 882],
+ [806, 866],
+ [791, 797],
+ [776, 784],
+ [761, 777],
+ [717, 768],
+ [558, 764],
+ [556, 762],
+ [498, 762],
+ [495, 760],
+ [437, 760],
+ [435, 758],
+ [378, 758],
+ [376, 755],
+ [317, 755],
+ [315, 753],
+ [256, 753],
+ [254, 751],
+ [195, 751],
+ [193, 749],
+ [134, 749],
+ [132, 747],
+ [54, 747],
+ [52, 749],
+ [30, 751],
+ [34, 755],
+ [26, 762],
+ [10, 755],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['123a4495-771b-8a5d-1250-e6ea703da61a'],
+ },
+ {
+ id: '8eab6f21-2c6b-59a7-8ec1-cd5e2b4d75e0',
+ entityType: 'VIEW',
+ elementId: '3ed12b36-252c-98ed-3ebb-8949220ab4aa',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 800,
+ yMin: 529,
+ width: 569,
+ height: 522,
+ },
+ polygons: [
+ [
+ [865, 638],
+ [843, 677],
+ [830, 714],
+ [828, 766],
+ [826, 768],
+ [826, 827],
+ [843, 871],
+ [865, 906],
+ [920, 967],
+ [948, 984],
+ [972, 1006],
+ [985, 1012],
+ [991, 1012],
+ [1011, 1021],
+ [1026, 1023],
+ [1035, 1028],
+ [1083, 1032],
+ [1100, 1036],
+ [1122, 1036],
+ [1146, 1030],
+ [1157, 1030],
+ [1178, 1017],
+ [1185, 1017],
+ [1209, 1004],
+ [1237, 999],
+ [1254, 993],
+ [1263, 982],
+ [1287, 967],
+ [1313, 936],
+ [1322, 921],
+ [1324, 897],
+ [1344, 864],
+ [1344, 851],
+ [1339, 836],
+ [1337, 775],
+ [1328, 738],
+ [1315, 705],
+ [1315, 697],
+ [1320, 686],
+ [1318, 677],
+ [1302, 671],
+ [1283, 653],
+ [1274, 634],
+ [1257, 623],
+ [1237, 605],
+ [1183, 579],
+ [1146, 568],
+ [1137, 568],
+ [1096, 553],
+ [1020, 555],
+ [1017, 557],
+ [1000, 557],
+ [998, 560],
+ [976, 562],
+ [924, 586],
+ [896, 607],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['5ed2f756-0753-6abc-5eb8-5529007546fb'],
+ },
+ {
+ id: '71dbdd16-1c49-864a-71b1-7f691b6faa0d',
+ entityType: 'VIEW',
+ elementId: 'f78b2228-70ce-da31-f7e1-805777e8f676',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1085,
+ yMin: 213,
+ width: 95,
+ height: 47,
+ },
+ polygons: [
+ [
+ [1174, 235],
+ [1161, 229],
+ [1150, 216],
+ [1141, 216],
+ [1111, 226],
+ [1113, 235],
+ [1100, 248],
+ [1094, 250],
+ [1089, 255],
+ [1089, 259],
+ [1109, 255],
+ [1122, 255],
+ [1124, 257],
+ [1126, 255],
+ [1146, 255],
+ [1154, 259],
+ [1167, 253],
+ [1176, 244],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['c5b13d52-feef-1524-c5db-9f2df9c93963'],
+ },
+ {
+ id: '8074a493-4434-7b65-801e-06ec43125722',
+ entityType: 'VIEW',
+ elementId: '68d766cb-9bbf-c196-68bd-c4b49c99edd1',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 552,
+ yMin: 125,
+ width: 244,
+ height: 172,
+ },
+ polygons: [
+ [
+ [563, 281],
+ [572, 290],
+ [576, 290],
+ [585, 281],
+ [598, 281],
+ [600, 279],
+ [632, 285],
+ [669, 283],
+ [702, 276],
+ [715, 276],
+ [743, 283],
+ [774, 283],
+ [785, 276],
+ [785, 263],
+ [776, 244],
+ [763, 224],
+ [743, 205],
+ [730, 196],
+ [713, 176],
+ [680, 155],
+ [665, 148],
+ [639, 146],
+ [637, 144],
+ [641, 135],
+ [628, 133],
+ [626, 155],
+ [602, 183],
+ [602, 187],
+ [585, 220],
+ [582, 233],
+ [565, 263],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['311115d4-789e-625e-317b-b7ab7fb84e19'],
+ },
+ {
+ id: '439bd668-c016-75e3-43f1-7417c73059a4',
+ entityType: 'VIEW',
+ elementId: '2d917d5d-6636-5ea7-2dfb-df22611072e0',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 0,
+ yMin: 74,
+ width: 657,
+ height: 718,
+ },
+ polygons: [
+ [
+ [626, 131],
+ [606, 124],
+ [569, 122],
+ [567, 120],
+ [539, 120],
+ [508, 113],
+ [471, 111],
+ [456, 107],
+ [363, 109],
+ [334, 115],
+ [317, 115],
+ [287, 122],
+ [271, 122],
+ [269, 124],
+ [197, 128],
+ [171, 139],
+ [158, 152],
+ [132, 161],
+ [115, 174],
+ [82, 192],
+ [52, 220],
+ [23, 235],
+ [8, 248],
+ [2, 244],
+ [0, 246],
+ [0, 760],
+ [8, 753],
+ [19, 755],
+ [26, 760],
+ [32, 755],
+ [28, 751],
+ [30, 749],
+ [52, 747],
+ [54, 745],
+ [132, 745],
+ [134, 747],
+ [193, 747],
+ [195, 749],
+ [254, 749],
+ [256, 751],
+ [267, 751],
+ [269, 742],
+ [282, 731],
+ [313, 714],
+ [328, 710],
+ [337, 712],
+ [341, 718],
+ [343, 753],
+ [495, 758],
+ [504, 760],
+ [532, 714],
+ [541, 697],
+ [548, 675],
+ [550, 653],
+ [558, 629],
+ [561, 592],
+ [567, 577],
+ [567, 566],
+ [569, 564],
+ [572, 536],
+ [576, 520],
+ [578, 468],
+ [589, 418],
+ [591, 388],
+ [593, 385],
+ [593, 368],
+ [598, 346],
+ [595, 311],
+ [593, 305],
+ [582, 294],
+ [572, 292],
+ [563, 285],
+ [561, 272],
+ [576, 237],
+ [580, 233],
+ [582, 220],
+ [600, 187],
+ [600, 183],
+ [624, 155],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['8d051e26-285e-30e7-8d6f-bc592f781ca0'],
+ },
+ {
+ id: '66937b3f-dc7a-f700-66f9-d940db5cdb47',
+ entityType: 'VIEW',
+ elementId: '4d9cb158-d7ed-c73e-4df6-1327d0cbeb79',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 947,
+ yMin: 417,
+ width: 269,
+ height: 36,
+ },
+ polygons: [
+ [
+ [959, 425],
+ [978, 432],
+ [994, 432],
+ [996, 433],
+ [1013, 433],
+ [1015, 435],
+ [1115, 438],
+ [1139, 447],
+ [1145, 447],
+ [1162, 452],
+ [1186, 452],
+ [1199, 448],
+ [1204, 445],
+ [1197, 438],
+ [1191, 438],
+ [1170, 432],
+ [1162, 432],
+ [1132, 425],
+ [1108, 425],
+ [1107, 423],
+ [1065, 422],
+ [1063, 420],
+ [1045, 420],
+ [1043, 418],
+ [1006, 420],
+ [1005, 422],
+ [976, 422],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['337b4153-f26e-59f9-3311-e32cf54875be'],
+ },
+ {
+ id: '058c0533-0a74-dcb3-05e6-a74c0d52f0f4',
+ entityType: 'VIEW',
+ elementId: 'fff6bd07-f76a-1597-ff9c-1f78f04c39d0',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1220,
+ yMin: 154,
+ width: 119,
+ height: 259,
+ },
+ polygons: [
+ [
+ [1227, 167],
+ [1226, 170],
+ [1231, 177],
+ [1237, 202],
+ [1259, 241],
+ [1266, 262],
+ [1284, 306],
+ [1284, 311],
+ [1293, 326],
+ [1299, 333],
+ [1306, 351],
+ [1333, 401],
+ [1335, 400],
+ [1331, 385],
+ [1333, 360],
+ [1330, 355],
+ [1331, 323],
+ [1299, 274],
+ [1293, 259],
+ [1269, 227],
+ [1249, 184],
+ [1234, 165],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['325ba3da-5e3e-40d0-3231-01a559186c97'],
+ },
+ {
+ id: '740607eb-7996-67f0-746c-a5947eb04bb7',
+ entityType: 'VIEW',
+ elementId: '299b478c-4091-0165-29f1-e5f347b72d22',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 441,
+ yMin: 367,
+ width: 971,
+ height: 618,
+ },
+ polygons: [
+ [
+ [524, 425],
+ [522, 442],
+ [520, 443],
+ [520, 462],
+ [517, 475],
+ [503, 504],
+ [497, 524],
+ [495, 552],
+ [490, 572],
+ [490, 594],
+ [485, 606],
+ [485, 616],
+ [503, 636],
+ [510, 649],
+ [519, 683],
+ [519, 699],
+ [520, 701],
+ [522, 721],
+ [530, 755],
+ [547, 795],
+ [549, 807],
+ [560, 818],
+ [567, 838],
+ [577, 857],
+ [602, 889],
+ [622, 907],
+ [653, 925],
+ [679, 932],
+ [691, 937],
+ [711, 939],
+ [726, 946],
+ [743, 946],
+ [755, 941],
+ [782, 942],
+ [795, 937],
+ [810, 937],
+ [832, 946],
+ [850, 946],
+ [870, 941],
+ [892, 942],
+ [894, 944],
+ [912, 944],
+ [936, 937],
+ [968, 937],
+ [969, 939],
+ [984, 939],
+ [1015, 949],
+ [1040, 951],
+ [1056, 957],
+ [1075, 956],
+ [1100, 944],
+ [1134, 946],
+ [1154, 937],
+ [1169, 936],
+ [1186, 924],
+ [1204, 915],
+ [1217, 912],
+ [1253, 894],
+ [1271, 874],
+ [1293, 828],
+ [1308, 812],
+ [1310, 803],
+ [1318, 788],
+ [1323, 771],
+ [1333, 755],
+ [1343, 718],
+ [1345, 699],
+ [1350, 686],
+ [1358, 648],
+ [1360, 619],
+ [1363, 609],
+ [1365, 586],
+ [1367, 584],
+ [1368, 530],
+ [1367, 529],
+ [1363, 482],
+ [1358, 468],
+ [1358, 462],
+ [1350, 432],
+ [1336, 410],
+ [1335, 417],
+ [1326, 425],
+ [1313, 432],
+ [1256, 448],
+ [1191, 455],
+ [1189, 457],
+ [1152, 460],
+ [1150, 462],
+ [1088, 463],
+ [1087, 465],
+ [1070, 465],
+ [1068, 467],
+ [986, 467],
+ [984, 468],
+ [902, 468],
+ [901, 467],
+ [867, 467],
+ [865, 465],
+ [827, 465],
+ [825, 463],
+ [778, 462],
+ [777, 460],
+ [725, 458],
+ [723, 457],
+ [708, 457],
+ [706, 455],
+ [693, 455],
+ [691, 453],
+ [678, 453],
+ [676, 452],
+ [644, 448],
+ [631, 442],
+ [626, 442],
+ [614, 437],
+ [574, 430],
+ [554, 422],
+ [549, 417],
+ [545, 405],
+ [549, 395],
+ [542, 400],
+ [537, 411],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['f85c1c27-7bec-9d6c-f836-be587ccab12b'],
+ },
+ {
+ id: 'be3948a9-777a-be37-be53-ead6705c9270',
+ entityType: 'VIEW',
+ elementId: '0f6665a0-47fe-6e8e-0f0c-c7df40d842c9',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 291,
+ yMin: 312,
+ width: 278,
+ height: 524,
+ },
+ polygons: [
+ [
+ [550, 336],
+ [535, 351],
+ [519, 378],
+ [507, 391],
+ [498, 406],
+ [493, 411],
+ [493, 415],
+ [455, 473],
+ [453, 480],
+ [436, 512],
+ [416, 534],
+ [408, 539],
+ [373, 572],
+ [363, 584],
+ [363, 592],
+ [358, 606],
+ [341, 616],
+ [333, 632],
+ [331, 641],
+ [326, 648],
+ [319, 666],
+ [319, 674],
+ [312, 689],
+ [309, 708],
+ [306, 715],
+ [304, 763],
+ [324, 783],
+ [334, 788],
+ [339, 788],
+ [346, 781],
+ [348, 763],
+ [349, 761],
+ [349, 746],
+ [363, 701],
+ [373, 679],
+ [383, 668],
+ [400, 663],
+ [420, 668],
+ [433, 674],
+ [468, 710],
+ [478, 726],
+ [487, 755],
+ [493, 768],
+ [500, 802],
+ [505, 812],
+ [508, 813],
+ [532, 813],
+ [547, 807],
+ [545, 795],
+ [529, 755],
+ [520, 721],
+ [519, 701],
+ [517, 699],
+ [517, 683],
+ [508, 649],
+ [502, 636],
+ [485, 619],
+ [483, 606],
+ [488, 594],
+ [488, 572],
+ [493, 552],
+ [495, 524],
+ [502, 504],
+ [515, 475],
+ [519, 462],
+ [519, 443],
+ [520, 442],
+ [522, 425],
+ [535, 411],
+ [540, 400],
+ [550, 391],
+ [555, 380],
+ [555, 356],
+ [557, 351],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['03a8e4a9-d417-c1fd-03c2-46d6d331edba'],
+ },
+ {
+ id: 'd64d13af-dd3c-cc9e-d627-b1d0da1ae0d9',
+ entityType: 'VIEW',
+ elementId: '3a4f083c-10fb-b483-3a25-aa4317dd98c4',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 508,
+ yMin: 138,
+ width: 866,
+ height: 344,
+ },
+ polygons: [
+ [
+ [547, 405],
+ [550, 417],
+ [564, 425],
+ [587, 432],
+ [599, 432],
+ [614, 435],
+ [626, 440],
+ [631, 440],
+ [644, 447],
+ [663, 448],
+ [658, 448],
+ [641, 442],
+ [627, 428],
+ [601, 427],
+ [599, 425],
+ [601, 417],
+ [624, 415],
+ [638, 410],
+ [644, 410],
+ [646, 411],
+ [691, 417],
+ [721, 423],
+ [733, 423],
+ [738, 427],
+ [740, 432],
+ [751, 443],
+ [748, 448],
+ [741, 450],
+ [666, 450],
+ [723, 455],
+ [725, 457],
+ [777, 458],
+ [778, 460],
+ [825, 462],
+ [827, 463],
+ [901, 465],
+ [902, 467],
+ [1068, 465],
+ [1070, 463],
+ [1087, 463],
+ [1088, 462],
+ [1150, 460],
+ [1152, 458],
+ [1179, 457],
+ [1204, 452],
+ [1232, 450],
+ [1234, 448],
+ [1256, 447],
+ [1305, 433],
+ [1326, 423],
+ [1333, 417],
+ [1335, 411],
+ [1335, 405],
+ [1328, 396],
+ [1315, 368],
+ [1311, 365],
+ [1298, 333],
+ [1291, 326],
+ [1283, 311],
+ [1283, 306],
+ [1264, 262],
+ [1258, 241],
+ [1236, 202],
+ [1229, 177],
+ [1224, 169],
+ [1172, 159],
+ [1124, 157],
+ [1122, 155],
+ [1080, 155],
+ [1078, 154],
+ [837, 154],
+ [835, 155],
+ [815, 155],
+ [814, 157],
+ [741, 157],
+ [740, 159],
+ [721, 159],
+ [720, 160],
+ [684, 160],
+ [668, 164],
+ [661, 169],
+ [638, 216],
+ [634, 219],
+ [629, 232],
+ [621, 244],
+ [604, 283],
+ [601, 286],
+ [591, 309],
+ [572, 343],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['4c2bf631-5213-7c6c-4c41-544e5535502b'],
+ },
+ {
+ id: '6805436b-c50b-756f-686f-e114c22d5928',
+ entityType: 'VIEW',
+ elementId: 'bf94b068-56d5-7a8a-bffe-121751f356cd',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 266,
+ yMin: 542,
+ width: 1339,
+ height: 537,
+ },
+ polygons: [
+ [
+ [1563, 723],
+ [1559, 715],
+ [1551, 666],
+ [1548, 654],
+ [1537, 636],
+ [1536, 627],
+ [1516, 587],
+ [1497, 567],
+ [1489, 567],
+ [1497, 594],
+ [1502, 601],
+ [1506, 612],
+ [1526, 622],
+ [1534, 636],
+ [1531, 653],
+ [1516, 669],
+ [1514, 674],
+ [1516, 676],
+ [1524, 676],
+ [1534, 681],
+ [1554, 703],
+ [1554, 716],
+ [1549, 726],
+ [1549, 735],
+ [1553, 740],
+ [1553, 761],
+ [1548, 773],
+ [1534, 785],
+ [1522, 783],
+ [1519, 778],
+ [1506, 731],
+ [1506, 723],
+ [1501, 710],
+ [1502, 721],
+ [1506, 728],
+ [1507, 751],
+ [1509, 753],
+ [1507, 785],
+ [1506, 787],
+ [1507, 808],
+ [1506, 810],
+ [1504, 832],
+ [1497, 855],
+ [1497, 864],
+ [1492, 877],
+ [1470, 909],
+ [1454, 925],
+ [1444, 932],
+ [1422, 952],
+ [1393, 967],
+ [1375, 964],
+ [1358, 956],
+ [1345, 944],
+ [1338, 930],
+ [1336, 910],
+ [1343, 892],
+ [1345, 879],
+ [1353, 848],
+ [1351, 830],
+ [1350, 825],
+ [1323, 820],
+ [1313, 817],
+ [1310, 813],
+ [1294, 828],
+ [1273, 874],
+ [1248, 899],
+ [1217, 914],
+ [1196, 920],
+ [1169, 937],
+ [1154, 939],
+ [1134, 947],
+ [1100, 946],
+ [1075, 957],
+ [1056, 959],
+ [1040, 952],
+ [1015, 951],
+ [984, 941],
+ [969, 941],
+ [968, 939],
+ [936, 939],
+ [912, 946],
+ [894, 946],
+ [892, 944],
+ [870, 942],
+ [850, 947],
+ [832, 947],
+ [810, 939],
+ [795, 939],
+ [782, 944],
+ [755, 942],
+ [743, 947],
+ [726, 947],
+ [711, 941],
+ [691, 939],
+ [679, 934],
+ [668, 932],
+ [653, 927],
+ [627, 912],
+ [601, 889],
+ [571, 848],
+ [559, 818],
+ [547, 808],
+ [532, 815],
+ [508, 815],
+ [507, 813],
+ [505, 837],
+ [510, 859],
+ [510, 887],
+ [515, 907],
+ [515, 925],
+ [510, 939],
+ [497, 954],
+ [475, 969],
+ [458, 974],
+ [445, 969],
+ [428, 957],
+ [393, 922],
+ [384, 907],
+ [364, 880],
+ [354, 853],
+ [349, 827],
+ [348, 776],
+ [348, 781],
+ [339, 790],
+ [334, 790],
+ [324, 785],
+ [304, 765],
+ [299, 780],
+ [299, 792],
+ [297, 793],
+ [299, 875],
+ [302, 887],
+ [304, 919],
+ [311, 946],
+ [319, 962],
+ [319, 971],
+ [329, 996],
+ [333, 1014],
+ [338, 1024],
+ [338, 1038],
+ [349, 1078],
+ [1512, 1078],
+ [1517, 1059],
+ [1526, 1041],
+ [1531, 1014],
+ [1536, 1004],
+ [1537, 994],
+ [1544, 982],
+ [1544, 977],
+ [1549, 967],
+ [1551, 954],
+ [1558, 932],
+ [1559, 915],
+ [1563, 910],
+ [1566, 813],
+ [1569, 797],
+ [1569, 781],
+ [1568, 780],
+ [1566, 751],
+ [1563, 738],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['2a5bdb7c-a086-b288-2a31-7903a7a09ecf'],
+ },
+ {
+ id: '65beb0eb-d1ea-7d19-65d4-1294d6cc515e',
+ entityType: 'VIEW',
+ elementId: '6d6b4bcd-e710-686c-6d01-e9b2e036442b',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 341,
+ yMin: 649,
+ width: 180,
+ height: 338,
+ },
+ polygons: [
+ [
+ [390, 666],
+ [378, 674],
+ [368, 693],
+ [351, 746],
+ [349, 805],
+ [351, 807],
+ [351, 827],
+ [356, 853],
+ [366, 880],
+ [386, 907],
+ [395, 922],
+ [428, 956],
+ [452, 971],
+ [462, 972],
+ [475, 967],
+ [497, 952],
+ [508, 939],
+ [514, 925],
+ [514, 907],
+ [508, 887],
+ [508, 859],
+ [503, 837],
+ [505, 813],
+ [498, 802],
+ [492, 768],
+ [485, 755],
+ [477, 726],
+ [462, 703],
+ [433, 676],
+ [410, 666],
+ [401, 666],
+ [400, 664],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['8e49cf30-378e-1cf1-8e23-6d4f30a830b6'],
+ },
+ {
+ id: '5084700a-043a-6074-50ee-d275031c4c33',
+ entityType: 'VIEW',
+ elementId: 'a2bd7491-a911-ffdb-a2d7-d6eeae37d39c',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1297,
+ yMin: 299,
+ width: 267,
+ height: 548,
+ },
+ polygons: [
+ [
+ [1333, 324],
+ [1331, 355],
+ [1335, 360],
+ [1335, 375],
+ [1333, 376],
+ [1335, 396],
+ [1336, 398],
+ [1335, 403],
+ [1351, 432],
+ [1367, 492],
+ [1367, 512],
+ [1368, 514],
+ [1368, 529],
+ [1370, 530],
+ [1368, 584],
+ [1367, 586],
+ [1365, 609],
+ [1362, 619],
+ [1360, 648],
+ [1351, 686],
+ [1346, 699],
+ [1345, 718],
+ [1335, 755],
+ [1325, 771],
+ [1320, 788],
+ [1311, 803],
+ [1310, 812],
+ [1313, 815],
+ [1323, 818],
+ [1350, 823],
+ [1355, 815],
+ [1368, 770],
+ [1393, 720],
+ [1408, 703],
+ [1442, 679],
+ [1462, 671],
+ [1475, 671],
+ [1482, 674],
+ [1492, 684],
+ [1499, 701],
+ [1499, 706],
+ [1506, 716],
+ [1507, 731],
+ [1521, 778],
+ [1526, 783],
+ [1534, 783],
+ [1546, 773],
+ [1551, 761],
+ [1551, 740],
+ [1548, 735],
+ [1548, 726],
+ [1553, 716],
+ [1553, 703],
+ [1534, 683],
+ [1524, 678],
+ [1516, 678],
+ [1512, 674],
+ [1514, 669],
+ [1529, 653],
+ [1532, 641],
+ [1531, 631],
+ [1526, 624],
+ [1507, 616],
+ [1504, 612],
+ [1501, 601],
+ [1496, 594],
+ [1487, 567],
+ [1496, 566],
+ [1482, 552],
+ [1454, 530],
+ [1437, 504],
+ [1392, 415],
+ [1363, 375],
+ [1355, 358],
+ [1343, 343],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['ce9f0c5d-2b3e-d5b1-cef5-ae222c18f9f6'],
+ },
+ {
+ id: '0383f405-66a7-0af0-03e9-567a618126b7',
+ entityType: 'VIEW',
+ elementId: '17fd8970-6229-d165-1797-2b0f650ffd22',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1267,
+ yMin: 1019,
+ width: 47,
+ height: 36,
+ },
+ polygons: [
+ [
+ [1269, 1031],
+ [1269, 1036],
+ [1278, 1044],
+ [1289, 1048],
+ [1301, 1054],
+ [1306, 1054],
+ [1310, 1051],
+ [1313, 1038],
+ [1310, 1029],
+ [1305, 1024],
+ [1289, 1021],
+ [1276, 1024],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['02d8800f-7866-58aa-02b2-22707f4074ed'],
+ },
+ {
+ id: '29ed50ba-aa7d-351a-2987-f2c5ad5b195d',
+ entityType: 'VIEW',
+ elementId: '833efbf2-dda5-8c31-8354-598dda83a076',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 632,
+ yMin: 49,
+ width: 628,
+ height: 123,
+ },
+ polygons: [
+ [
+ [661, 155],
+ [661, 162],
+ [666, 164],
+ [684, 159],
+ [720, 159],
+ [721, 157],
+ [740, 157],
+ [741, 155],
+ [814, 155],
+ [815, 154],
+ [835, 154],
+ [837, 152],
+ [1078, 152],
+ [1080, 154],
+ [1122, 154],
+ [1124, 155],
+ [1172, 157],
+ [1174, 159],
+ [1204, 162],
+ [1219, 167],
+ [1226, 167],
+ [1232, 164],
+ [1229, 150],
+ [1224, 140],
+ [1204, 112],
+ [1187, 95],
+ [1167, 82],
+ [1135, 68],
+ [1119, 65],
+ [1105, 65],
+ [1103, 63],
+ [1068, 62],
+ [1067, 60],
+ [1036, 60],
+ [1035, 58],
+ [1011, 58],
+ [1010, 57],
+ [922, 57],
+ [921, 55],
+ [849, 57],
+ [847, 58],
+ [835, 58],
+ [822, 62],
+ [807, 62],
+ [772, 68],
+ [762, 68],
+ [731, 77],
+ [718, 83],
+ [701, 98],
+ [669, 139],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['79c36708-b835-5fdb-79a9-c577bf13739c'],
+ },
+ {
+ id: 'e7a16ffb-a22c-8705-e7cb-cd84a50aab42',
+ entityType: 'VIEW',
+ elementId: '4c70355e-6197-3c0b-4c1a-972166b1104c',
+ imageRegion: {
+ specification: {
+ boundingBox: {
+ xMin: 1330,
+ yMin: 658,
+ width: 186,
+ height: 322,
+ },
+ polygons: [
+ [
+ [1475, 673],
+ [1455, 674],
+ [1442, 681],
+ [1408, 704],
+ [1400, 713],
+ [1390, 728],
+ [1367, 778],
+ [1358, 810],
+ [1351, 823],
+ [1355, 848],
+ [1346, 879],
+ [1345, 892],
+ [1338, 910],
+ [1338, 922],
+ [1340, 930],
+ [1346, 944],
+ [1358, 954],
+ [1375, 962],
+ [1393, 966],
+ [1422, 951],
+ [1469, 909],
+ [1489, 880],
+ [1494, 869],
+ [1497, 847],
+ [1504, 823],
+ [1504, 810],
+ [1506, 808],
+ [1504, 787],
+ [1506, 785],
+ [1507, 753],
+ [1506, 751],
+ [1504, 728],
+ [1501, 721],
+ [1496, 696],
+ [1491, 684],
+ [1482, 676],
+ ],
+ ],
+ },
+ },
+ renderedOutputs: ['662bc970-70d1-83de-6641-6b0f77f7af99'],
+ },
+ ],
+};
diff --git a/packages/public/network/test/api/inspection/mappers.test.ts b/packages/public/network/test/api/inspection/mappers.test.ts
new file mode 100644
index 000000000..58c5a0ac8
--- /dev/null
+++ b/packages/public/network/test/api/inspection/mappers.test.ts
@@ -0,0 +1,13 @@
+import data from './apiInspectionGet.data.json';
+import apiInspectionGetParsed from './apiInspectionGet.data';
+import { ApiInspectionGet } from '../../../src/api/models';
+import { mapApiInspectionGet } from '../../../src/api/inspection/mappers';
+
+describe('Inspection API Mappers', () => {
+ describe('ApiInspectionGet mapper', () => {
+ it('should properly map the ApiInspectionGet object', () => {
+ const result = mapApiInspectionGet(data as unknown as ApiInspectionGet);
+ expect(result).toEqual(apiInspectionGetParsed);
+ });
+ });
+});
diff --git a/packages/public/network/test/api/inspection/requests.test.ts b/packages/public/network/test/api/inspection/requests.test.ts
new file mode 100644
index 000000000..f3330d3f4
--- /dev/null
+++ b/packages/public/network/test/api/inspection/requests.test.ts
@@ -0,0 +1,40 @@
+jest.mock('../../../src/api/config', () => ({
+ getDefaultOptions: jest.fn(() => ({ prefixUrl: 'getDefaultOptionsTest' })),
+}));
+jest.mock('../../../src/api/inspection/mappers', () => ({
+ mapApiInspectionGet: jest.fn(() => ({ test: 'hello' })),
+}));
+
+import ky from 'ky';
+import { MonkActionType } from '@monkvision/common';
+import { getDefaultOptions } from '../../../src/api/config';
+import { getInspection } from '../../../src/api/inspection';
+import { mapApiInspectionGet } from '../../../src/api/inspection/mappers';
+
+const apiConfig = { apiDomain: 'apiDomain', authToken: 'authToken' };
+
+describe('Inspection requests', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('getInspection request', () => {
+ it('should make the proper API call and map the resulting response', async () => {
+ const id = 'test-inspection-id';
+ const result = await getInspection(id, apiConfig);
+ const response = await (ky.get as jest.Mock).mock.results[0].value;
+ const body = await response.json();
+
+ expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig);
+ expect(ky.get).toHaveBeenCalledWith(`inspections/${id}`, getDefaultOptions(apiConfig));
+ expect(result).toEqual({
+ action: {
+ type: MonkActionType.GOT_ONE_INSPECTION,
+ payload: mapApiInspectionGet(body),
+ },
+ response,
+ body,
+ });
+ });
+ });
+});
diff --git a/packages/public/network/test/api/react.test.ts b/packages/public/network/test/api/react.test.ts
index bb90fcd08..441e57e01 100644
--- a/packages/public/network/test/api/react.test.ts
+++ b/packages/public/network/test/api/react.test.ts
@@ -1,15 +1,18 @@
-jest.mock('@monkvision/common');
-jest.mock('../../src/api/requests', () => ({
+jest.mock('../../src/api/api', () => ({
MonkApi: {
getInspection: jest.fn(() =>
- Promise.resolve({ payload: { test: 'payload' }, axiosResponse: 'test' }),
+ Promise.resolve({ action: { test: 'getInspection' }, test: 'getInspection' }),
+ ),
+ addImage: jest.fn(() => Promise.resolve({ action: { test: 'addImage' }, test: 'addImage' })),
+ updateTaskStatus: jest.fn(() =>
+ Promise.resolve({ action: { test: 'updateTaskStatus' }, test: 'updateTaskStatus' }),
),
},
}));
import { renderHook } from '@testing-library/react-hooks';
-import { useMonkState, MonkActionType } from '@monkvision/common';
-import { MonkAPIConfig, useMonkApi, MonkApi } from '../../src';
+import { useMonkState } from '@monkvision/common';
+import { MonkApi, MonkAPIConfig, useMonkApi } from '../../src';
describe('Monk API React utilities', () => {
afterEach(() => {
@@ -25,19 +28,16 @@ describe('Monk API React utilities', () => {
expect(useMonkState).toHaveBeenCalledTimes(1);
const dispatchMock = (useMonkState as jest.Mock).mock.results[0].value.dispatch as jest.Mock;
+
Object.keys(MonkApi).forEach(async (requestKey, index) => {
dispatchMock.mockClear();
expect(typeof (result.current as any)[requestKey]).toBe('function');
+
const resultMock = await (result.current as any)[requestKey](index, index * 2);
const requestMock = MonkApi[requestKey as keyof typeof MonkApi] as jest.Mock;
expect(requestMock).toHaveBeenCalledWith(index, index * 2, config);
const requestResultMock = await requestMock.mock.results[0].value;
- expect(dispatchMock).toHaveBeenCalledWith(
- expect.objectContaining({
- type: MonkActionType.UPDATE_STATE,
- payload: requestResultMock.payload,
- }),
- );
+ expect(dispatchMock).toHaveBeenCalledWith(requestResultMock.action);
expect(resultMock).toBe(requestResultMock);
});
unmount();
diff --git a/packages/public/network/test/api/requests/inspections/getInspection.testdata.json b/packages/public/network/test/api/requests/inspections/getInspection.testdata.json
deleted file mode 100644
index c6c8568e2..000000000
--- a/packages/public/network/test/api/requests/inspections/getInspection.testdata.json
+++ /dev/null
@@ -1,9688 +0,0 @@
-{
- "accident_nature": null,
- "additional_data": {
- "damage_detection_version": "v2",
- "environment": {
- "custom_inspection": true
- },
- "use_dynamic_crops": true
- },
- "compliances": null,
- "created_at": "2023-12-13T13:27:09.065973+00:00",
- "creator_id": "google-oauth2|109722589040992162710",
- "damages": [
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_size_cm": 50.021743434811555,
- "damage_type": "scratch",
- "id": "80bac861-01b6-9381-80d0-6a1e0690bfc6",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "DAMAGE",
- "part_ids": [
- "2d917d5d-6636-5ea7-2dfb-df22611072e0"
- ],
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 22919.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=216&y=504&width=531&height=739&uuid=72c9a923-d181-4584-8d29-f6290a41d140"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_size_cm": 3.17945481147886,
- "damage_type": "scratch",
- "id": "c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "DAMAGE",
- "part_ids": [
- "56c74ad5-c077-1d08-56ad-e8aac751314f"
- ],
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 40.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=719&y=797&width=757&height=826&uuid=bcd1e0e8-c6bd-4ea6-addc-746853672836"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:17.816097+00:00",
- "created_by": "close_up",
- "damage_type": "paint_peeling",
- "id": "be6cb3ae-8bfd-5efb-be06-11d18cdb72bc",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "DAMAGE",
- "part_ids": [
- "833efbf2-dda5-8c31-8354-598dda83a076"
- ],
- "related_images": [
- {
- "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
- "base_image_type": "close_up",
- "image_type": "close_up",
- "mimetype": "image/jpeg",
- "object_type": "IMAGE",
- "order": 0,
- "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:18.033002+00:00",
- "created_by": "close_up",
- "damage_type": "misshape",
- "id": "3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "DAMAGE",
- "part_ids": [
- "833efbf2-dda5-8c31-8354-598dda83a076"
- ],
- "related_images": [
- {
- "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
- "base_image_type": "close_up",
- "image_type": "close_up",
- "mimetype": "image/jpeg",
- "object_type": "IMAGE",
- "order": 0,
- "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media"
- }
- ]
- }
- ],
- "deleted_at": null,
- "id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "images": [
- {
- "additional_data": {
- "category": "exterior",
- "sightId": "ffocus18-S3kgFOBb",
- "label": {
- "de": "Hinten Seitlich Niedrig Links",
- "en": "Rear Lateral Low Left",
- "fr": "Arrière Gauche Latéral - vue basse"
- }
- },
- "binary_size": 141539,
- "has_vehicle": true,
- "id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "image_height": 1080,
- "image_type": "beauty_shot",
- "image_width": 1920,
- "mimetype": "image/jpeg",
- "name": "rear_lateral_low_left.jpeg",
- "object_type": "IMAGE",
- "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg?generation=1702474029543236&alt=media",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "rendering of detected parts"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "87650e8f-b8c9-6c6b-870f-acf0bfef402c",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.688404_c49980a3-4c96-44ec-bdb5-76e25391efc3.jpg"
- },
- {
- "additional_data": {
- "description": "rendering of detected damages"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "d44c1e3d-fcab-dd30-d426-bc42fb8df177",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.710278_24b3ec1c-5282-4451-8985-afd589513069.jpg"
- }
- ],
- "viewpoint": {
- "confidence": 0.9847987294197083,
- "prediction": "left"
- },
- "views": [
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "08ec65ae-3fb6-e4a3-0886-c7d13890c8e4",
- "id": "ab2f9850-0165-3458-ab45-3a2f0643181f",
- "image_region": {
- "id": "2f8da631-b95a-f569-2fe7-044ebe7cd92e",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 205,
- "width": 543,
- "xmin": 88,
- "ymin": 113
- },
- "polygons": [
- [
- [
- 606,
- 137
- ],
- [
- 598,
- 131
- ],
- [
- 576,
- 126
- ],
- [
- 491,
- 124
- ],
- [
- 489,
- 122
- ],
- [
- 369,
- 122
- ],
- [
- 367,
- 124
- ],
- [
- 332,
- 124
- ],
- [
- 330,
- 126
- ],
- [
- 289,
- 128
- ],
- [
- 243,
- 146
- ],
- [
- 202,
- 155
- ],
- [
- 152,
- 176
- ],
- [
- 134,
- 189
- ],
- [
- 115,
- 211
- ],
- [
- 113,
- 218
- ],
- [
- 113,
- 253
- ],
- [
- 119,
- 233
- ],
- [
- 139,
- 213
- ],
- [
- 150,
- 216
- ],
- [
- 160,
- 233
- ],
- [
- 167,
- 237
- ],
- [
- 176,
- 237
- ],
- [
- 187,
- 233
- ],
- [
- 202,
- 233
- ],
- [
- 210,
- 242
- ],
- [
- 210,
- 246
- ],
- [
- 204,
- 253
- ],
- [
- 176,
- 268
- ],
- [
- 171,
- 266
- ],
- [
- 152,
- 266
- ],
- [
- 141,
- 274
- ],
- [
- 139,
- 300
- ],
- [
- 128,
- 309
- ],
- [
- 141,
- 309
- ],
- [
- 143,
- 307
- ],
- [
- 191,
- 309
- ],
- [
- 193,
- 307
- ],
- [
- 219,
- 307
- ],
- [
- 221,
- 305
- ],
- [
- 276,
- 305
- ],
- [
- 278,
- 303
- ],
- [
- 289,
- 303
- ],
- [
- 291,
- 305
- ],
- [
- 319,
- 305
- ],
- [
- 321,
- 303
- ],
- [
- 487,
- 303
- ],
- [
- 489,
- 300
- ],
- [
- 519,
- 300
- ],
- [
- 526,
- 298
- ],
- [
- 537,
- 290
- ],
- [
- 552,
- 261
- ],
- [
- 558,
- 237
- ],
- [
- 582,
- 194
- ],
- [
- 589,
- 168
- ],
- [
- 606,
- 146
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "d8195eaa-8800-3c56-d873-fcd58f261011",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=88&y=12&width=631&height=419&uuid=465f6cb3-fe92-4911-b219-4e09e37f2e4a"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "cae4e3f4-c675-af59-ca8e-418bc153831e",
- "id": "4d503f11-7b27-a0aa-4d3a-9d6e7c018ced",
- "image_region": {
- "id": "ed4041cc-5953-dd1d-ed2a-e3b35e75f15a",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 158,
- "width": 244,
- "xmin": 552,
- "ymin": 139
- },
- "polygons": [
- [
- [
- 563,
- 281
- ],
- [
- 572,
- 290
- ],
- [
- 576,
- 290
- ],
- [
- 585,
- 281
- ],
- [
- 598,
- 281
- ],
- [
- 600,
- 279
- ],
- [
- 632,
- 285
- ],
- [
- 669,
- 283
- ],
- [
- 702,
- 276
- ],
- [
- 715,
- 276
- ],
- [
- 743,
- 283
- ],
- [
- 774,
- 283
- ],
- [
- 785,
- 276
- ],
- [
- 785,
- 263
- ],
- [
- 776,
- 244
- ],
- [
- 763,
- 224
- ],
- [
- 743,
- 205
- ],
- [
- 730,
- 196
- ],
- [
- 713,
- 176
- ],
- [
- 680,
- 155
- ],
- [
- 665,
- 148
- ],
- [
- 639,
- 146
- ],
- [
- 632,
- 152
- ],
- [
- 619,
- 189
- ],
- [
- 600,
- 209
- ],
- [
- 589,
- 229
- ],
- [
- 569,
- 255
- ],
- [
- 563,
- 272
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "0bd88b55-6e6f-d766-0bb2-292a6949fb21",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=127&width=796&height=309&uuid=8ee0b33a-83d6-4d3f-9624-f333c2ab63f7"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "299b478c-4091-0165-29f1-e5f347b72d22",
- "id": "afbadee2-e194-3b07-afd0-7c9de6b21740",
- "image_region": {
- "id": "066fa03c-3975-d8f2-0605-02433e53f4b5",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 59,
- "width": 66,
- "xmin": 1630,
- "ymin": 330
- },
- "polygons": [
- [
- [
- 1635,
- 335
- ],
- [
- 1633,
- 342
- ],
- [
- 1639,
- 353
- ],
- [
- 1639,
- 361
- ],
- [
- 1661,
- 385
- ],
- [
- 1670,
- 388
- ],
- [
- 1683,
- 383
- ],
- [
- 1694,
- 370
- ],
- [
- 1692,
- 361
- ],
- [
- 1685,
- 355
- ],
- [
- 1674,
- 351
- ],
- [
- 1666,
- 340
- ],
- [
- 1646,
- 333
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "4df4bd91-b34d-5da2-4d9e-1feeb46b71e5",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1624&y=330&width=1702&height=389&uuid=f5422914-4b91-4c3f-af25-bbcf6ce39b70"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "fff6bd07-f76a-1597-ff9c-1f78f04c39d0",
- "id": "7145ebd3-58f4-2328-712f-49ac5fd20f6f",
- "image_region": {
- "id": "e88f242c-6f51-4f78-e8e5-86536877633f",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 45,
- "width": 66,
- "xmin": 1,
- "ymin": 203
- },
- "polygons": [
- [
- [
- 65,
- 205
- ],
- [
- 4,
- 244
- ],
- [
- 8,
- 246
- ],
- [
- 23,
- 233
- ],
- [
- 43,
- 224
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "fd4a9f9b-7e8b-59e2-fd20-3de479ad75a5",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1&y=201&width=67&height=250&uuid=b5bf1613-2d11-4b5d-81ff-25a8c37635f3"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "833efbf2-dda5-8c31-8354-598dda83a076",
- "id": "b0b552b2-f1b6-1bcc-b0df-f0cdf690378b",
- "image_region": {
- "id": "0c7155ae-2228-cda4-0c1b-f7d1250ee1e3",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 258,
- "width": 973,
- "xmin": 158,
- "ymin": 45
- },
- "polygons": [
- [
- [
- 202,
- 126
- ],
- [
- 269,
- 122
- ],
- [
- 271,
- 120
- ],
- [
- 287,
- 120
- ],
- [
- 317,
- 113
- ],
- [
- 334,
- 113
- ],
- [
- 363,
- 107
- ],
- [
- 415,
- 107
- ],
- [
- 417,
- 105
- ],
- [
- 456,
- 105
- ],
- [
- 471,
- 109
- ],
- [
- 508,
- 111
- ],
- [
- 539,
- 118
- ],
- [
- 567,
- 118
- ],
- [
- 569,
- 120
- ],
- [
- 606,
- 122
- ],
- [
- 628,
- 131
- ],
- [
- 667,
- 137
- ],
- [
- 698,
- 152
- ],
- [
- 722,
- 170
- ],
- [
- 765,
- 211
- ],
- [
- 785,
- 224
- ],
- [
- 948,
- 239
- ],
- [
- 965,
- 246
- ],
- [
- 998,
- 272
- ],
- [
- 1026,
- 285
- ],
- [
- 1041,
- 285
- ],
- [
- 1050,
- 290
- ],
- [
- 1065,
- 292
- ],
- [
- 1054,
- 287
- ],
- [
- 1059,
- 281
- ],
- [
- 1078,
- 281
- ],
- [
- 1028,
- 272
- ],
- [
- 985,
- 233
- ],
- [
- 983,
- 224
- ],
- [
- 993,
- 216
- ],
- [
- 1004,
- 216
- ],
- [
- 1020,
- 222
- ],
- [
- 1039,
- 220
- ],
- [
- 1039,
- 218
- ],
- [
- 1035,
- 218
- ],
- [
- 1030,
- 213
- ],
- [
- 1030,
- 207
- ],
- [
- 1037,
- 200
- ],
- [
- 1070,
- 198
- ],
- [
- 1078,
- 189
- ],
- [
- 1087,
- 185
- ],
- [
- 1070,
- 170
- ],
- [
- 1059,
- 165
- ],
- [
- 1037,
- 172
- ],
- [
- 1015,
- 163
- ],
- [
- 998,
- 148
- ],
- [
- 998,
- 139
- ],
- [
- 987,
- 128
- ],
- [
- 983,
- 118
- ],
- [
- 965,
- 105
- ],
- [
- 941,
- 96
- ],
- [
- 880,
- 81
- ],
- [
- 830,
- 78
- ],
- [
- 800,
- 72
- ],
- [
- 780,
- 72
- ],
- [
- 778,
- 70
- ],
- [
- 739,
- 68
- ],
- [
- 717,
- 63
- ],
- [
- 696,
- 63
- ],
- [
- 676,
- 59
- ],
- [
- 598,
- 59
- ],
- [
- 595,
- 57
- ],
- [
- 478,
- 59
- ],
- [
- 476,
- 61
- ],
- [
- 408,
- 65
- ],
- [
- 406,
- 68
- ],
- [
- 380,
- 70
- ],
- [
- 334,
- 78
- ],
- [
- 271,
- 96
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "015826ca-e188-6e37-0132-84b5e6ae4270",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=158&y=0&width=1131&height=538&uuid=67101981-8f78-46b5-a637-f1719056b000"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "02ef856b-c9bf-3932-0285-2714ce991575",
- "id": "ca7c9b47-530d-6eb3-ca16-3938542b42f4",
- "image_region": {
- "id": "101de385-7efa-78b0-1077-41fa79dc54f7",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 83,
- "width": 110,
- "xmin": 1595,
- "ymin": 419
- },
- "polygons": [
- [
- [
- 1696,
- 427
- ],
- [
- 1692,
- 422
- ],
- [
- 1685,
- 422
- ],
- [
- 1674,
- 429
- ],
- [
- 1659,
- 433
- ],
- [
- 1620,
- 455
- ],
- [
- 1605,
- 470
- ],
- [
- 1600,
- 479
- ],
- [
- 1602,
- 488
- ],
- [
- 1618,
- 499
- ],
- [
- 1635,
- 499
- ],
- [
- 1666,
- 483
- ],
- [
- 1687,
- 477
- ],
- [
- 1700,
- 464
- ],
- [
- 1700,
- 448
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "57d9e4d8-030a-c10e-57b3-46a7042ced49",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1595&y=419&width=1705&height=502&uuid=de52a81b-ffab-4999-84ae-90082a59e42e"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "3a4f083c-10fb-b483-3a25-aa4317dd98c4",
- "id": "aba1ed91-94b8-c4d3-abcb-4fee939ee894",
- "image_region": {
- "id": "d7332a17-018a-2558-d759-886806ac091f",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 43,
- "width": 136,
- "xmin": 1026,
- "ymin": 183
- },
- "polygons": [
- [
- [
- 1033,
- 213
- ],
- [
- 1041,
- 218
- ],
- [
- 1085,
- 216
- ],
- [
- 1107,
- 224
- ],
- [
- 1113,
- 224
- ],
- [
- 1133,
- 216
- ],
- [
- 1150,
- 213
- ],
- [
- 1157,
- 198
- ],
- [
- 1154,
- 192
- ],
- [
- 1144,
- 185
- ],
- [
- 1115,
- 185
- ],
- [
- 1113,
- 187
- ],
- [
- 1089,
- 185
- ],
- [
- 1070,
- 200
- ],
- [
- 1037,
- 202
- ],
- [
- 1033,
- 207
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "1ffaa2e9-4a52-083a-1f90-00964d74247d",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1026&y=154&width=1162&height=255&uuid=bc4222b3-d65e-4c38-a7d3-3f6886607e33"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "437b7a70-311c-6ac9-4311-d80f363a468e",
- "id": "25ac94fe-126f-2614-25c6-368115490a53",
- "image_region": {
- "id": "61d2ce60-c1f8-bde9-61b8-6c1fc6de91ae",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 38,
- "width": 169,
- "xmin": 368,
- "ymin": 397
- },
- "polygons": [
- [
- [
- 376,
- 411
- ],
- [
- 378,
- 418
- ],
- [
- 389,
- 427
- ],
- [
- 415,
- 433
- ],
- [
- 458,
- 433
- ],
- [
- 482,
- 427
- ],
- [
- 517,
- 427
- ],
- [
- 524,
- 425
- ],
- [
- 530,
- 418
- ],
- [
- 530,
- 411
- ],
- [
- 526,
- 405
- ],
- [
- 511,
- 401
- ],
- [
- 463,
- 409
- ],
- [
- 461,
- 407
- ],
- [
- 432,
- 405
- ],
- [
- 413,
- 398
- ],
- [
- 389,
- 398
- ],
- [
- 380,
- 403
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "cc7caa83-c7a7-375f-cc16-08fcc0811b18",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=368&y=353&width=537&height=479&uuid=e5054412-4407-4e4d-86cf-8c7d4720caf7"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "80bac861-01b6-9381-80d0-6a1e0690bfc6",
- "id": "78340423-5a5d-6f33-785e-a65c5d7b4374",
- "image_region": {
- "id": "c3cec615-59af-c49b-c3a4-646a5e89e8dc",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 231,
- "width": 315,
- "xmin": 216,
- "ymin": 506
- },
- "polygons": [
- [
- [
- 503,
- 550
- ],
- [
- 498,
- 544
- ],
- [
- 496,
- 543
- ],
- [
- 488,
- 542
- ],
- [
- 487,
- 541
- ],
- [
- 477,
- 542
- ],
- [
- 475,
- 544
- ],
- [
- 468,
- 537
- ],
- [
- 468,
- 536
- ],
- [
- 466,
- 534
- ],
- [
- 465,
- 532
- ],
- [
- 465,
- 529
- ],
- [
- 464,
- 528
- ],
- [
- 464,
- 525
- ],
- [
- 461,
- 522
- ],
- [
- 460,
- 518
- ],
- [
- 451,
- 509
- ],
- [
- 449,
- 509
- ],
- [
- 443,
- 506
- ],
- [
- 436,
- 506
- ],
- [
- 434,
- 507
- ],
- [
- 432,
- 509
- ],
- [
- 424,
- 526
- ],
- [
- 414,
- 536
- ],
- [
- 402,
- 542
- ],
- [
- 397,
- 547
- ],
- [
- 386,
- 551
- ],
- [
- 381,
- 556
- ],
- [
- 378,
- 556
- ],
- [
- 374,
- 558
- ],
- [
- 368,
- 563
- ],
- [
- 364,
- 565
- ],
- [
- 361,
- 568
- ],
- [
- 355,
- 570
- ],
- [
- 351,
- 574
- ],
- [
- 347,
- 575
- ],
- [
- 341,
- 578
- ],
- [
- 337,
- 582
- ],
- [
- 335,
- 583
- ],
- [
- 332,
- 583
- ],
- [
- 331,
- 584
- ],
- [
- 328,
- 584
- ],
- [
- 324,
- 586
- ],
- [
- 321,
- 586
- ],
- [
- 317,
- 588
- ],
- [
- 314,
- 591
- ],
- [
- 312,
- 592
- ],
- [
- 309,
- 592
- ],
- [
- 301,
- 596
- ],
- [
- 294,
- 603
- ],
- [
- 292,
- 609
- ],
- [
- 284,
- 618
- ],
- [
- 282,
- 622
- ],
- [
- 282,
- 624
- ],
- [
- 280,
- 626
- ],
- [
- 280,
- 627
- ],
- [
- 273,
- 634
- ],
- [
- 271,
- 638
- ],
- [
- 269,
- 647
- ],
- [
- 267,
- 649
- ],
- [
- 267,
- 652
- ],
- [
- 263,
- 659
- ],
- [
- 263,
- 668
- ],
- [
- 264,
- 670
- ],
- [
- 273,
- 679
- ],
- [
- 275,
- 680
- ],
- [
- 283,
- 679
- ],
- [
- 285,
- 677
- ],
- [
- 292,
- 677
- ],
- [
- 293,
- 678
- ],
- [
- 299,
- 679
- ],
- [
- 304,
- 684
- ],
- [
- 304,
- 687
- ],
- [
- 302,
- 689
- ],
- [
- 301,
- 693
- ],
- [
- 297,
- 697
- ],
- [
- 291,
- 700
- ],
- [
- 285,
- 706
- ],
- [
- 281,
- 707
- ],
- [
- 273,
- 715
- ],
- [
- 271,
- 716
- ],
- [
- 268,
- 716
- ],
- [
- 266,
- 718
- ],
- [
- 257,
- 720
- ],
- [
- 254,
- 723
- ],
- [
- 246,
- 724
- ],
- [
- 245,
- 725
- ],
- [
- 242,
- 725
- ],
- [
- 241,
- 726
- ],
- [
- 228,
- 726
- ],
- [
- 227,
- 727
- ],
- [
- 217,
- 727
- ],
- [
- 216,
- 728
- ],
- [
- 216,
- 730
- ],
- [
- 218,
- 732
- ],
- [
- 234,
- 732
- ],
- [
- 235,
- 733
- ],
- [
- 240,
- 733
- ],
- [
- 244,
- 735
- ],
- [
- 247,
- 735
- ],
- [
- 250,
- 737
- ],
- [
- 255,
- 737
- ],
- [
- 256,
- 736
- ],
- [
- 260,
- 736
- ],
- [
- 261,
- 735
- ],
- [
- 266,
- 735
- ],
- [
- 268,
- 733
- ],
- [
- 276,
- 731
- ],
- [
- 281,
- 726
- ],
- [
- 285,
- 725
- ],
- [
- 286,
- 724
- ],
- [
- 288,
- 724
- ],
- [
- 295,
- 717
- ],
- [
- 299,
- 715
- ],
- [
- 301,
- 715
- ],
- [
- 308,
- 708
- ],
- [
- 312,
- 707
- ],
- [
- 321,
- 698
- ],
- [
- 324,
- 697
- ],
- [
- 326,
- 695
- ],
- [
- 327,
- 692
- ],
- [
- 338,
- 681
- ],
- [
- 340,
- 676
- ],
- [
- 350,
- 666
- ],
- [
- 350,
- 664
- ],
- [
- 352,
- 661
- ],
- [
- 353,
- 657
- ],
- [
- 355,
- 656
- ],
- [
- 365,
- 646
- ],
- [
- 370,
- 644
- ],
- [
- 372,
- 642
- ],
- [
- 380,
- 638
- ],
- [
- 383,
- 638
- ],
- [
- 386,
- 636
- ],
- [
- 389,
- 636
- ],
- [
- 390,
- 635
- ],
- [
- 393,
- 635
- ],
- [
- 394,
- 634
- ],
- [
- 397,
- 634
- ],
- [
- 398,
- 633
- ],
- [
- 402,
- 633
- ],
- [
- 404,
- 631
- ],
- [
- 407,
- 631
- ],
- [
- 410,
- 629
- ],
- [
- 413,
- 629
- ],
- [
- 414,
- 628
- ],
- [
- 419,
- 628
- ],
- [
- 420,
- 627
- ],
- [
- 424,
- 627
- ],
- [
- 425,
- 626
- ],
- [
- 430,
- 626
- ],
- [
- 439,
- 621
- ],
- [
- 443,
- 621
- ],
- [
- 444,
- 620
- ],
- [
- 447,
- 620
- ],
- [
- 448,
- 619
- ],
- [
- 452,
- 619
- ],
- [
- 453,
- 618
- ],
- [
- 462,
- 617
- ],
- [
- 463,
- 616
- ],
- [
- 466,
- 616
- ],
- [
- 467,
- 615
- ],
- [
- 470,
- 615
- ],
- [
- 471,
- 614
- ],
- [
- 474,
- 614
- ],
- [
- 475,
- 613
- ],
- [
- 482,
- 613
- ],
- [
- 483,
- 612
- ],
- [
- 487,
- 612
- ],
- [
- 488,
- 613
- ],
- [
- 492,
- 613
- ],
- [
- 493,
- 614
- ],
- [
- 496,
- 614
- ],
- [
- 497,
- 615
- ],
- [
- 500,
- 615
- ],
- [
- 501,
- 616
- ],
- [
- 503,
- 616
- ],
- [
- 504,
- 617
- ],
- [
- 508,
- 617
- ],
- [
- 509,
- 618
- ],
- [
- 523,
- 618
- ],
- [
- 524,
- 617
- ],
- [
- 527,
- 617
- ],
- [
- 531,
- 614
- ],
- [
- 529,
- 611
- ],
- [
- 529,
- 606
- ],
- [
- 526,
- 602
- ],
- [
- 526,
- 601
- ],
- [
- 513,
- 588
- ],
- [
- 513,
- 585
- ],
- [
- 512,
- 584
- ],
- [
- 512,
- 576
- ],
- [
- 511,
- 575
- ],
- [
- 511,
- 570
- ],
- [
- 506,
- 561
- ],
- [
- 505,
- 554
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of damage view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "5a8a4569-ae52-aeb3-5ae0-e716a97482f4",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=216&y=504&width=531&height=739&uuid=72c9a923-d181-4584-8d29-f6290a41d140"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "535c6a2a-f1f3-f91b-5336-c855f6d5d55c",
- "id": "667da482-f302-4e5d-6617-06fdf424621a",
- "image_region": {
- "id": "d9fa80fc-f40d-a5cb-d990-2283f32b898c",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 9,
- "width": 23,
- "xmin": 1290,
- "ymin": 287
- },
- "polygons": [
- [
- [
- 1291,
- 287
- ],
- [
- 1291,
- 294
- ],
- [
- 1298,
- 296
- ],
- [
- 1313,
- 294
- ],
- [
- 1311,
- 287
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "2760d5fe-df9e-bb67-270a-7781d8b89720",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1290&y=283&width=1313&height=300&uuid=79754efd-a945-4c92-b1b2-48b1ab05b845"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a",
- "id": "3bdc7c4e-75cc-4386-3bb6-de3172ea6fc1",
- "image_region": {
- "id": "74ae4157-385a-4d1a-74c4-e3283f7c615d",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 59,
- "width": 138,
- "xmin": 978,
- "ymin": 215
- },
- "polygons": [
- [
- [
- 985,
- 224
- ],
- [
- 987,
- 233
- ],
- [
- 1000,
- 246
- ],
- [
- 1033,
- 272
- ],
- [
- 1041,
- 272
- ],
- [
- 1054,
- 266
- ],
- [
- 1076,
- 266
- ],
- [
- 1087,
- 259
- ],
- [
- 1087,
- 255
- ],
- [
- 1094,
- 248
- ],
- [
- 1100,
- 246
- ],
- [
- 1111,
- 235
- ],
- [
- 1109,
- 226
- ],
- [
- 1085,
- 218
- ],
- [
- 1041,
- 220
- ],
- [
- 1026,
- 224
- ],
- [
- 1020,
- 224
- ],
- [
- 1004,
- 218
- ],
- [
- 993,
- 218
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "1567aac5-c8e4-7584-150d-08bacfc259c3",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=978&y=193&width=1116&height=296&uuid=86b3e176-a99d-4e8e-80e4-18aaad8682df"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "0f6665a0-47fe-6e8e-0f0c-c7df40d842c9",
- "id": "47ba5f70-96de-6fad-47d0-fd0f91f843ea",
- "image_region": {
- "id": "b72b8933-0384-c224-b741-2b4c04a2ee63",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 26,
- "width": 19,
- "xmin": 1517,
- "ymin": 380
- },
- "polygons": [
- [
- [
- 1526,
- 381
- ],
- [
- 1518,
- 388
- ],
- [
- 1518,
- 398
- ],
- [
- 1529,
- 405
- ],
- [
- 1535,
- 396
- ],
- [
- 1535,
- 390
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "1df49927-6747-6719-1d9e-3b5860614b5e",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1510&y=380&width=1543&height=406&uuid=234a6c06-8755-4250-a3a7-5e572485908d"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "a6b41fba-df6f-2d6f-a6de-bdc5d8490128",
- "id": "d838da37-94f5-586a-d852-784893d3742d",
- "image_region": {
- "id": "79f5b017-68a2-340b-799f-12686f84184c",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 433,
- "width": 468,
- "xmin": 839,
- "ymin": 588
- },
- "polygons": [
- [
- [
- 928,
- 647
- ],
- [
- 889,
- 688
- ],
- [
- 870,
- 723
- ],
- [
- 861,
- 747
- ],
- [
- 861,
- 792
- ],
- [
- 870,
- 821
- ],
- [
- 872,
- 845
- ],
- [
- 909,
- 912
- ],
- [
- 924,
- 930
- ],
- [
- 946,
- 949
- ],
- [
- 985,
- 973
- ],
- [
- 1037,
- 993
- ],
- [
- 1074,
- 1001
- ],
- [
- 1120,
- 1001
- ],
- [
- 1122,
- 999
- ],
- [
- 1146,
- 997
- ],
- [
- 1194,
- 980
- ],
- [
- 1218,
- 960
- ],
- [
- 1241,
- 936
- ],
- [
- 1263,
- 908
- ],
- [
- 1278,
- 871
- ],
- [
- 1287,
- 825
- ],
- [
- 1287,
- 792
- ],
- [
- 1278,
- 775
- ],
- [
- 1270,
- 747
- ],
- [
- 1252,
- 721
- ],
- [
- 1244,
- 712
- ],
- [
- 1231,
- 688
- ],
- [
- 1189,
- 649
- ],
- [
- 1170,
- 636
- ],
- [
- 1148,
- 625
- ],
- [
- 1122,
- 616
- ],
- [
- 1087,
- 612
- ],
- [
- 1085,
- 610
- ],
- [
- 1037,
- 607
- ],
- [
- 1035,
- 610
- ],
- [
- 1022,
- 610
- ],
- [
- 989,
- 616
- ],
- [
- 952,
- 631
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "b69e0f13-edf3-0474-b6f4-ad6cead52833",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=785&y=588&width=1361&height=1021&uuid=12d45d89-b837-4823-8e9d-82bb96027530"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "bf94b068-56d5-7a8a-bffe-121751f356cd",
- "id": "f5adeae9-5e35-ec2d-f5c7-48965913c06a",
- "image_region": {
- "id": "77ae40e0-e9da-fd44-77c4-e29feefcd103",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 596,
- "width": 366,
- "xmin": 1355,
- "ymin": 345
- },
- "polygons": [
- [
- [
- 1685,
- 398
- ],
- [
- 1681,
- 398
- ],
- [
- 1666,
- 409
- ],
- [
- 1659,
- 409
- ],
- [
- 1635,
- 422
- ],
- [
- 1620,
- 422
- ],
- [
- 1616,
- 414
- ],
- [
- 1631,
- 398
- ],
- [
- 1637,
- 396
- ],
- [
- 1644,
- 390
- ],
- [
- 1631,
- 377
- ],
- [
- 1624,
- 379
- ],
- [
- 1609,
- 372
- ],
- [
- 1598,
- 372
- ],
- [
- 1576,
- 383
- ],
- [
- 1563,
- 394
- ],
- [
- 1559,
- 394
- ],
- [
- 1548,
- 405
- ],
- [
- 1561,
- 414
- ],
- [
- 1576,
- 416
- ],
- [
- 1589,
- 425
- ],
- [
- 1589,
- 431
- ],
- [
- 1583,
- 446
- ],
- [
- 1563,
- 457
- ],
- [
- 1542,
- 464
- ],
- [
- 1524,
- 475
- ],
- [
- 1526,
- 477
- ],
- [
- 1537,
- 477
- ],
- [
- 1559,
- 472
- ],
- [
- 1568,
- 475
- ],
- [
- 1576,
- 481
- ],
- [
- 1579,
- 490
- ],
- [
- 1574,
- 494
- ],
- [
- 1548,
- 483
- ],
- [
- 1535,
- 488
- ],
- [
- 1535,
- 496
- ],
- [
- 1555,
- 514
- ],
- [
- 1552,
- 520
- ],
- [
- 1546,
- 520
- ],
- [
- 1529,
- 503
- ],
- [
- 1518,
- 503
- ],
- [
- 1502,
- 509
- ],
- [
- 1465,
- 507
- ],
- [
- 1457,
- 509
- ],
- [
- 1452,
- 514
- ],
- [
- 1452,
- 522
- ],
- [
- 1479,
- 544
- ],
- [
- 1492,
- 549
- ],
- [
- 1502,
- 549
- ],
- [
- 1509,
- 557
- ],
- [
- 1505,
- 566
- ],
- [
- 1498,
- 570
- ],
- [
- 1474,
- 568
- ],
- [
- 1463,
- 573
- ],
- [
- 1457,
- 579
- ],
- [
- 1457,
- 583
- ],
- [
- 1463,
- 588
- ],
- [
- 1470,
- 586
- ],
- [
- 1500,
- 588
- ],
- [
- 1515,
- 592
- ],
- [
- 1539,
- 605
- ],
- [
- 1552,
- 601
- ],
- [
- 1563,
- 581
- ],
- [
- 1576,
- 601
- ],
- [
- 1574,
- 627
- ],
- [
- 1581,
- 642
- ],
- [
- 1589,
- 649
- ],
- [
- 1607,
- 653
- ],
- [
- 1633,
- 638
- ],
- [
- 1642,
- 638
- ],
- [
- 1650,
- 651
- ],
- [
- 1650,
- 660
- ],
- [
- 1635,
- 673
- ],
- [
- 1624,
- 675
- ],
- [
- 1622,
- 673
- ],
- [
- 1605,
- 673
- ],
- [
- 1583,
- 684
- ],
- [
- 1566,
- 697
- ],
- [
- 1555,
- 699
- ],
- [
- 1546,
- 697
- ],
- [
- 1529,
- 686
- ],
- [
- 1513,
- 690
- ],
- [
- 1507,
- 699
- ],
- [
- 1526,
- 718
- ],
- [
- 1526,
- 729
- ],
- [
- 1515,
- 745
- ],
- [
- 1515,
- 751
- ],
- [
- 1520,
- 758
- ],
- [
- 1552,
- 755
- ],
- [
- 1572,
- 768
- ],
- [
- 1583,
- 779
- ],
- [
- 1585,
- 786
- ],
- [
- 1583,
- 792
- ],
- [
- 1570,
- 801
- ],
- [
- 1561,
- 803
- ],
- [
- 1555,
- 810
- ],
- [
- 1537,
- 803
- ],
- [
- 1529,
- 803
- ],
- [
- 1524,
- 810
- ],
- [
- 1522,
- 821
- ],
- [
- 1522,
- 836
- ],
- [
- 1526,
- 845
- ],
- [
- 1524,
- 851
- ],
- [
- 1513,
- 860
- ],
- [
- 1457,
- 858
- ],
- [
- 1448,
- 862
- ],
- [
- 1435,
- 875
- ],
- [
- 1431,
- 884
- ],
- [
- 1405,
- 908
- ],
- [
- 1396,
- 912
- ],
- [
- 1387,
- 912
- ],
- [
- 1372,
- 906
- ],
- [
- 1372,
- 910
- ],
- [
- 1378,
- 914
- ],
- [
- 1394,
- 914
- ],
- [
- 1424,
- 906
- ],
- [
- 1448,
- 895
- ],
- [
- 1452,
- 890
- ],
- [
- 1476,
- 882
- ],
- [
- 1513,
- 860
- ],
- [
- 1561,
- 845
- ],
- [
- 1594,
- 827
- ],
- [
- 1609,
- 814
- ],
- [
- 1633,
- 786
- ],
- [
- 1637,
- 777
- ],
- [
- 1672,
- 745
- ],
- [
- 1689,
- 721
- ],
- [
- 1703,
- 692
- ],
- [
- 1703,
- 666
- ],
- [
- 1694,
- 644
- ],
- [
- 1694,
- 607
- ],
- [
- 1700,
- 597
- ],
- [
- 1700,
- 583
- ],
- [
- 1692,
- 560
- ],
- [
- 1692,
- 546
- ],
- [
- 1694,
- 544
- ],
- [
- 1692,
- 522
- ],
- [
- 1694,
- 520
- ],
- [
- 1694,
- 483
- ],
- [
- 1698,
- 466
- ],
- [
- 1703,
- 459
- ],
- [
- 1703,
- 446
- ],
- [
- 1705,
- 444
- ],
- [
- 1705,
- 420
- ],
- [
- 1694,
- 405
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "fc3a666a-8ea9-f60e-fc50-c415898fda49",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1141&y=345&width=1935&height=941&uuid=43c2d2b3-a647-44f5-baa5-eb1553504971"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac",
- "id": "3179a7a6-8703-f8dc-3113-05d98025d49b",
- "image_region": {
- "id": "0ddd377c-7244-154a-0db7-95037562390d",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 29,
- "width": 14,
- "xmin": 731,
- "ymin": 797
- },
- "polygons": [
- [
- [
- 736,
- 819
- ],
- [
- 734,
- 820
- ],
- [
- 731,
- 820
- ],
- [
- 731,
- 825
- ],
- [
- 732,
- 826
- ],
- [
- 736,
- 826
- ],
- [
- 738,
- 824
- ],
- [
- 738,
- 821
- ]
- ],
- [
- [
- 731,
- 798
- ],
- [
- 732,
- 802
- ],
- [
- 737,
- 807
- ],
- [
- 741,
- 809
- ],
- [
- 745,
- 807
- ],
- [
- 745,
- 801
- ],
- [
- 741,
- 797
- ],
- [
- 732,
- 797
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of damage view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "9d797036-7e3b-de8f-9d13-d249791df2c8",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=719&y=797&width=757&height=826&uuid=bcd1e0e8-c6bd-4ea6-addc-746853672836"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "ce9382ec-d9f3-17df-cef9-2093ded53b98",
- "id": "72d12f4f-8de3-2ed7-72bb-8d308ac50290",
- "image_region": {
- "id": "5d8d0505-7557-1643-5de7-a77a72713a04",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 725,
- "width": 1232,
- "xmin": 463,
- "ymin": 102
- },
- "polygons": [
- [
- [
- 519,
- 760
- ],
- [
- 717,
- 766
- ],
- [
- 761,
- 775
- ],
- [
- 776,
- 782
- ],
- [
- 791,
- 795
- ],
- [
- 789,
- 784
- ],
- [
- 793,
- 773
- ],
- [
- 793,
- 745
- ],
- [
- 806,
- 677
- ],
- [
- 815,
- 651
- ],
- [
- 826,
- 629
- ],
- [
- 843,
- 605
- ],
- [
- 876,
- 573
- ],
- [
- 900,
- 555
- ],
- [
- 930,
- 540
- ],
- [
- 974,
- 525
- ],
- [
- 1000,
- 522
- ],
- [
- 1015,
- 518
- ],
- [
- 1076,
- 518
- ],
- [
- 1091,
- 522
- ],
- [
- 1109,
- 522
- ],
- [
- 1128,
- 527
- ],
- [
- 1181,
- 544
- ],
- [
- 1237,
- 573
- ],
- [
- 1272,
- 601
- ],
- [
- 1285,
- 605
- ],
- [
- 1372,
- 536
- ],
- [
- 1398,
- 518
- ],
- [
- 1450,
- 494
- ],
- [
- 1494,
- 481
- ],
- [
- 1518,
- 477
- ],
- [
- 1542,
- 462
- ],
- [
- 1563,
- 455
- ],
- [
- 1581,
- 446
- ],
- [
- 1587,
- 431
- ],
- [
- 1587,
- 425
- ],
- [
- 1576,
- 418
- ],
- [
- 1561,
- 416
- ],
- [
- 1546,
- 405
- ],
- [
- 1561,
- 388
- ],
- [
- 1566,
- 388
- ],
- [
- 1598,
- 370
- ],
- [
- 1609,
- 370
- ],
- [
- 1624,
- 377
- ],
- [
- 1631,
- 374
- ],
- [
- 1626,
- 359
- ],
- [
- 1620,
- 353
- ],
- [
- 1618,
- 346
- ],
- [
- 1631,
- 342
- ],
- [
- 1633,
- 335
- ],
- [
- 1639,
- 331
- ],
- [
- 1629,
- 318
- ],
- [
- 1620,
- 314
- ],
- [
- 1594,
- 309
- ],
- [
- 1572,
- 300
- ],
- [
- 1561,
- 298
- ],
- [
- 1520,
- 300
- ],
- [
- 1494,
- 276
- ],
- [
- 1483,
- 272
- ],
- [
- 1459,
- 270
- ],
- [
- 1457,
- 274
- ],
- [
- 1446,
- 281
- ],
- [
- 1433,
- 281
- ],
- [
- 1426,
- 274
- ],
- [
- 1426,
- 270
- ],
- [
- 1437,
- 259
- ],
- [
- 1428,
- 253
- ],
- [
- 1415,
- 255
- ],
- [
- 1413,
- 266
- ],
- [
- 1392,
- 287
- ],
- [
- 1361,
- 305
- ],
- [
- 1355,
- 305
- ],
- [
- 1339,
- 311
- ],
- [
- 1322,
- 311
- ],
- [
- 1300,
- 316
- ],
- [
- 1272,
- 316
- ],
- [
- 1259,
- 311
- ],
- [
- 1248,
- 311
- ],
- [
- 1235,
- 296
- ],
- [
- 1235,
- 281
- ],
- [
- 1231,
- 274
- ],
- [
- 1222,
- 274
- ],
- [
- 1211,
- 281
- ],
- [
- 1207,
- 287
- ],
- [
- 1209,
- 294
- ],
- [
- 1207,
- 303
- ],
- [
- 1100,
- 303
- ],
- [
- 1061,
- 292
- ],
- [
- 1044,
- 292
- ],
- [
- 1020,
- 285
- ],
- [
- 998,
- 274
- ],
- [
- 965,
- 248
- ],
- [
- 948,
- 242
- ],
- [
- 785,
- 226
- ],
- [
- 765,
- 213
- ],
- [
- 709,
- 161
- ],
- [
- 667,
- 139
- ],
- [
- 643,
- 135
- ],
- [
- 639,
- 144
- ],
- [
- 659,
- 144
- ],
- [
- 680,
- 152
- ],
- [
- 713,
- 174
- ],
- [
- 730,
- 194
- ],
- [
- 743,
- 202
- ],
- [
- 765,
- 224
- ],
- [
- 778,
- 244
- ],
- [
- 787,
- 263
- ],
- [
- 787,
- 276
- ],
- [
- 774,
- 285
- ],
- [
- 743,
- 285
- ],
- [
- 715,
- 279
- ],
- [
- 702,
- 279
- ],
- [
- 669,
- 285
- ],
- [
- 643,
- 285
- ],
- [
- 641,
- 287
- ],
- [
- 600,
- 281
- ],
- [
- 598,
- 283
- ],
- [
- 585,
- 283
- ],
- [
- 578,
- 287
- ],
- [
- 578,
- 292
- ],
- [
- 582,
- 292
- ],
- [
- 595,
- 305
- ],
- [
- 600,
- 320
- ],
- [
- 595,
- 385
- ],
- [
- 593,
- 388
- ],
- [
- 591,
- 418
- ],
- [
- 580,
- 468
- ],
- [
- 578,
- 520
- ],
- [
- 574,
- 536
- ],
- [
- 572,
- 564
- ],
- [
- 561,
- 605
- ],
- [
- 561,
- 649
- ],
- [
- 556,
- 657
- ],
- [
- 545,
- 703
- ],
- [
- 535,
- 723
- ],
- [
- 528,
- 747
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "23585679-dd88-b9fc-2332-f406daae95bb",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=463&y=3&width=1695&height=926&uuid=951e4ca7-78fe-4c88-b2da-ad66ea5a19a4"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3",
- "id": "260594c3-2b62-74c0-266f-36bc2c445887",
- "image_region": {
- "id": "53358a7f-f12b-f039-535f-2800f60ddc7e",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 19,
- "width": 16,
- "xmin": 1275,
- "ymin": 278
- },
- "polygons": [
- [
- [
- 1283,
- 279
- ],
- [
- 1278,
- 283
- ],
- [
- 1276,
- 292
- ],
- [
- 1281,
- 296
- ],
- [
- 1287,
- 296
- ],
- [
- 1291,
- 285
- ],
- [
- 1289,
- 281
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "f958525f-3602-3369-f932-f02031241f2e",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1271&y=278&width=1295&height=297&uuid=62e1711e-2119-4a1b-9399-ed26fcf67f58"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "640b0825-60c5-887d-6461-aa5a67e3a43a",
- "id": "858717fe-a458-fbb3-85ed-b581a37ed7f4",
- "image_region": {
- "id": "5cfad77b-c11e-364e-5c90-7504c6381a09",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 478,
- "width": 397,
- "xmin": 1269,
- "ymin": 453
- },
- "polygons": [
- [
- [
- 1568,
- 477
- ],
- [
- 1550,
- 475
- ],
- [
- 1537,
- 479
- ],
- [
- 1520,
- 477
- ],
- [
- 1450,
- 496
- ],
- [
- 1420,
- 512
- ],
- [
- 1415,
- 512
- ],
- [
- 1381,
- 531
- ],
- [
- 1287,
- 605
- ],
- [
- 1296,
- 623
- ],
- [
- 1318,
- 644
- ],
- [
- 1341,
- 679
- ],
- [
- 1355,
- 708
- ],
- [
- 1359,
- 712
- ],
- [
- 1370,
- 745
- ],
- [
- 1378,
- 760
- ],
- [
- 1378,
- 779
- ],
- [
- 1385,
- 801
- ],
- [
- 1385,
- 823
- ],
- [
- 1378,
- 843
- ],
- [
- 1378,
- 880
- ],
- [
- 1372,
- 899
- ],
- [
- 1374,
- 906
- ],
- [
- 1387,
- 910
- ],
- [
- 1396,
- 910
- ],
- [
- 1405,
- 906
- ],
- [
- 1428,
- 884
- ],
- [
- 1433,
- 875
- ],
- [
- 1448,
- 860
- ],
- [
- 1457,
- 856
- ],
- [
- 1492,
- 856
- ],
- [
- 1494,
- 858
- ],
- [
- 1513,
- 858
- ],
- [
- 1518,
- 856
- ],
- [
- 1524,
- 845
- ],
- [
- 1520,
- 836
- ],
- [
- 1520,
- 821
- ],
- [
- 1522,
- 810
- ],
- [
- 1529,
- 801
- ],
- [
- 1537,
- 801
- ],
- [
- 1555,
- 808
- ],
- [
- 1561,
- 801
- ],
- [
- 1581,
- 792
- ],
- [
- 1583,
- 786
- ],
- [
- 1581,
- 779
- ],
- [
- 1552,
- 758
- ],
- [
- 1520,
- 760
- ],
- [
- 1515,
- 755
- ],
- [
- 1513,
- 745
- ],
- [
- 1524,
- 729
- ],
- [
- 1524,
- 718
- ],
- [
- 1505,
- 699
- ],
- [
- 1513,
- 688
- ],
- [
- 1529,
- 684
- ],
- [
- 1546,
- 694
- ],
- [
- 1555,
- 697
- ],
- [
- 1566,
- 694
- ],
- [
- 1583,
- 681
- ],
- [
- 1605,
- 671
- ],
- [
- 1622,
- 671
- ],
- [
- 1624,
- 673
- ],
- [
- 1635,
- 671
- ],
- [
- 1648,
- 660
- ],
- [
- 1648,
- 651
- ],
- [
- 1642,
- 640
- ],
- [
- 1633,
- 640
- ],
- [
- 1607,
- 655
- ],
- [
- 1589,
- 651
- ],
- [
- 1581,
- 644
- ],
- [
- 1574,
- 636
- ],
- [
- 1572,
- 627
- ],
- [
- 1574,
- 601
- ],
- [
- 1563,
- 583
- ],
- [
- 1555,
- 601
- ],
- [
- 1539,
- 607
- ],
- [
- 1509,
- 592
- ],
- [
- 1487,
- 588
- ],
- [
- 1463,
- 590
- ],
- [
- 1455,
- 583
- ],
- [
- 1455,
- 579
- ],
- [
- 1468,
- 568
- ],
- [
- 1474,
- 566
- ],
- [
- 1498,
- 568
- ],
- [
- 1507,
- 560
- ],
- [
- 1502,
- 551
- ],
- [
- 1492,
- 551
- ],
- [
- 1479,
- 546
- ],
- [
- 1450,
- 522
- ],
- [
- 1450,
- 514
- ],
- [
- 1457,
- 507
- ],
- [
- 1465,
- 505
- ],
- [
- 1502,
- 507
- ],
- [
- 1518,
- 501
- ],
- [
- 1529,
- 501
- ],
- [
- 1537,
- 507
- ],
- [
- 1526,
- 496
- ],
- [
- 1515,
- 492
- ],
- [
- 1529,
- 483
- ],
- [
- 1542,
- 479
- ],
- [
- 1561,
- 479
- ],
- [
- 1570,
- 485
- ],
- [
- 1574,
- 485
- ],
- [
- 1574,
- 481
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "707c5d25-0878-8683-7016-ff5a0f5eaac4",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1149&y=453&width=1786&height=931&uuid=6db3ece5-ef73-446e-b224-8fa0abb038ee"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "56c74ad5-c077-1d08-56ad-e8aac751314f",
- "id": "461084a6-d7e8-4430-467a-26d9d0ce6877",
- "image_region": {
- "id": "54fe0ad2-e714-e3ab-5494-a8ade032cfec",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 155,
- "width": 847,
- "xmin": 0,
- "ymin": 740
- },
- "polygons": [
- [
- [
- 0,
- 762
- ],
- [
- 0,
- 766
- ],
- [
- 6,
- 771
- ],
- [
- 21,
- 771
- ],
- [
- 23,
- 773
- ],
- [
- 50,
- 773
- ],
- [
- 52,
- 775
- ],
- [
- 87,
- 777
- ],
- [
- 102,
- 782
- ],
- [
- 174,
- 786
- ],
- [
- 176,
- 788
- ],
- [
- 204,
- 788
- ],
- [
- 219,
- 792
- ],
- [
- 271,
- 795
- ],
- [
- 274,
- 797
- ],
- [
- 302,
- 799
- ],
- [
- 334,
- 808
- ],
- [
- 337,
- 812
- ],
- [
- 350,
- 812
- ],
- [
- 367,
- 816
- ],
- [
- 465,
- 819
- ],
- [
- 467,
- 823
- ],
- [
- 548,
- 825
- ],
- [
- 574,
- 832
- ],
- [
- 589,
- 832
- ],
- [
- 611,
- 838
- ],
- [
- 632,
- 840
- ],
- [
- 641,
- 845
- ],
- [
- 661,
- 847
- ],
- [
- 667,
- 851
- ],
- [
- 687,
- 853
- ],
- [
- 743,
- 871
- ],
- [
- 750,
- 875
- ],
- [
- 756,
- 875
- ],
- [
- 789,
- 888
- ],
- [
- 804,
- 886
- ],
- [
- 806,
- 882
- ],
- [
- 806,
- 866
- ],
- [
- 791,
- 797
- ],
- [
- 776,
- 784
- ],
- [
- 761,
- 777
- ],
- [
- 717,
- 768
- ],
- [
- 558,
- 764
- ],
- [
- 556,
- 762
- ],
- [
- 498,
- 762
- ],
- [
- 495,
- 760
- ],
- [
- 437,
- 760
- ],
- [
- 435,
- 758
- ],
- [
- 378,
- 758
- ],
- [
- 376,
- 755
- ],
- [
- 317,
- 755
- ],
- [
- 315,
- 753
- ],
- [
- 256,
- 753
- ],
- [
- 254,
- 751
- ],
- [
- 195,
- 751
- ],
- [
- 193,
- 749
- ],
- [
- 134,
- 749
- ],
- [
- 132,
- 747
- ],
- [
- 54,
- 747
- ],
- [
- 52,
- 749
- ],
- [
- 30,
- 751
- ],
- [
- 34,
- 755
- ],
- [
- 26,
- 762
- ],
- [
- 10,
- 755
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "123a4495-771b-8a5d-1250-e6ea703da61a",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=500&width=847&height=1135&uuid=898411ea-6207-4837-9d0e-b9c9498c27be"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "3ed12b36-252c-98ed-3ebb-8949220ab4aa",
- "id": "8eab6f21-2c6b-59a7-8ec1-cd5e2b4d75e0",
- "image_region": {
- "id": "605778f4-e70d-3ad5-603d-da8be02b1692",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 522,
- "width": 569,
- "xmin": 800,
- "ymin": 529
- },
- "polygons": [
- [
- [
- 865,
- 638
- ],
- [
- 843,
- 677
- ],
- [
- 830,
- 714
- ],
- [
- 828,
- 766
- ],
- [
- 826,
- 768
- ],
- [
- 826,
- 827
- ],
- [
- 843,
- 871
- ],
- [
- 865,
- 906
- ],
- [
- 920,
- 967
- ],
- [
- 948,
- 984
- ],
- [
- 972,
- 1006
- ],
- [
- 985,
- 1012
- ],
- [
- 991,
- 1012
- ],
- [
- 1011,
- 1021
- ],
- [
- 1026,
- 1023
- ],
- [
- 1035,
- 1028
- ],
- [
- 1083,
- 1032
- ],
- [
- 1100,
- 1036
- ],
- [
- 1122,
- 1036
- ],
- [
- 1146,
- 1030
- ],
- [
- 1157,
- 1030
- ],
- [
- 1178,
- 1017
- ],
- [
- 1185,
- 1017
- ],
- [
- 1209,
- 1004
- ],
- [
- 1237,
- 999
- ],
- [
- 1254,
- 993
- ],
- [
- 1263,
- 982
- ],
- [
- 1287,
- 967
- ],
- [
- 1313,
- 936
- ],
- [
- 1322,
- 921
- ],
- [
- 1324,
- 897
- ],
- [
- 1344,
- 864
- ],
- [
- 1344,
- 851
- ],
- [
- 1339,
- 836
- ],
- [
- 1337,
- 775
- ],
- [
- 1328,
- 738
- ],
- [
- 1315,
- 705
- ],
- [
- 1315,
- 697
- ],
- [
- 1320,
- 686
- ],
- [
- 1318,
- 677
- ],
- [
- 1302,
- 671
- ],
- [
- 1283,
- 653
- ],
- [
- 1274,
- 634
- ],
- [
- 1257,
- 623
- ],
- [
- 1237,
- 605
- ],
- [
- 1183,
- 579
- ],
- [
- 1146,
- 568
- ],
- [
- 1137,
- 568
- ],
- [
- 1096,
- 553
- ],
- [
- 1020,
- 555
- ],
- [
- 1017,
- 557
- ],
- [
- 1000,
- 557
- ],
- [
- 998,
- 560
- ],
- [
- 976,
- 562
- ],
- [
- 924,
- 586
- ],
- [
- 896,
- 607
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "5ed2f756-0753-6abc-5eb8-5529007546fb",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=737&y=529&width=1432&height=1051&uuid=5b79a55e-69d7-4343-a3ce-8fd512d5058f"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "f78b2228-70ce-da31-f7e1-805777e8f676",
- "id": "71dbdd16-1c49-864a-71b1-7f691b6faa0d",
- "image_region": {
- "id": "26d25157-5182-f302-26b8-f32856a4df45",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 47,
- "width": 95,
- "xmin": 1085,
- "ymin": 213
- },
- "polygons": [
- [
- [
- 1174,
- 235
- ],
- [
- 1161,
- 229
- ],
- [
- 1150,
- 216
- ],
- [
- 1141,
- 216
- ],
- [
- 1111,
- 226
- ],
- [
- 1113,
- 235
- ],
- [
- 1100,
- 248
- ],
- [
- 1094,
- 250
- ],
- [
- 1089,
- 255
- ],
- [
- 1089,
- 259
- ],
- [
- 1109,
- 255
- ],
- [
- 1122,
- 255
- ],
- [
- 1124,
- 257
- ],
- [
- 1126,
- 255
- ],
- [
- 1146,
- 255
- ],
- [
- 1154,
- 259
- ],
- [
- 1167,
- 253
- ],
- [
- 1176,
- 244
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "c5b13d52-feef-1524-c5db-9f2df9c93963",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1085&y=201&width=1180&height=272&uuid=bd9cdd26-6ac4-42c0-8af7-012cc61460fc"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "68d766cb-9bbf-c196-68bd-c4b49c99edd1",
- "id": "8074a493-4434-7b65-801e-06ec43125722",
- "image_region": {
- "id": "18534349-8753-18d6-1839-e13680753491",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 172,
- "width": 244,
- "xmin": 552,
- "ymin": 125
- },
- "polygons": [
- [
- [
- 563,
- 281
- ],
- [
- 572,
- 290
- ],
- [
- 576,
- 290
- ],
- [
- 585,
- 281
- ],
- [
- 598,
- 281
- ],
- [
- 600,
- 279
- ],
- [
- 632,
- 285
- ],
- [
- 669,
- 283
- ],
- [
- 702,
- 276
- ],
- [
- 715,
- 276
- ],
- [
- 743,
- 283
- ],
- [
- 774,
- 283
- ],
- [
- 785,
- 276
- ],
- [
- 785,
- 263
- ],
- [
- 776,
- 244
- ],
- [
- 763,
- 224
- ],
- [
- 743,
- 205
- ],
- [
- 730,
- 196
- ],
- [
- 713,
- 176
- ],
- [
- 680,
- 155
- ],
- [
- 665,
- 148
- ],
- [
- 639,
- 146
- ],
- [
- 637,
- 144
- ],
- [
- 641,
- 135
- ],
- [
- 628,
- 133
- ],
- [
- 626,
- 155
- ],
- [
- 602,
- 183
- ],
- [
- 602,
- 187
- ],
- [
- 585,
- 220
- ],
- [
- 582,
- 233
- ],
- [
- 565,
- 263
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "311115d4-789e-625e-317b-b7ab7fb84e19",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=120&width=796&height=302&uuid=2f0c7d32-1f45-466f-83a6-40362240c624"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "2d917d5d-6636-5ea7-2dfb-df22611072e0",
- "id": "439bd668-c016-75e3-43f1-7417c73059a4",
- "image_region": {
- "id": "df5893b6-895d-e82f-df32-31c98e7bc468",
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 718,
- "width": 657,
- "xmin": 0,
- "ymin": 74
- },
- "polygons": [
- [
- [
- 626,
- 131
- ],
- [
- 606,
- 124
- ],
- [
- 569,
- 122
- ],
- [
- 567,
- 120
- ],
- [
- 539,
- 120
- ],
- [
- 508,
- 113
- ],
- [
- 471,
- 111
- ],
- [
- 456,
- 107
- ],
- [
- 363,
- 109
- ],
- [
- 334,
- 115
- ],
- [
- 317,
- 115
- ],
- [
- 287,
- 122
- ],
- [
- 271,
- 122
- ],
- [
- 269,
- 124
- ],
- [
- 197,
- 128
- ],
- [
- 171,
- 139
- ],
- [
- 158,
- 152
- ],
- [
- 132,
- 161
- ],
- [
- 115,
- 174
- ],
- [
- 82,
- 192
- ],
- [
- 52,
- 220
- ],
- [
- 23,
- 235
- ],
- [
- 8,
- 248
- ],
- [
- 2,
- 244
- ],
- [
- 0,
- 246
- ],
- [
- 0,
- 760
- ],
- [
- 8,
- 753
- ],
- [
- 19,
- 755
- ],
- [
- 26,
- 760
- ],
- [
- 32,
- 755
- ],
- [
- 28,
- 751
- ],
- [
- 30,
- 749
- ],
- [
- 52,
- 747
- ],
- [
- 54,
- 745
- ],
- [
- 132,
- 745
- ],
- [
- 134,
- 747
- ],
- [
- 193,
- 747
- ],
- [
- 195,
- 749
- ],
- [
- 254,
- 749
- ],
- [
- 256,
- 751
- ],
- [
- 267,
- 751
- ],
- [
- 269,
- 742
- ],
- [
- 282,
- 731
- ],
- [
- 313,
- 714
- ],
- [
- 328,
- 710
- ],
- [
- 337,
- 712
- ],
- [
- 341,
- 718
- ],
- [
- 343,
- 753
- ],
- [
- 495,
- 758
- ],
- [
- 504,
- 760
- ],
- [
- 532,
- 714
- ],
- [
- 541,
- 697
- ],
- [
- 548,
- 675
- ],
- [
- 550,
- 653
- ],
- [
- 558,
- 629
- ],
- [
- 561,
- 592
- ],
- [
- 567,
- 577
- ],
- [
- 567,
- 566
- ],
- [
- 569,
- 564
- ],
- [
- 572,
- 536
- ],
- [
- 576,
- 520
- ],
- [
- 578,
- 468
- ],
- [
- 589,
- 418
- ],
- [
- 591,
- 388
- ],
- [
- 593,
- 385
- ],
- [
- 593,
- 368
- ],
- [
- 598,
- 346
- ],
- [
- 595,
- 311
- ],
- [
- 593,
- 305
- ],
- [
- 582,
- 294
- ],
- [
- 572,
- 292
- ],
- [
- 563,
- 285
- ],
- [
- 561,
- 272
- ],
- [
- 576,
- 237
- ],
- [
- 580,
- 233
- ],
- [
- 582,
- 220
- ],
- [
- 600,
- 187
- ],
- [
- 600,
- 183
- ],
- [
- 624,
- 155
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "8d051e26-285e-30e7-8d6f-bc592f781ca0",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=74&width=807&height=792&uuid=ef876343-5944-443f-b0ef-19e2a5b21dfb"
- }
- ]
- }
- ]
- },
- {
- "additional_data": {
- "category": "exterior",
- "sightId": "ffocus18-3TiCVAaN",
- "label": {
- "de": "Motorhaube",
- "en": "Hood",
- "fr": "Capot"
- }
- },
- "binary_size": 160207,
- "compliances": {
- "compliance_issues": [
- "low_resolution"
- ],
- "compliance_status": "non_compliant",
- "coverage_analysis": {
- "compliance_issues": [],
- "is_compliant_with_sight": true,
- "probability_by_viewpoint": {
- "front_left": 1.0
- }
- },
- "image_analysis": {
- "binary_size": 160207,
- "blurriness": 0.28681227564811707,
- "image_height": 1920,
- "image_width": 1080,
- "lens_flare": 0.22103191912174225,
- "orientation": "landscape",
- "overexposure": 0.11703607439994812,
- "predicted_image_type": "unknown",
- "underexposure": 0.021420717239379883,
- "zoom": 0.7965673208236694
- },
- "should_retake": true,
- "vehicle_analysis": {
- "dirtiness": 0.22652102261781693,
- "is_vehicle_present": true,
- "ratio_of_vehicle_to_image_pixels": 0.4923013117283951,
- "reflections": 0.5470188856124878,
- "snowness": 0.09955208003520966,
- "vehicle_type": "unknown",
- "wetness": 0.44983914494514465
- }
- },
- "has_vehicle": true,
- "id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "image_height": 1080,
- "image_type": "beauty_shot",
- "image_width": 1920,
- "mimetype": "image/jpeg",
- "name": "hood.jpeg",
- "object_type": "IMAGE",
- "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg?generation=1702474029549060&alt=media",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "rendering of detected parts"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "d14655ab-f662-3898-d12c-f7d4f14414df",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.690063_21731085-0378-40c7-b729-17a56c6aee55.jpg"
- },
- {
- "additional_data": {
- "description": "rendering of detected damages"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "ffee30b3-6d49-58ea-ff84-92cc6a6f74ad",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.709410_5f163233-6d20-4990-bd54-0cf001b052bc.jpg"
- }
- ],
- "viewpoint": {
- "confidence": 0.4582550525665283,
- "prediction": "front_left"
- },
- "views": [
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "4d9cb158-d7ed-c73e-4df6-1327d0cbeb79",
- "id": "66937b3f-dc7a-f700-66f9-d940db5cdb47",
- "image_region": {
- "id": "e3717de5-9696-359d-e31b-df9a91b019da",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 36,
- "width": 269,
- "xmin": 947,
- "ymin": 417
- },
- "polygons": [
- [
- [
- 959,
- 425
- ],
- [
- 978,
- 432
- ],
- [
- 994,
- 432
- ],
- [
- 996,
- 433
- ],
- [
- 1013,
- 433
- ],
- [
- 1015,
- 435
- ],
- [
- 1115,
- 438
- ],
- [
- 1139,
- 447
- ],
- [
- 1145,
- 447
- ],
- [
- 1162,
- 452
- ],
- [
- 1186,
- 452
- ],
- [
- 1199,
- 448
- ],
- [
- 1204,
- 445
- ],
- [
- 1197,
- 438
- ],
- [
- 1191,
- 438
- ],
- [
- 1170,
- 432
- ],
- [
- 1162,
- 432
- ],
- [
- 1132,
- 425
- ],
- [
- 1108,
- 425
- ],
- [
- 1107,
- 423
- ],
- [
- 1065,
- 422
- ],
- [
- 1063,
- 420
- ],
- [
- 1045,
- 420
- ],
- [
- 1043,
- 418
- ],
- [
- 1006,
- 420
- ],
- [
- 1005,
- 422
- ],
- [
- 976,
- 422
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "337b4153-f26e-59f9-3311-e32cf54875be",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=947&y=335&width=1216&height=535&uuid=fb9e798e-dd63-4bc8-ba76-70c565f93cea"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "fff6bd07-f76a-1597-ff9c-1f78f04c39d0",
- "id": "058c0533-0a74-dcb3-05e6-a74c0d52f0f4",
- "image_region": {
- "id": "fcf7022e-de54-0da3-fc9d-a051d97221e4",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 259,
- "width": 119,
- "xmin": 1220,
- "ymin": 154
- },
- "polygons": [
- [
- [
- 1227,
- 167
- ],
- [
- 1226,
- 170
- ],
- [
- 1231,
- 177
- ],
- [
- 1237,
- 202
- ],
- [
- 1259,
- 241
- ],
- [
- 1266,
- 262
- ],
- [
- 1284,
- 306
- ],
- [
- 1284,
- 311
- ],
- [
- 1293,
- 326
- ],
- [
- 1299,
- 333
- ],
- [
- 1306,
- 351
- ],
- [
- 1333,
- 401
- ],
- [
- 1335,
- 400
- ],
- [
- 1331,
- 385
- ],
- [
- 1333,
- 360
- ],
- [
- 1330,
- 355
- ],
- [
- 1331,
- 323
- ],
- [
- 1299,
- 274
- ],
- [
- 1293,
- 259
- ],
- [
- 1269,
- 227
- ],
- [
- 1249,
- 184
- ],
- [
- 1234,
- 165
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "325ba3da-5e3e-40d0-3231-01a559186c97",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1107&y=154&width=1452&height=413&uuid=c6142566-5af5-49f3-bfe3-56cc3e79b57e"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "299b478c-4091-0165-29f1-e5f347b72d22",
- "id": "740607eb-7996-67f0-746c-a5947eb04bb7",
- "image_region": {
- "id": "f2345957-6e6b-99d7-f25e-fb28694db590",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 618,
- "width": 971,
- "xmin": 441,
- "ymin": 367
- },
- "polygons": [
- [
- [
- 524,
- 425
- ],
- [
- 522,
- 442
- ],
- [
- 520,
- 443
- ],
- [
- 520,
- 462
- ],
- [
- 517,
- 475
- ],
- [
- 503,
- 504
- ],
- [
- 497,
- 524
- ],
- [
- 495,
- 552
- ],
- [
- 490,
- 572
- ],
- [
- 490,
- 594
- ],
- [
- 485,
- 606
- ],
- [
- 485,
- 616
- ],
- [
- 503,
- 636
- ],
- [
- 510,
- 649
- ],
- [
- 519,
- 683
- ],
- [
- 519,
- 699
- ],
- [
- 520,
- 701
- ],
- [
- 522,
- 721
- ],
- [
- 530,
- 755
- ],
- [
- 547,
- 795
- ],
- [
- 549,
- 807
- ],
- [
- 560,
- 818
- ],
- [
- 567,
- 838
- ],
- [
- 577,
- 857
- ],
- [
- 602,
- 889
- ],
- [
- 622,
- 907
- ],
- [
- 653,
- 925
- ],
- [
- 679,
- 932
- ],
- [
- 691,
- 937
- ],
- [
- 711,
- 939
- ],
- [
- 726,
- 946
- ],
- [
- 743,
- 946
- ],
- [
- 755,
- 941
- ],
- [
- 782,
- 942
- ],
- [
- 795,
- 937
- ],
- [
- 810,
- 937
- ],
- [
- 832,
- 946
- ],
- [
- 850,
- 946
- ],
- [
- 870,
- 941
- ],
- [
- 892,
- 942
- ],
- [
- 894,
- 944
- ],
- [
- 912,
- 944
- ],
- [
- 936,
- 937
- ],
- [
- 968,
- 937
- ],
- [
- 969,
- 939
- ],
- [
- 984,
- 939
- ],
- [
- 1015,
- 949
- ],
- [
- 1040,
- 951
- ],
- [
- 1056,
- 957
- ],
- [
- 1075,
- 956
- ],
- [
- 1100,
- 944
- ],
- [
- 1134,
- 946
- ],
- [
- 1154,
- 937
- ],
- [
- 1169,
- 936
- ],
- [
- 1186,
- 924
- ],
- [
- 1204,
- 915
- ],
- [
- 1217,
- 912
- ],
- [
- 1253,
- 894
- ],
- [
- 1271,
- 874
- ],
- [
- 1293,
- 828
- ],
- [
- 1308,
- 812
- ],
- [
- 1310,
- 803
- ],
- [
- 1318,
- 788
- ],
- [
- 1323,
- 771
- ],
- [
- 1333,
- 755
- ],
- [
- 1343,
- 718
- ],
- [
- 1345,
- 699
- ],
- [
- 1350,
- 686
- ],
- [
- 1358,
- 648
- ],
- [
- 1360,
- 619
- ],
- [
- 1363,
- 609
- ],
- [
- 1365,
- 586
- ],
- [
- 1367,
- 584
- ],
- [
- 1368,
- 530
- ],
- [
- 1367,
- 529
- ],
- [
- 1363,
- 482
- ],
- [
- 1358,
- 468
- ],
- [
- 1358,
- 462
- ],
- [
- 1350,
- 432
- ],
- [
- 1336,
- 410
- ],
- [
- 1335,
- 417
- ],
- [
- 1326,
- 425
- ],
- [
- 1313,
- 432
- ],
- [
- 1256,
- 448
- ],
- [
- 1191,
- 455
- ],
- [
- 1189,
- 457
- ],
- [
- 1152,
- 460
- ],
- [
- 1150,
- 462
- ],
- [
- 1088,
- 463
- ],
- [
- 1087,
- 465
- ],
- [
- 1070,
- 465
- ],
- [
- 1068,
- 467
- ],
- [
- 986,
- 467
- ],
- [
- 984,
- 468
- ],
- [
- 902,
- 468
- ],
- [
- 901,
- 467
- ],
- [
- 867,
- 467
- ],
- [
- 865,
- 465
- ],
- [
- 827,
- 465
- ],
- [
- 825,
- 463
- ],
- [
- 778,
- 462
- ],
- [
- 777,
- 460
- ],
- [
- 725,
- 458
- ],
- [
- 723,
- 457
- ],
- [
- 708,
- 457
- ],
- [
- 706,
- 455
- ],
- [
- 693,
- 455
- ],
- [
- 691,
- 453
- ],
- [
- 678,
- 453
- ],
- [
- 676,
- 452
- ],
- [
- 644,
- 448
- ],
- [
- 631,
- 442
- ],
- [
- 626,
- 442
- ],
- [
- 614,
- 437
- ],
- [
- 574,
- 430
- ],
- [
- 554,
- 422
- ],
- [
- 549,
- 417
- ],
- [
- 545,
- 405
- ],
- [
- 549,
- 395
- ],
- [
- 542,
- 400
- ],
- [
- 537,
- 411
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "f85c1c27-7bec-9d6c-f836-be587ccab12b",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=441&y=312&width=1412&height=1040&uuid=d03546db-19bc-45d3-822c-0dfb77ded67e"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "0f6665a0-47fe-6e8e-0f0c-c7df40d842c9",
- "id": "be3948a9-777a-be37-be53-ead6705c9270",
- "image_region": {
- "id": "cff65dc0-757d-278d-cf9c-ffbf725b0bca",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 524,
- "width": 278,
- "xmin": 291,
- "ymin": 312
- },
- "polygons": [
- [
- [
- 550,
- 336
- ],
- [
- 535,
- 351
- ],
- [
- 519,
- 378
- ],
- [
- 507,
- 391
- ],
- [
- 498,
- 406
- ],
- [
- 493,
- 411
- ],
- [
- 493,
- 415
- ],
- [
- 455,
- 473
- ],
- [
- 453,
- 480
- ],
- [
- 436,
- 512
- ],
- [
- 416,
- 534
- ],
- [
- 408,
- 539
- ],
- [
- 373,
- 572
- ],
- [
- 363,
- 584
- ],
- [
- 363,
- 592
- ],
- [
- 358,
- 606
- ],
- [
- 341,
- 616
- ],
- [
- 333,
- 632
- ],
- [
- 331,
- 641
- ],
- [
- 326,
- 648
- ],
- [
- 319,
- 666
- ],
- [
- 319,
- 674
- ],
- [
- 312,
- 689
- ],
- [
- 309,
- 708
- ],
- [
- 306,
- 715
- ],
- [
- 304,
- 763
- ],
- [
- 324,
- 783
- ],
- [
- 334,
- 788
- ],
- [
- 339,
- 788
- ],
- [
- 346,
- 781
- ],
- [
- 348,
- 763
- ],
- [
- 349,
- 761
- ],
- [
- 349,
- 746
- ],
- [
- 363,
- 701
- ],
- [
- 373,
- 679
- ],
- [
- 383,
- 668
- ],
- [
- 400,
- 663
- ],
- [
- 420,
- 668
- ],
- [
- 433,
- 674
- ],
- [
- 468,
- 710
- ],
- [
- 478,
- 726
- ],
- [
- 487,
- 755
- ],
- [
- 493,
- 768
- ],
- [
- 500,
- 802
- ],
- [
- 505,
- 812
- ],
- [
- 508,
- 813
- ],
- [
- 532,
- 813
- ],
- [
- 547,
- 807
- ],
- [
- 545,
- 795
- ],
- [
- 529,
- 755
- ],
- [
- 520,
- 721
- ],
- [
- 519,
- 701
- ],
- [
- 517,
- 699
- ],
- [
- 517,
- 683
- ],
- [
- 508,
- 649
- ],
- [
- 502,
- 636
- ],
- [
- 485,
- 619
- ],
- [
- 483,
- 606
- ],
- [
- 488,
- 594
- ],
- [
- 488,
- 572
- ],
- [
- 493,
- 552
- ],
- [
- 495,
- 524
- ],
- [
- 502,
- 504
- ],
- [
- 515,
- 475
- ],
- [
- 519,
- 462
- ],
- [
- 519,
- 443
- ],
- [
- 520,
- 442
- ],
- [
- 522,
- 425
- ],
- [
- 535,
- 411
- ],
- [
- 540,
- 400
- ],
- [
- 550,
- 391
- ],
- [
- 555,
- 380
- ],
- [
- 555,
- 356
- ],
- [
- 557,
- 351
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "03a8e4a9-d417-c1fd-03c2-46d6d331edba",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=81&y=312&width=779&height=836&uuid=47a952b4-0931-4993-b993-bf589f0f20b4"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "3a4f083c-10fb-b483-3a25-aa4317dd98c4",
- "id": "d64d13af-dd3c-cc9e-d627-b1d0da1ae0d9",
- "image_region": {
- "id": "3ac998f2-2c0e-736c-3aa3-3a8d2b285f2b",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 344,
- "width": 866,
- "xmin": 508,
- "ymin": 138
- },
- "polygons": [
- [
- [
- 547,
- 405
- ],
- [
- 550,
- 417
- ],
- [
- 564,
- 425
- ],
- [
- 587,
- 432
- ],
- [
- 599,
- 432
- ],
- [
- 614,
- 435
- ],
- [
- 626,
- 440
- ],
- [
- 631,
- 440
- ],
- [
- 644,
- 447
- ],
- [
- 663,
- 448
- ],
- [
- 658,
- 448
- ],
- [
- 641,
- 442
- ],
- [
- 627,
- 428
- ],
- [
- 601,
- 427
- ],
- [
- 599,
- 425
- ],
- [
- 601,
- 417
- ],
- [
- 624,
- 415
- ],
- [
- 638,
- 410
- ],
- [
- 644,
- 410
- ],
- [
- 646,
- 411
- ],
- [
- 691,
- 417
- ],
- [
- 721,
- 423
- ],
- [
- 733,
- 423
- ],
- [
- 738,
- 427
- ],
- [
- 740,
- 432
- ],
- [
- 751,
- 443
- ],
- [
- 748,
- 448
- ],
- [
- 741,
- 450
- ],
- [
- 666,
- 450
- ],
- [
- 723,
- 455
- ],
- [
- 725,
- 457
- ],
- [
- 777,
- 458
- ],
- [
- 778,
- 460
- ],
- [
- 825,
- 462
- ],
- [
- 827,
- 463
- ],
- [
- 901,
- 465
- ],
- [
- 902,
- 467
- ],
- [
- 1068,
- 465
- ],
- [
- 1070,
- 463
- ],
- [
- 1087,
- 463
- ],
- [
- 1088,
- 462
- ],
- [
- 1150,
- 460
- ],
- [
- 1152,
- 458
- ],
- [
- 1179,
- 457
- ],
- [
- 1204,
- 452
- ],
- [
- 1232,
- 450
- ],
- [
- 1234,
- 448
- ],
- [
- 1256,
- 447
- ],
- [
- 1305,
- 433
- ],
- [
- 1326,
- 423
- ],
- [
- 1333,
- 417
- ],
- [
- 1335,
- 411
- ],
- [
- 1335,
- 405
- ],
- [
- 1328,
- 396
- ],
- [
- 1315,
- 368
- ],
- [
- 1311,
- 365
- ],
- [
- 1298,
- 333
- ],
- [
- 1291,
- 326
- ],
- [
- 1283,
- 311
- ],
- [
- 1283,
- 306
- ],
- [
- 1264,
- 262
- ],
- [
- 1258,
- 241
- ],
- [
- 1236,
- 202
- ],
- [
- 1229,
- 177
- ],
- [
- 1224,
- 169
- ],
- [
- 1172,
- 159
- ],
- [
- 1124,
- 157
- ],
- [
- 1122,
- 155
- ],
- [
- 1080,
- 155
- ],
- [
- 1078,
- 154
- ],
- [
- 837,
- 154
- ],
- [
- 835,
- 155
- ],
- [
- 815,
- 155
- ],
- [
- 814,
- 157
- ],
- [
- 741,
- 157
- ],
- [
- 740,
- 159
- ],
- [
- 721,
- 159
- ],
- [
- 720,
- 160
- ],
- [
- 684,
- 160
- ],
- [
- 668,
- 164
- ],
- [
- 661,
- 169
- ],
- [
- 638,
- 216
- ],
- [
- 634,
- 219
- ],
- [
- 629,
- 232
- ],
- [
- 621,
- 244
- ],
- [
- 604,
- 283
- ],
- [
- 601,
- 286
- ],
- [
- 591,
- 309
- ],
- [
- 572,
- 343
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "4c2bf631-5213-7c6c-4c41-544e5535502b",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=508&y=0&width=1374&height=634&uuid=26a62e63-16f8-4861-90c5-eabc18a6adfa"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "bf94b068-56d5-7a8a-bffe-121751f356cd",
- "id": "6805436b-c50b-756f-686f-e114c22d5928",
- "image_region": {
- "id": "ea5e1605-ba1b-9cf1-ea34-b47abd3db0b6",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 537,
- "width": 1339,
- "xmin": 266,
- "ymin": 542
- },
- "polygons": [
- [
- [
- 1563,
- 723
- ],
- [
- 1559,
- 715
- ],
- [
- 1551,
- 666
- ],
- [
- 1548,
- 654
- ],
- [
- 1537,
- 636
- ],
- [
- 1536,
- 627
- ],
- [
- 1516,
- 587
- ],
- [
- 1497,
- 567
- ],
- [
- 1489,
- 567
- ],
- [
- 1497,
- 594
- ],
- [
- 1502,
- 601
- ],
- [
- 1506,
- 612
- ],
- [
- 1526,
- 622
- ],
- [
- 1534,
- 636
- ],
- [
- 1531,
- 653
- ],
- [
- 1516,
- 669
- ],
- [
- 1514,
- 674
- ],
- [
- 1516,
- 676
- ],
- [
- 1524,
- 676
- ],
- [
- 1534,
- 681
- ],
- [
- 1554,
- 703
- ],
- [
- 1554,
- 716
- ],
- [
- 1549,
- 726
- ],
- [
- 1549,
- 735
- ],
- [
- 1553,
- 740
- ],
- [
- 1553,
- 761
- ],
- [
- 1548,
- 773
- ],
- [
- 1534,
- 785
- ],
- [
- 1522,
- 783
- ],
- [
- 1519,
- 778
- ],
- [
- 1506,
- 731
- ],
- [
- 1506,
- 723
- ],
- [
- 1501,
- 710
- ],
- [
- 1502,
- 721
- ],
- [
- 1506,
- 728
- ],
- [
- 1507,
- 751
- ],
- [
- 1509,
- 753
- ],
- [
- 1507,
- 785
- ],
- [
- 1506,
- 787
- ],
- [
- 1507,
- 808
- ],
- [
- 1506,
- 810
- ],
- [
- 1504,
- 832
- ],
- [
- 1497,
- 855
- ],
- [
- 1497,
- 864
- ],
- [
- 1492,
- 877
- ],
- [
- 1470,
- 909
- ],
- [
- 1454,
- 925
- ],
- [
- 1444,
- 932
- ],
- [
- 1422,
- 952
- ],
- [
- 1393,
- 967
- ],
- [
- 1375,
- 964
- ],
- [
- 1358,
- 956
- ],
- [
- 1345,
- 944
- ],
- [
- 1338,
- 930
- ],
- [
- 1336,
- 910
- ],
- [
- 1343,
- 892
- ],
- [
- 1345,
- 879
- ],
- [
- 1353,
- 848
- ],
- [
- 1351,
- 830
- ],
- [
- 1350,
- 825
- ],
- [
- 1323,
- 820
- ],
- [
- 1313,
- 817
- ],
- [
- 1310,
- 813
- ],
- [
- 1294,
- 828
- ],
- [
- 1273,
- 874
- ],
- [
- 1248,
- 899
- ],
- [
- 1217,
- 914
- ],
- [
- 1196,
- 920
- ],
- [
- 1169,
- 937
- ],
- [
- 1154,
- 939
- ],
- [
- 1134,
- 947
- ],
- [
- 1100,
- 946
- ],
- [
- 1075,
- 957
- ],
- [
- 1056,
- 959
- ],
- [
- 1040,
- 952
- ],
- [
- 1015,
- 951
- ],
- [
- 984,
- 941
- ],
- [
- 969,
- 941
- ],
- [
- 968,
- 939
- ],
- [
- 936,
- 939
- ],
- [
- 912,
- 946
- ],
- [
- 894,
- 946
- ],
- [
- 892,
- 944
- ],
- [
- 870,
- 942
- ],
- [
- 850,
- 947
- ],
- [
- 832,
- 947
- ],
- [
- 810,
- 939
- ],
- [
- 795,
- 939
- ],
- [
- 782,
- 944
- ],
- [
- 755,
- 942
- ],
- [
- 743,
- 947
- ],
- [
- 726,
- 947
- ],
- [
- 711,
- 941
- ],
- [
- 691,
- 939
- ],
- [
- 679,
- 934
- ],
- [
- 668,
- 932
- ],
- [
- 653,
- 927
- ],
- [
- 627,
- 912
- ],
- [
- 601,
- 889
- ],
- [
- 571,
- 848
- ],
- [
- 559,
- 818
- ],
- [
- 547,
- 808
- ],
- [
- 532,
- 815
- ],
- [
- 508,
- 815
- ],
- [
- 507,
- 813
- ],
- [
- 505,
- 837
- ],
- [
- 510,
- 859
- ],
- [
- 510,
- 887
- ],
- [
- 515,
- 907
- ],
- [
- 515,
- 925
- ],
- [
- 510,
- 939
- ],
- [
- 497,
- 954
- ],
- [
- 475,
- 969
- ],
- [
- 458,
- 974
- ],
- [
- 445,
- 969
- ],
- [
- 428,
- 957
- ],
- [
- 393,
- 922
- ],
- [
- 384,
- 907
- ],
- [
- 364,
- 880
- ],
- [
- 354,
- 853
- ],
- [
- 349,
- 827
- ],
- [
- 348,
- 776
- ],
- [
- 348,
- 781
- ],
- [
- 339,
- 790
- ],
- [
- 334,
- 790
- ],
- [
- 324,
- 785
- ],
- [
- 304,
- 765
- ],
- [
- 299,
- 780
- ],
- [
- 299,
- 792
- ],
- [
- 297,
- 793
- ],
- [
- 299,
- 875
- ],
- [
- 302,
- 887
- ],
- [
- 304,
- 919
- ],
- [
- 311,
- 946
- ],
- [
- 319,
- 962
- ],
- [
- 319,
- 971
- ],
- [
- 329,
- 996
- ],
- [
- 333,
- 1014
- ],
- [
- 338,
- 1024
- ],
- [
- 338,
- 1038
- ],
- [
- 349,
- 1078
- ],
- [
- 1512,
- 1078
- ],
- [
- 1517,
- 1059
- ],
- [
- 1526,
- 1041
- ],
- [
- 1531,
- 1014
- ],
- [
- 1536,
- 1004
- ],
- [
- 1537,
- 994
- ],
- [
- 1544,
- 982
- ],
- [
- 1544,
- 977
- ],
- [
- 1549,
- 967
- ],
- [
- 1551,
- 954
- ],
- [
- 1558,
- 932
- ],
- [
- 1559,
- 915
- ],
- [
- 1563,
- 910
- ],
- [
- 1566,
- 813
- ],
- [
- 1569,
- 797
- ],
- [
- 1569,
- 781
- ],
- [
- 1568,
- 780
- ],
- [
- 1566,
- 751
- ],
- [
- 1563,
- 738
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "2a5bdb7c-a086-b288-2a31-7903a7a09ecf",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=266&y=309&width=1605&height=1312&uuid=254f827d-79cb-45fb-9b11-037fda961c4c"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "6d6b4bcd-e710-686c-6d01-e9b2e036442b",
- "id": "65beb0eb-d1ea-7d19-65d4-1294d6cc515e",
- "image_region": {
- "id": "8b9c92c0-07dc-4b41-8bf6-30bf00fa6706",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 338,
- "width": 180,
- "xmin": 341,
- "ymin": 649
- },
- "polygons": [
- [
- [
- 390,
- 666
- ],
- [
- 378,
- 674
- ],
- [
- 368,
- 693
- ],
- [
- 351,
- 746
- ],
- [
- 349,
- 805
- ],
- [
- 351,
- 807
- ],
- [
- 351,
- 827
- ],
- [
- 356,
- 853
- ],
- [
- 366,
- 880
- ],
- [
- 386,
- 907
- ],
- [
- 395,
- 922
- ],
- [
- 428,
- 956
- ],
- [
- 452,
- 971
- ],
- [
- 462,
- 972
- ],
- [
- 475,
- 967
- ],
- [
- 497,
- 952
- ],
- [
- 508,
- 939
- ],
- [
- 514,
- 925
- ],
- [
- 514,
- 907
- ],
- [
- 508,
- 887
- ],
- [
- 508,
- 859
- ],
- [
- 503,
- 837
- ],
- [
- 505,
- 813
- ],
- [
- 498,
- 802
- ],
- [
- 492,
- 768
- ],
- [
- 485,
- 755
- ],
- [
- 477,
- 726
- ],
- [
- 462,
- 703
- ],
- [
- 433,
- 676
- ],
- [
- 410,
- 666
- ],
- [
- 401,
- 666
- ],
- [
- 400,
- 664
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "8e49cf30-378e-1cf1-8e23-6d4f30a830b6",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=206&y=649&width=656&height=987&uuid=3e69cdc5-2371-4eb9-86ff-cedcfde8addc"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "a2bd7491-a911-ffdb-a2d7-d6eeae37d39c",
- "id": "5084700a-043a-6074-50ee-d275031c4c33",
- "image_region": {
- "id": "4ff541ec-7cc5-3075-4f9f-e3937be31c32",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 548,
- "width": 267,
- "xmin": 1297,
- "ymin": 299
- },
- "polygons": [
- [
- [
- 1333,
- 324
- ],
- [
- 1331,
- 355
- ],
- [
- 1335,
- 360
- ],
- [
- 1335,
- 375
- ],
- [
- 1333,
- 376
- ],
- [
- 1335,
- 396
- ],
- [
- 1336,
- 398
- ],
- [
- 1335,
- 403
- ],
- [
- 1351,
- 432
- ],
- [
- 1367,
- 492
- ],
- [
- 1367,
- 512
- ],
- [
- 1368,
- 514
- ],
- [
- 1368,
- 529
- ],
- [
- 1370,
- 530
- ],
- [
- 1368,
- 584
- ],
- [
- 1367,
- 586
- ],
- [
- 1365,
- 609
- ],
- [
- 1362,
- 619
- ],
- [
- 1360,
- 648
- ],
- [
- 1351,
- 686
- ],
- [
- 1346,
- 699
- ],
- [
- 1345,
- 718
- ],
- [
- 1335,
- 755
- ],
- [
- 1325,
- 771
- ],
- [
- 1320,
- 788
- ],
- [
- 1311,
- 803
- ],
- [
- 1310,
- 812
- ],
- [
- 1313,
- 815
- ],
- [
- 1323,
- 818
- ],
- [
- 1350,
- 823
- ],
- [
- 1355,
- 815
- ],
- [
- 1368,
- 770
- ],
- [
- 1393,
- 720
- ],
- [
- 1408,
- 703
- ],
- [
- 1442,
- 679
- ],
- [
- 1462,
- 671
- ],
- [
- 1475,
- 671
- ],
- [
- 1482,
- 674
- ],
- [
- 1492,
- 684
- ],
- [
- 1499,
- 701
- ],
- [
- 1499,
- 706
- ],
- [
- 1506,
- 716
- ],
- [
- 1507,
- 731
- ],
- [
- 1521,
- 778
- ],
- [
- 1526,
- 783
- ],
- [
- 1534,
- 783
- ],
- [
- 1546,
- 773
- ],
- [
- 1551,
- 761
- ],
- [
- 1551,
- 740
- ],
- [
- 1548,
- 735
- ],
- [
- 1548,
- 726
- ],
- [
- 1553,
- 716
- ],
- [
- 1553,
- 703
- ],
- [
- 1534,
- 683
- ],
- [
- 1524,
- 678
- ],
- [
- 1516,
- 678
- ],
- [
- 1512,
- 674
- ],
- [
- 1514,
- 669
- ],
- [
- 1529,
- 653
- ],
- [
- 1532,
- 641
- ],
- [
- 1531,
- 631
- ],
- [
- 1526,
- 624
- ],
- [
- 1507,
- 616
- ],
- [
- 1504,
- 612
- ],
- [
- 1501,
- 601
- ],
- [
- 1496,
- 594
- ],
- [
- 1487,
- 567
- ],
- [
- 1496,
- 566
- ],
- [
- 1482,
- 552
- ],
- [
- 1454,
- 530
- ],
- [
- 1437,
- 504
- ],
- [
- 1392,
- 415
- ],
- [
- 1363,
- 375
- ],
- [
- 1355,
- 358
- ],
- [
- 1343,
- 343
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "ce9f0c5d-2b3e-d5b1-cef5-ae222c18f9f6",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1066&y=299&width=1795&height=847&uuid=8ace9da8-c7fa-4ed3-8671-4185fff3235b"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "17fd8970-6229-d165-1797-2b0f650ffd22",
- "id": "0383f405-66a7-0af0-03e9-567a618126b7",
- "image_region": {
- "id": "c3e9c375-57cf-5fee-c383-610a50e973a9",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 36,
- "width": 47,
- "xmin": 1267,
- "ymin": 1019
- },
- "polygons": [
- [
- [
- 1269,
- 1031
- ],
- [
- 1269,
- 1036
- ],
- [
- 1278,
- 1044
- ],
- [
- 1289,
- 1048
- ],
- [
- 1301,
- 1054
- ],
- [
- 1306,
- 1054
- ],
- [
- 1310,
- 1051
- ],
- [
- 1313,
- 1038
- ],
- [
- 1310,
- 1029
- ],
- [
- 1305,
- 1024
- ],
- [
- 1289,
- 1021
- ],
- [
- 1276,
- 1024
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "02d8800f-7866-58aa-02b2-22707f4074ed",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1267&y=1019&width=1314&height=1055&uuid=93b4e516-ebf0-4434-83e3-1b60842006c7"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "833efbf2-dda5-8c31-8354-598dda83a076",
- "id": "29ed50ba-aa7d-351a-2987-f2c5ad5b195d",
- "image_region": {
- "id": "17cf2eb5-ba82-c186-17a5-8ccabda4edc1",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 123,
- "width": 628,
- "xmin": 632,
- "ymin": 49
- },
- "polygons": [
- [
- [
- 661,
- 155
- ],
- [
- 661,
- 162
- ],
- [
- 666,
- 164
- ],
- [
- 684,
- 159
- ],
- [
- 720,
- 159
- ],
- [
- 721,
- 157
- ],
- [
- 740,
- 157
- ],
- [
- 741,
- 155
- ],
- [
- 814,
- 155
- ],
- [
- 815,
- 154
- ],
- [
- 835,
- 154
- ],
- [
- 837,
- 152
- ],
- [
- 1078,
- 152
- ],
- [
- 1080,
- 154
- ],
- [
- 1122,
- 154
- ],
- [
- 1124,
- 155
- ],
- [
- 1172,
- 157
- ],
- [
- 1174,
- 159
- ],
- [
- 1204,
- 162
- ],
- [
- 1219,
- 167
- ],
- [
- 1226,
- 167
- ],
- [
- 1232,
- 164
- ],
- [
- 1229,
- 150
- ],
- [
- 1224,
- 140
- ],
- [
- 1204,
- 112
- ],
- [
- 1187,
- 95
- ],
- [
- 1167,
- 82
- ],
- [
- 1135,
- 68
- ],
- [
- 1119,
- 65
- ],
- [
- 1105,
- 65
- ],
- [
- 1103,
- 63
- ],
- [
- 1068,
- 62
- ],
- [
- 1067,
- 60
- ],
- [
- 1036,
- 60
- ],
- [
- 1035,
- 58
- ],
- [
- 1011,
- 58
- ],
- [
- 1010,
- 57
- ],
- [
- 922,
- 57
- ],
- [
- 921,
- 55
- ],
- [
- 849,
- 57
- ],
- [
- 847,
- 58
- ],
- [
- 835,
- 58
- ],
- [
- 822,
- 62
- ],
- [
- 807,
- 62
- ],
- [
- 772,
- 68
- ],
- [
- 762,
- 68
- ],
- [
- 731,
- 77
- ],
- [
- 718,
- 83
- ],
- [
- 701,
- 98
- ],
- [
- 669,
- 139
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "79c36708-b835-5fdb-79a9-c577bf13739c",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=632&y=0&width=1260&height=346&uuid=98eb6dce-d25c-448e-9d1e-abe957165ce0"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "element_id": "4c70355e-6197-3c0b-4c1a-972166b1104c",
- "id": "e7a16ffb-a22c-8705-e7cb-cd84a50aab42",
- "image_region": {
- "id": "f117fd69-5b6d-6d90-f17d-5f165c4b41d7",
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "object_type": "IMAGE_REGION",
- "specification": {
- "bounding_box": {
- "height": 322,
- "width": 186,
- "xmin": 1330,
- "ymin": 658
- },
- "polygons": [
- [
- [
- 1475,
- 673
- ],
- [
- 1455,
- 674
- ],
- [
- 1442,
- 681
- ],
- [
- 1408,
- 704
- ],
- [
- 1400,
- 713
- ],
- [
- 1390,
- 728
- ],
- [
- 1367,
- 778
- ],
- [
- 1358,
- 810
- ],
- [
- 1351,
- 823
- ],
- [
- 1355,
- 848
- ],
- [
- 1346,
- 879
- ],
- [
- 1345,
- 892
- ],
- [
- 1338,
- 910
- ],
- [
- 1338,
- 922
- ],
- [
- 1340,
- 930
- ],
- [
- 1346,
- 944
- ],
- [
- 1358,
- 954
- ],
- [
- 1375,
- 962
- ],
- [
- 1393,
- 966
- ],
- [
- 1422,
- 951
- ],
- [
- 1469,
- 909
- ],
- [
- 1489,
- 880
- ],
- [
- 1494,
- 869
- ],
- [
- 1497,
- 847
- ],
- [
- 1504,
- 823
- ],
- [
- 1504,
- 810
- ],
- [
- 1506,
- 808
- ],
- [
- 1504,
- 787
- ],
- [
- 1506,
- 785
- ],
- [
- 1507,
- 753
- ],
- [
- 1506,
- 751
- ],
- [
- 1504,
- 728
- ],
- [
- 1501,
- 721
- ],
- [
- 1496,
- 696
- ],
- [
- 1491,
- 684
- ],
- [
- 1482,
- 676
- ]
- ]
- ]
- }
- },
- "object_type": "VIEW",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "crop of part view"
- },
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "662bc970-70d1-83de-6641-6b0f77f7af99",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1209&y=658&width=1637&height=980&uuid=5f32b9a4-4f03-41df-893a-5816fdde5030"
- }
- ]
- }
- ]
- },
- {
- "additional_data": {
- "category": "exterior",
- "label": {
- "de": "Dach",
- "en": "Roof",
- "fr": "Toit"
- }
- },
- "binary_size": 145095,
- "detailed_viewpoint": {
- "centers_on": [
- "roof"
- ]
- },
- "id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
- "image_height": 1080,
- "image_type": "close_up",
- "image_width": 1920,
- "mimetype": "image/jpeg",
- "name": "close_up_roof.jpeg",
- "object_type": "IMAGE",
- "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media",
- "rendered_outputs": [
- {
- "additional_data": {
- "description": "rendering of detected parts"
- },
- "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "00b314cf-c28d-d75e-00d9-b6b0c5abfb19",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.672125_e3d7e8dc-ffa7-44cf-a19d-6b08b80b100c.jpg"
- },
- {
- "additional_data": {
- "description": "rendering of detected damages"
- },
- "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
- "created_at": "2023-12-13T13:27:15.750698+00:00",
- "id": "b67370af-bedc-69ad-b619-d2d0b9fa45ea",
- "object_type": "RENDERED_OUTPUT",
- "path": "https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.701367_a449d728-ceb7-49f1-994a-701ac8f4645f.jpg"
- }
- ],
- "views": []
- }
- ],
- "inspection_type": null,
- "inspector_id": null,
- "object_type": "INSPECTION",
- "owner_id": "google-oauth2|109722589040992162710",
- "parts": [
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "640b0825-60c5-887d-6461-aa5a67e3a43a",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "bumper_back",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 67370.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1149&y=453&width=1786&height=931&uuid=6db3ece5-ef73-446e-b224-8fa0abb038ee"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "bf94b068-56d5-7a8a-bffe-121751f356cd",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "bumper_front",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 208376.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=266&y=309&width=1605&height=1312&uuid=254f827d-79cb-45fb-9b11-037fda961c4c"
- },
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 63099.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 1,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1141&y=345&width=1935&height=941&uuid=43c2d2b3-a647-44f5-baa5-eb1553504971"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "68d766cb-9bbf-c196-68bd-c4b49c99edd1",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "door_back_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 20874.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=120&width=796&height=302&uuid=2f0c7d32-1f45-466f-83a6-40362240c624"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [
- "80bac861-01b6-9381-80d0-6a1e0690bfc6"
- ],
- "id": "2d917d5d-6636-5ea7-2dfb-df22611072e0",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "door_front_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 349380.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=74&width=807&height=792&uuid=ef876343-5944-443f-b0ef-19e2a5b21dfb"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "ce9382ec-d9f3-17df-cef9-2093ded53b98",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "fender_back_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 315816.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=463&y=3&width=1695&height=926&uuid=951e4ca7-78fe-4c88-b2da-ad66ea5a19a4"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "0f6665a0-47fe-6e8e-0f0c-c7df40d842c9",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "fender_front_right",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 40201.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=81&y=312&width=779&height=836&uuid=47a952b4-0931-4993-b993-bf589f0f20b4"
- },
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 274.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 1,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1510&y=380&width=1543&height=406&uuid=234a6c06-8755-4250-a3a7-5e572485908d"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "02ef856b-c9bf-3932-0285-2714ce991575",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "grill_radiator",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 4590.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1595&y=419&width=1705&height=502&uuid=de52a81b-ffab-4999-84ae-90082a59e42e"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "437b7a70-311c-6ac9-4311-d80f363a468e",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "handle_front_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 3931.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=368&y=353&width=537&height=479&uuid=e5054412-4407-4e4d-86cf-8c7d4720caf7"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "299b478c-4091-0165-29f1-e5f347b72d22",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "hood",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 385971.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=441&y=312&width=1412&height=1040&uuid=d03546db-19bc-45d3-822c-0dfb77ded67e"
- },
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 1957.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 1,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1624&y=330&width=1702&height=389&uuid=f5422914-4b91-4c3f-af25-bbcf6ce39b70"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "a6b41fba-df6f-2d6f-a6de-bdc5d8490128",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "hubcap_back_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 129960.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=785&y=588&width=1361&height=1021&uuid=12d45d89-b837-4823-8e9d-82bb96027530"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "mirror_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 4617.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=978&y=193&width=1116&height=296&uuid=86b3e176-a99d-4e8e-80e4-18aaad8682df"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "535c6a2a-f1f3-f91b-5336-c855f6d5d55c",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "mirror_right",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 169.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1290&y=283&width=1313&height=300&uuid=79754efd-a945-4c92-b1b2-48b1ab05b845"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "mirror_support",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 182.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1271&y=278&width=1295&height=297&uuid=62e1711e-2119-4a1b-9399-ed26fcf67f58"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "fff6bd07-f76a-1597-ff9c-1f78f04c39d0",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "pillar",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 5341.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1107&y=154&width=1452&height=413&uuid=c6142566-5af5-49f3-bfe3-56cc3e79b57e"
- },
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 167.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 1,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1&y=201&width=67&height=250&uuid=b5bf1613-2d11-4b5d-81ff-25a8c37635f3"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [
- "c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac"
- ],
- "id": "56c74ad5-c077-1d08-56ad-e8aac751314f",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "rocker_panel_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 45738.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=500&width=847&height=1135&uuid=898411ea-6207-4837-9d0e-b9c9498c27be"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [
- "be6cb3ae-8bfd-5efb-be06-11d18cdb72bc",
- "3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b"
- ],
- "id": "833efbf2-dda5-8c31-8354-598dda83a076",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "roof",
- "related_images": [
- {
- "base_image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4",
- "base_image_type": "close_up",
- "image_type": "close_up",
- "mimetype": "image/jpeg",
- "object_type": "IMAGE",
- "order": 0,
- "path": "https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media"
- },
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 68908.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 1,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=158&y=0&width=1131&height=538&uuid=67101981-8f78-46b5-a637-f1719056b000"
- },
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 46823.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 2,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=632&y=0&width=1260&height=346&uuid=98eb6dce-d25c-448e-9d1e-abe957165ce0"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "3ed12b36-252c-98ed-3ebb-8949220ab4aa",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "wheel_back_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 196941.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=737&y=529&width=1432&height=1051&uuid=5b79a55e-69d7-4343-a3ce-8fd512d5058f"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "cae4e3f4-c675-af59-ca8e-418bc153831e",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "window_back_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 19720.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=127&width=796&height=309&uuid=8ee0b33a-83d6-4d3f-9624-f333c2ab63f7"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "08ec65ae-3fb6-e4a3-0886-c7d13890c8e4",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "window_front_left",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 71554.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=88&y=12&width=631&height=419&uuid=465f6cb3-fe92-4911-b219-4e09e37f2e4a"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "f78b2228-70ce-da31-f7e1-805777e8f676",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "windshield_back",
- "related_images": [
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 2248.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1085&y=201&width=1180&height=272&uuid=bd9cdd26-6ac4-42c0-8af7-012cc61460fc"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "3a4f083c-10fb-b483-3a25-aa4317dd98c4",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "windshield_front",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 200714.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=508&y=0&width=1374&height=634&uuid=26a62e63-16f8-4861-90c5-eabc18a6adfa"
- },
- {
- "base_image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1",
- "base_image_type": "beauty_shot",
- "item_area": 3161.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 1,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1026&y=154&width=1162&height=255&uuid=bc4222b3-d65e-4c38-a7d3-3f6886607e33"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "a2bd7491-a911-ffdb-a2d7-d6eeae37d39c",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "fender_front_left",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 41287.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1066&y=299&width=1795&height=847&uuid=8ace9da8-c7fa-4ed3-8671-4185fff3235b"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "17fd8970-6229-d165-1797-2b0f650ffd22",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "fog_light_front_left",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 1013.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1267&y=1019&width=1314&height=1055&uuid=93b4e516-ebf0-4434-83e3-1b60842006c7"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "4c70355e-6197-3c0b-4c1a-972166b1104c",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "head_light_left",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 34315.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1209&y=658&width=1637&height=980&uuid=5f32b9a4-4f03-41df-893a-5816fdde5030"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "6d6b4bcd-e710-686c-6d01-e9b2e036442b",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "head_light_right",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 36407.0,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=206&y=649&width=656&height=987&uuid=3e69cdc5-2371-4eb9-86ff-cedcfde8addc"
- }
- ]
- },
- {
- "created_at": "2023-12-13T13:27:15.023995+00:00",
- "created_by": "algo",
- "damage_ids": [],
- "id": "4d9cb158-d7ed-c73e-4df6-1327d0cbeb79",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "object_type": "PART",
- "part_type": "wiper_front",
- "related_images": [
- {
- "base_image_id": "a842f4bf-5404-215b-a828-56c053220d1c",
- "base_image_type": "beauty_shot",
- "item_area": 3536.5,
- "mimetype": "image/jpeg",
- "object_type": "RENDERED_OUTPUT",
- "order": 0,
- "path": "https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=947&y=335&width=1216&height=535&uuid=fb9e798e-dd63-4bc8-ba76-70c565f93cea"
- }
- ]
- }
- ],
- "pdf_generation_ready": true,
- "pricing": {
- "details": {},
- "total_price": 0
- },
- "related_inspection_id": null,
- "severity_results": [
- {
- "created_by": "algo",
- "id": "ddf61a7f-8f66-65d9-dd9c-b8008840499e",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "is_user_modified": false,
- "label": "door_front_left",
- "object_type": "SEVERITY_RESULT",
- "related_item_id": "2d917d5d-6636-5ea7-2dfb-df22611072e0",
- "related_item_type": "part",
- "value": {
- "custom_severity": {
- "level": 3,
- "pricing": 282,
- "repair_operation": {
- "ADDITIONAL": false,
- "PAINT": 1,
- "REPLACE": false,
- "T1": 0,
- "T2": 0
- }
- }
- }
- },
- {
- "created_by": "algo",
- "id": "ca8b97ee-a50f-919b-cae1-3591a229bddc",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "is_user_modified": false,
- "label": "rocker_panel_left",
- "object_type": "SEVERITY_RESULT",
- "related_item_id": "56c74ad5-c077-1d08-56ad-e8aac751314f",
- "related_item_type": "part",
- "value": {
- "custom_severity": {
- "level": 1,
- "pricing": 264,
- "repair_operation": {
- "ADDITIONAL": false,
- "PAINT": 1,
- "REPLACE": false,
- "T1": 0,
- "T2": 0
- }
- }
- }
- },
- {
- "created_by": "algo",
- "id": "5dfffeae-6b5f-a699-5d95-5cd16c798ade",
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "is_user_modified": false,
- "label": "roof",
- "object_type": "SEVERITY_RESULT",
- "related_item_id": "833efbf2-dda5-8c31-8354-598dda83a076",
- "related_item_type": "part",
- "value": {
- "custom_severity": {
- "level": 1,
- "pricing": 480,
- "repair_operation": {
- "ADDITIONAL": false,
- "PAINT": 0,
- "REPLACE": false,
- "T1": 0,
- "T2": 0
- }
- }
- }
- }
- ],
- "tasks": [
- {
- "arguments": {
- "damage_score_threshold": 0.3,
- "generate_subimages_damages": {
- "damage_view_part_interpolation": 0.0,
- "generate_tight": false,
- "margin": 0,
- "quality": 0.9,
- "ratio": 1.3333333333333333
- },
- "generate_subimages_parts": {
- "damage_view_part_interpolation": 0.0,
- "generate_tight": false,
- "margin": 0,
- "quality": 0.9,
- "ratio": 1.3333333333333333
- },
- "generate_visual_output": {
- "generate_damages": true,
- "generate_parts": true
- }
- },
- "confidence_post_execution": true,
- "confidence_pre_execution_status": "NOT_STARTED",
- "created_at": "2023-12-13T13:27:09.065973+00:00",
- "done_at": "2023-12-13T13:27:18.366089+00:00",
- "id": "fd605401-c5b6-8400-fd0a-f67ec290a847",
- "images": [
- {
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1"
- },
- {
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c"
- },
- {
- "image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4"
- }
- ],
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "name": "damage_detection",
- "object_type": "TASK",
- "status": "DONE"
- },
- {
- "arguments": {},
- "confidence_pre_execution_status": "NOT_STARTED",
- "created_at": "2023-12-13T13:27:09.112670+00:00",
- "id": "f3ce86b5-d3a8-27dc-f3a4-24cad48e0b9b",
- "images": [
- {
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1"
- },
- {
- "image_id": "a842f4bf-5404-215b-a828-56c053220d1c"
- },
- {
- "image_id": "8ad728c4-90ee-7983-8abd-8abb97c855c4"
- }
- ],
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "name": "compliances",
- "object_type": "TASK",
- "status": "IN_PROGRESS"
- },
- {
- "arguments": {
- "use_longshots": true
- },
- "confidence_pre_execution_status": "NOT_STARTED",
- "created_at": "2023-12-13T13:27:09.084506+00:00",
- "done_at": "2023-12-13T13:27:17.475157+00:00",
- "id": "89216d57-0d6d-dd29-894b-cf280a4bf16e",
- "images": [
- {
- "image_id": "6a5e9a4c-8752-c1e6-6a34-38338074eda1"
- }
- ],
- "inspection_id": "42242f0b-378b-1325-424e-8d7430ad3f62",
- "name": "wheel_analysis",
- "object_type": "TASK",
- "status": "DONE"
- }
- ],
- "usage_duration": null,
- "vehicle": {
- "additional_data": {
- "brand": {
- "confidence": 0.8455246090888977,
- "prediction": "Toyota"
- },
- "model": {
- "confidence": 0.8455246090888977,
- "prediction": "Aygo"
- },
- "plate": {
- "confidence": -1.0,
- "prediction": ""
- },
- "vehicle_type": "car"
- },
- "brand": "Toyota",
- "car_registration": null,
- "color": null,
- "created_at": "2023-12-13T13:27:09.065973+00:00",
- "date_of_circulation": null,
- "deleted_at": null,
- "duplicate_keys": null,
- "expertise_requested": null,
- "exterior_cleanliness": null,
- "id": "051b7118-915f-4bec-0571-d367967967ab",
- "interior_cleanliness": null,
- "market_value": {},
- "mileage": {},
- "model": "Aygo",
- "object_type": "VEHICLE",
- "owner_info": null,
- "plate": "",
- "trade_in_offer": null,
- "vehicle_quotation": null,
- "vehicle_type": "car",
- "vin": "1HGEJ8244YL06777"
- },
- "wheel_analysis": []
-}
diff --git a/packages/public/network/test/api/requests/inspections/getInspectionParsed.testdata.ts b/packages/public/network/test/api/requests/inspections/getInspectionParsed.testdata.ts
deleted file mode 100644
index 13d016214..000000000
--- a/packages/public/network/test/api/requests/inspections/getInspectionParsed.testdata.ts
+++ /dev/null
@@ -1,1730 +0,0 @@
-export default {
- damages: [
- {
- id: '80bac861-01b6-9381-80d0-6a1e0690bfc6',
- entityType: 'DAMAGE',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'scratch',
- size: 50.021743434811555,
- parts: [
- '2d917d5d-6636-5ea7-2dfb-df22611072e0'
- ],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac',
- entityType: 'DAMAGE',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'scratch',
- size: 3.17945481147886,
- parts: [
- '56c74ad5-c077-1d08-56ad-e8aac751314f'
- ],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'be6cb3ae-8bfd-5efb-be06-11d18cdb72bc',
- entityType: 'DAMAGE',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'paint_peeling',
- parts: [
- '833efbf2-dda5-8c31-8354-598dda83a076'
- ],
- relatedImages: [
- '8ad728c4-90ee-7983-8abd-8abb97c855c4'
- ]
- },
- {
- id: '3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b',
- entityType: 'DAMAGE',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'misshape',
- parts: [
- '833efbf2-dda5-8c31-8354-598dda83a076'
- ],
- relatedImages: [
- '8ad728c4-90ee-7983-8abd-8abb97c855c4'
- ]
- }
- ],
- images: [
- {
- id: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- entityType: 'IMAGE',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- path: 'https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg?generation=1702474029543236&alt=media',
- width: 1920,
- height: 1080,
- size: 141539,
- mimetype: 'image/jpeg',
- type: 'beauty_shot',
- viewpoint: {
- confidence: 0.9847987294197083,
- prediction: 'left'
- },
- label: {
- de: 'Hinten Seitlich Niedrig Links',
- en: 'Rear Lateral Low Left',
- fr: 'Arrière Gauche Latéral - vue basse',
- },
- renderedOutputs: [
- '87650e8f-b8c9-6c6b-870f-acf0bfef402c',
- 'd44c1e3d-fcab-dd30-d426-bc42fb8df177'
- ],
- views: [
- 'ab2f9850-0165-3458-ab45-3a2f0643181f',
- '4d503f11-7b27-a0aa-4d3a-9d6e7c018ced',
- 'afbadee2-e194-3b07-afd0-7c9de6b21740',
- '7145ebd3-58f4-2328-712f-49ac5fd20f6f',
- 'b0b552b2-f1b6-1bcc-b0df-f0cdf690378b',
- 'ca7c9b47-530d-6eb3-ca16-3938542b42f4',
- 'aba1ed91-94b8-c4d3-abcb-4fee939ee894',
- '25ac94fe-126f-2614-25c6-368115490a53',
- '78340423-5a5d-6f33-785e-a65c5d7b4374',
- '667da482-f302-4e5d-6617-06fdf424621a',
- '3bdc7c4e-75cc-4386-3bb6-de3172ea6fc1',
- '47ba5f70-96de-6fad-47d0-fd0f91f843ea',
- 'd838da37-94f5-586a-d852-784893d3742d',
- 'f5adeae9-5e35-ec2d-f5c7-48965913c06a',
- '3179a7a6-8703-f8dc-3113-05d98025d49b',
- '72d12f4f-8de3-2ed7-72bb-8d308ac50290',
- '260594c3-2b62-74c0-266f-36bc2c445887',
- '858717fe-a458-fbb3-85ed-b581a37ed7f4',
- '461084a6-d7e8-4430-467a-26d9d0ce6877',
- '8eab6f21-2c6b-59a7-8ec1-cd5e2b4d75e0',
- '71dbdd16-1c49-864a-71b1-7f691b6faa0d',
- '8074a493-4434-7b65-801e-06ec43125722',
- '439bd668-c016-75e3-43f1-7417c73059a4'
- ],
- additionalData: {
- category: 'exterior',
- sightId: 'ffocus18-S3kgFOBb',
- label: {
- de: 'Hinten Seitlich Niedrig Links',
- en: 'Rear Lateral Low Left',
- fr: 'Arrière Gauche Latéral - vue basse'
- }
- }
- },
- {
- id: 'a842f4bf-5404-215b-a828-56c053220d1c',
- entityType: 'IMAGE',
- path: 'https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg?generation=1702474029549060&alt=media',
- width: 1920,
- height: 1080,
- size: 160207,
- mimetype: 'image/jpeg',
- type: 'beauty_shot',
- viewpoint: {
- confidence: 0.4582550525665283,
- prediction: 'front_left',
- },
- compliances: {},
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- label: {
- de: 'Motorhaube',
- en: 'Hood',
- fr: 'Capot',
- },
- renderedOutputs: [
- 'd14655ab-f662-3898-d12c-f7d4f14414df',
- 'ffee30b3-6d49-58ea-ff84-92cc6a6f74ad'
- ],
- views: [
- '66937b3f-dc7a-f700-66f9-d940db5cdb47',
- '058c0533-0a74-dcb3-05e6-a74c0d52f0f4',
- '740607eb-7996-67f0-746c-a5947eb04bb7',
- 'be3948a9-777a-be37-be53-ead6705c9270',
- 'd64d13af-dd3c-cc9e-d627-b1d0da1ae0d9',
- '6805436b-c50b-756f-686f-e114c22d5928',
- '65beb0eb-d1ea-7d19-65d4-1294d6cc515e',
- '5084700a-043a-6074-50ee-d275031c4c33',
- '0383f405-66a7-0af0-03e9-567a618126b7',
- '29ed50ba-aa7d-351a-2987-f2c5ad5b195d',
- 'e7a16ffb-a22c-8705-e7cb-cd84a50aab42'
- ],
- additionalData: {
- category: 'exterior',
- sightId: 'ffocus18-3TiCVAaN',
- label: {
- de: 'Motorhaube',
- en: 'Hood',
- fr: 'Capot'
- }
- }
- },
- {
- id: '8ad728c4-90ee-7983-8abd-8abb97c855c4',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- entityType: 'IMAGE',
- path: 'https://www.googleapis.com/download/storage/v1/b/core-preview-images/o/close_up_roof.jpeg-2a10933e-0e7d-4499-9586-82b932bd0b37.jpeg?generation=1702474029574675&alt=media',
- width: 1920,
- height: 1080,
- size: 145095,
- mimetype: 'image/jpeg',
- type: 'close_up',
- detailedViewpoint: {
- centersOn: [
- 'roof'
- ],
- },
- label: {
- de: 'Dach',
- en: 'Roof',
- fr: 'Toit'
- },
- renderedOutputs: [
- '00b314cf-c28d-d75e-00d9-b6b0c5abfb19',
- 'b67370af-bedc-69ad-b619-d2d0b9fa45ea'
- ],
- views: [],
- additionalData: {
- category: 'exterior',
- label: {
- de: 'Dach',
- en: 'Roof',
- fr: 'Toit'
- }
- }
- }
- ],
- inspections: [
- {
- id: '42242f0b-378b-1325-424e-8d7430ad3f62',
- entityType: 'INSPECTION',
- tasks: [
- 'fd605401-c5b6-8400-fd0a-f67ec290a847',
- 'f3ce86b5-d3a8-27dc-f3a4-24cad48e0b9b',
- '89216d57-0d6d-dd29-894b-cf280a4bf16e'
- ],
- images: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- 'a842f4bf-5404-215b-a828-56c053220d1c',
- '8ad728c4-90ee-7983-8abd-8abb97c855c4'
- ],
- damages: [
- '80bac861-01b6-9381-80d0-6a1e0690bfc6',
- 'c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac',
- 'be6cb3ae-8bfd-5efb-be06-11d18cdb72bc',
- '3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b'
- ],
- parts: [
- '640b0825-60c5-887d-6461-aa5a67e3a43a',
- 'bf94b068-56d5-7a8a-bffe-121751f356cd',
- '68d766cb-9bbf-c196-68bd-c4b49c99edd1',
- '2d917d5d-6636-5ea7-2dfb-df22611072e0',
- 'ce9382ec-d9f3-17df-cef9-2093ded53b98',
- '0f6665a0-47fe-6e8e-0f0c-c7df40d842c9',
- '02ef856b-c9bf-3932-0285-2714ce991575',
- '437b7a70-311c-6ac9-4311-d80f363a468e',
- '299b478c-4091-0165-29f1-e5f347b72d22',
- 'a6b41fba-df6f-2d6f-a6de-bdc5d8490128',
- 'c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a',
- '535c6a2a-f1f3-f91b-5336-c855f6d5d55c',
- 'fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3',
- 'fff6bd07-f76a-1597-ff9c-1f78f04c39d0',
- '56c74ad5-c077-1d08-56ad-e8aac751314f',
- '833efbf2-dda5-8c31-8354-598dda83a076',
- '3ed12b36-252c-98ed-3ebb-8949220ab4aa',
- 'cae4e3f4-c675-af59-ca8e-418bc153831e',
- '08ec65ae-3fb6-e4a3-0886-c7d13890c8e4',
- 'f78b2228-70ce-da31-f7e1-805777e8f676',
- '3a4f083c-10fb-b483-3a25-aa4317dd98c4',
- 'a2bd7491-a911-ffdb-a2d7-d6eeae37d39c',
- '17fd8970-6229-d165-1797-2b0f650ffd22',
- '4c70355e-6197-3c0b-4c1a-972166b1104c',
- '6d6b4bcd-e710-686c-6d01-e9b2e036442b',
- '4d9cb158-d7ed-c73e-4df6-1327d0cbeb79'
- ],
- wheelAnalysis: [],
- severityResults: [
- 'ddf61a7f-8f66-65d9-dd9c-b8008840499e',
- 'ca8b97ee-a50f-919b-cae1-3591a229bddc',
- '5dfffeae-6b5f-a699-5d95-5cd16c798ade'
- ],
- pricing: {
- details: {},
- totalPrice: 0
- },
- additionalData: {
- damage_detection_version: 'v2',
- environment: {
- custom_inspection: true
- },
- use_dynamic_crops: true
- }
- }
- ],
- parts: [
- {
- id: '640b0825-60c5-887d-6461-aa5a67e3a43a',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'bumper_back',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'bf94b068-56d5-7a8a-bffe-121751f356cd',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'bumper_front',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c',
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '68d766cb-9bbf-c196-68bd-c4b49c99edd1',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'door_back_left',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '2d917d5d-6636-5ea7-2dfb-df22611072e0',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'door_front_left',
- damages: [
- '80bac861-01b6-9381-80d0-6a1e0690bfc6'
- ],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'ce9382ec-d9f3-17df-cef9-2093ded53b98',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'fender_back_left',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '0f6665a0-47fe-6e8e-0f0c-c7df40d842c9',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'fender_front_right',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c',
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '02ef856b-c9bf-3932-0285-2714ce991575',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'grill_radiator',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '437b7a70-311c-6ac9-4311-d80f363a468e',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'handle_front_left',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '299b478c-4091-0165-29f1-e5f347b72d22',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'hood',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c',
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'a6b41fba-df6f-2d6f-a6de-bdc5d8490128',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'hubcap_back_left',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'mirror_left',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '535c6a2a-f1f3-f91b-5336-c855f6d5d55c',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'mirror_right',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'mirror_support',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'fff6bd07-f76a-1597-ff9c-1f78f04c39d0',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'pillar',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c',
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '56c74ad5-c077-1d08-56ad-e8aac751314f',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'rocker_panel_left',
- damages: [
- 'c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac'
- ],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '833efbf2-dda5-8c31-8354-598dda83a076',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'roof',
- damages: [
- 'be6cb3ae-8bfd-5efb-be06-11d18cdb72bc',
- '3bbe87db-c968-9a0c-3bd4-25a4ce4eb64b'
- ],
- relatedImages: [
- '8ad728c4-90ee-7983-8abd-8abb97c855c4',
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- 'a842f4bf-5404-215b-a828-56c053220d1c'
- ]
- },
- {
- id: '3ed12b36-252c-98ed-3ebb-8949220ab4aa',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'wheel_back_left',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'cae4e3f4-c675-af59-ca8e-418bc153831e',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'window_back_left',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '08ec65ae-3fb6-e4a3-0886-c7d13890c8e4',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'window_front_left',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'f78b2228-70ce-da31-f7e1-805777e8f676',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'windshield_back',
- damages: [],
- relatedImages: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: '3a4f083c-10fb-b483-3a25-aa4317dd98c4',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'windshield_front',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c',
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- },
- {
- id: 'a2bd7491-a911-ffdb-a2d7-d6eeae37d39c',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'fender_front_left',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c'
- ]
- },
- {
- id: '17fd8970-6229-d165-1797-2b0f650ffd22',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'fog_light_front_left',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c'
- ]
- },
- {
- id: '4c70355e-6197-3c0b-4c1a-972166b1104c',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'head_light_left',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c'
- ]
- },
- {
- id: '6d6b4bcd-e710-686c-6d01-e9b2e036442b',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'head_light_right',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c'
- ]
- },
- {
- id: '4d9cb158-d7ed-c73e-4df6-1327d0cbeb79',
- entityType: 'PART',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- type: 'wiper_front',
- damages: [],
- relatedImages: [
- 'a842f4bf-5404-215b-a828-56c053220d1c'
- ]
- }
- ],
- renderedOutputs: [
- {
- id: '87650e8f-b8c9-6c6b-870f-acf0bfef402c',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.688404_c49980a3-4c96-44ec-bdb5-76e25391efc3.jpg',
- additionalData: {
- description: 'rendering of detected parts'
- }
- },
- {
- id: 'd44c1e3d-fcab-dd30-d426-bc42fb8df177',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.710278_24b3ec1c-5282-4451-8985-afd589513069.jpg',
- additionalData: {
- description: 'rendering of detected damages'
- }
- },
- {
- id: 'd8195eaa-8800-3c56-d873-fcd58f261011',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=88&y=12&width=631&height=419&uuid=465f6cb3-fe92-4911-b219-4e09e37f2e4a',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '0bd88b55-6e6f-d766-0bb2-292a6949fb21',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=127&width=796&height=309&uuid=8ee0b33a-83d6-4d3f-9624-f333c2ab63f7',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '4df4bd91-b34d-5da2-4d9e-1feeb46b71e5',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1624&y=330&width=1702&height=389&uuid=f5422914-4b91-4c3f-af25-bbcf6ce39b70',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'fd4a9f9b-7e8b-59e2-fd20-3de479ad75a5',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1&y=201&width=67&height=250&uuid=b5bf1613-2d11-4b5d-81ff-25a8c37635f3',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '015826ca-e188-6e37-0132-84b5e6ae4270',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=158&y=0&width=1131&height=538&uuid=67101981-8f78-46b5-a637-f1719056b000',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '57d9e4d8-030a-c10e-57b3-46a7042ced49',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1595&y=419&width=1705&height=502&uuid=de52a81b-ffab-4999-84ae-90082a59e42e',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '1ffaa2e9-4a52-083a-1f90-00964d74247d',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1026&y=154&width=1162&height=255&uuid=bc4222b3-d65e-4c38-a7d3-3f6886607e33',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'cc7caa83-c7a7-375f-cc16-08fcc0811b18',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=368&y=353&width=537&height=479&uuid=e5054412-4407-4e4d-86cf-8c7d4720caf7',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '5a8a4569-ae52-aeb3-5ae0-e716a97482f4',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=216&y=504&width=531&height=739&uuid=72c9a923-d181-4584-8d29-f6290a41d140',
- additionalData: {
- description: 'crop of damage view'
- }
- },
- {
- id: '2760d5fe-df9e-bb67-270a-7781d8b89720',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1290&y=283&width=1313&height=300&uuid=79754efd-a945-4c92-b1b2-48b1ab05b845',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '1567aac5-c8e4-7584-150d-08bacfc259c3',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=978&y=193&width=1116&height=296&uuid=86b3e176-a99d-4e8e-80e4-18aaad8682df',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '1df49927-6747-6719-1d9e-3b5860614b5e',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1510&y=380&width=1543&height=406&uuid=234a6c06-8755-4250-a3a7-5e572485908d',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'b69e0f13-edf3-0474-b6f4-ad6cead52833',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=785&y=588&width=1361&height=1021&uuid=12d45d89-b837-4823-8e9d-82bb96027530',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'fc3a666a-8ea9-f60e-fc50-c415898fda49',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1141&y=345&width=1935&height=941&uuid=43c2d2b3-a647-44f5-baa5-eb1553504971',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '9d797036-7e3b-de8f-9d13-d249791df2c8',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=719&y=797&width=757&height=826&uuid=bcd1e0e8-c6bd-4ea6-addc-746853672836',
- additionalData: {
- description: 'crop of damage view'
- }
- },
- {
- id: '23585679-dd88-b9fc-2332-f406daae95bb',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=463&y=3&width=1695&height=926&uuid=951e4ca7-78fe-4c88-b2da-ad66ea5a19a4',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'f958525f-3602-3369-f932-f02031241f2e',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1271&y=278&width=1295&height=297&uuid=62e1711e-2119-4a1b-9399-ed26fcf67f58',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '707c5d25-0878-8683-7016-ff5a0f5eaac4',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1149&y=453&width=1786&height=931&uuid=6db3ece5-ef73-446e-b224-8fa0abb038ee',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '123a4495-771b-8a5d-1250-e6ea703da61a',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=500&width=847&height=1135&uuid=898411ea-6207-4837-9d0e-b9c9498c27be',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '5ed2f756-0753-6abc-5eb8-5529007546fb',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=737&y=529&width=1432&height=1051&uuid=5b79a55e-69d7-4343-a3ce-8fd512d5058f',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'c5b13d52-feef-1524-c5db-9f2df9c93963',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=1085&y=201&width=1180&height=272&uuid=bd9cdd26-6ac4-42c0-8af7-012cc61460fc',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '311115d4-789e-625e-317b-b7ab7fb84e19',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=552&y=120&width=796&height=302&uuid=2f0c7d32-1f45-466f-83a6-40362240c624',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '8d051e26-285e-30e7-8d6f-bc592f781ca0',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=rear_lateral_low_left.jpeg-eefefe38-b25b-471f-89ad-8adb2cbcca1f.jpeg&x=0&y=74&width=807&height=792&uuid=ef876343-5944-443f-b0ef-19e2a5b21dfb',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'd14655ab-f662-3898-d12c-f7d4f14414df',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.690063_21731085-0378-40c7-b729-17a56c6aee55.jpg',
- additionalData: {
- description: 'rendering of detected parts'
- }
- },
- {
- id: 'ffee30b3-6d49-58ea-ff84-92cc6a6f74ad',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.709410_5f163233-6d20-4990-bd54-0cf001b052bc.jpg',
- additionalData: {
- description: 'rendering of detected damages'
- }
- },
- {
- id: '337b4153-f26e-59f9-3311-e32cf54875be',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=947&y=335&width=1216&height=535&uuid=fb9e798e-dd63-4bc8-ba76-70c565f93cea',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '325ba3da-5e3e-40d0-3231-01a559186c97',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1107&y=154&width=1452&height=413&uuid=c6142566-5af5-49f3-bfe3-56cc3e79b57e',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'f85c1c27-7bec-9d6c-f836-be587ccab12b',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=441&y=312&width=1412&height=1040&uuid=d03546db-19bc-45d3-822c-0dfb77ded67e',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '03a8e4a9-d417-c1fd-03c2-46d6d331edba',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=81&y=312&width=779&height=836&uuid=47a952b4-0931-4993-b993-bf589f0f20b4',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '4c2bf631-5213-7c6c-4c41-544e5535502b',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=508&y=0&width=1374&height=634&uuid=26a62e63-16f8-4861-90c5-eabc18a6adfa',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '2a5bdb7c-a086-b288-2a31-7903a7a09ecf',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=266&y=309&width=1605&height=1312&uuid=254f827d-79cb-45fb-9b11-037fda961c4c',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '8e49cf30-378e-1cf1-8e23-6d4f30a830b6',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=206&y=649&width=656&height=987&uuid=3e69cdc5-2371-4eb9-86ff-cedcfde8addc',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: 'ce9f0c5d-2b3e-d5b1-cef5-ae222c18f9f6',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1066&y=299&width=1795&height=847&uuid=8ace9da8-c7fa-4ed3-8671-4185fff3235b',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '02d8800f-7866-58aa-02b2-22707f4074ed',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1267&y=1019&width=1314&height=1055&uuid=93b4e516-ebf0-4434-83e3-1b60842006c7',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '79c36708-b835-5fdb-79a9-c577bf13739c',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=632&y=0&width=1260&height=346&uuid=98eb6dce-d25c-448e-9d1e-abe957165ce0',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '662bc970-70d1-83de-6641-6b0f77f7af99',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: 'a842f4bf-5404-215b-a828-56c053220d1c',
- path: 'https://get-image-crop-4la6rtzclq-ew.a.run.app?bucket=core-preview-images&object_path=hood.jpeg-8d7dadf7-414d-4771-9d0e-127312706291.jpeg&x=1209&y=658&width=1637&height=980&uuid=5f32b9a4-4f03-41df-893a-5816fdde5030',
- additionalData: {
- description: 'crop of part view'
- }
- },
- {
- id: '00b314cf-c28d-d75e-00d9-b6b0c5abfb19',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '8ad728c4-90ee-7983-8abd-8abb97c855c4',
- path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.672125_e3d7e8dc-ffa7-44cf-a19d-6b08b80b100c.jpg',
- additionalData: {
- description: 'rendering of detected parts'
- }
- },
- {
- id: 'b67370af-bedc-69ad-b619-d2d0b9fa45ea',
- entityType: 'RENDERED_OUTPUT',
- baseImageId: '8ad728c4-90ee-7983-8abd-8abb97c855c4',
- path: 'https://storage.googleapis.com/core-preview-images/291346187255024138_2023-12-13%2013%3A27%3A16.701367_a449d728-ceb7-49f1-994a-701ac8f4645f.jpg',
- additionalData: {
- description: 'rendering of detected damages'
- }
- }
- ],
- severityResults: [
- {
- id: 'ddf61a7f-8f66-65d9-dd9c-b8008840499e',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- entityType: 'SEVERITY_RESULT',
- label: 'door_front_left',
- isUserModified: false,
- relatedItemId: '2d917d5d-6636-5ea7-2dfb-df22611072e0',
- relatedItemType: 'part',
- value: {
- level: 3,
- pricing: 282
- }
- },
- {
- id: 'ca8b97ee-a50f-919b-cae1-3591a229bddc',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- entityType: 'SEVERITY_RESULT',
- label: 'rocker_panel_left',
- isUserModified: false,
- relatedItemId: '56c74ad5-c077-1d08-56ad-e8aac751314f',
- relatedItemType: 'part',
- value: {
- level: 1,
- pricing: 264
- }
- },
- {
- id: '5dfffeae-6b5f-a699-5d95-5cd16c798ade',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- entityType: 'SEVERITY_RESULT',
- label: 'roof',
- isUserModified: false,
- relatedItemId: '833efbf2-dda5-8c31-8354-598dda83a076',
- relatedItemType: 'part',
- value: {
- level: 1,
- pricing: 480
- }
- }
- ],
- tasks: [
- {
- id: 'fd605401-c5b6-8400-fd0a-f67ec290a847',
- entityType: 'TASK',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- name: 'damage_detection',
- status: 'DONE',
- images: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- 'a842f4bf-5404-215b-a828-56c053220d1c',
- '8ad728c4-90ee-7983-8abd-8abb97c855c4'
- ]
- },
- {
- id: 'f3ce86b5-d3a8-27dc-f3a4-24cad48e0b9b',
- entityType: 'TASK',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- name: 'compliances',
- status: 'IN_PROGRESS',
- images: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1',
- 'a842f4bf-5404-215b-a828-56c053220d1c',
- '8ad728c4-90ee-7983-8abd-8abb97c855c4'
- ]
- },
- {
- id: '89216d57-0d6d-dd29-894b-cf280a4bf16e',
- entityType: 'TASK',
- inspectionId: '42242f0b-378b-1325-424e-8d7430ad3f62',
- name: 'wheel_analysis',
- status: 'DONE',
- images: [
- '6a5e9a4c-8752-c1e6-6a34-38338074eda1'
- ]
- }
- ],
- vehicles: [
- {
- id: '051b7118-915f-4bec-0571-d367967967ab',
- entityType: 'VEHICLE',
- brand: 'Toyota',
- model: 'Aygo',
- plate: '',
- vin: '1HGEJ8244YL06777',
- color: null,
- exteriorCleanliness: null,
- interior_cleanliness: null,
- dateOfCirculation: null,
- duplicateKeys: null,
- expertiseRequested: null,
- carRegistration: null,
- vehicleQuotation: null,
- tradeInOffer: null,
- ownerInfo: null,
- additionalData: {
- brand: {
- confidence: 0.8455246090888977,
- prediction: 'Toyota'
- },
- model: {
- confidence: 0.8455246090888977,
- prediction: 'Aygo'
- },
- plate: {
- confidence: -1,
- prediction: ''
- },
- vehicle_type: 'car'
- }
- }
- ],
- views: [
- {
- id: 'ab2f9850-0165-3458-ab45-3a2f0643181f',
- entityType: 'VIEW',
- elementId: '08ec65ae-3fb6-e4a3-0886-c7d13890c8e4',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 88,
- yMin: 113,
- width: 543,
- height: 205
- },
- polygons: [
- [[606, 137], [598, 131], [576, 126], [491, 124], [489, 122], [369, 122], [367, 124], [332, 124], [330, 126], [289, 128], [243, 146], [202, 155], [152, 176], [134, 189], [115, 211], [113, 218], [113, 253], [119, 233], [139, 213], [150, 216], [160, 233], [167, 237], [176, 237], [187, 233], [202, 233], [210, 242], [210, 246], [204, 253], [176, 268], [171, 266], [152, 266], [141, 274], [139, 300], [128, 309], [141, 309], [143, 307], [191, 309], [193, 307], [219, 307], [221, 305], [276, 305], [278, 303], [289, 303], [291, 305], [319, 305], [321, 303], [487, 303], [489, 300], [519, 300], [526, 298], [537, 290], [552, 261], [558, 237], [582, 194], [589, 168],[606, 146]]
- ]
- }
- },
- renderedOutputs: [
- 'd8195eaa-8800-3c56-d873-fcd58f261011'
- ]
- },
- {
- id: '4d503f11-7b27-a0aa-4d3a-9d6e7c018ced',
- entityType: 'VIEW',
- elementId: 'cae4e3f4-c675-af59-ca8e-418bc153831e',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 552,
- yMin: 139,
- width: 244,
- height: 158
- },
- polygons: [
- [[563, 281], [572, 290], [576, 290], [585, 281], [598, 281], [600, 279], [632, 285], [669, 283], [702, 276], [715, 276], [743, 283], [774, 283], [785, 276], [785, 263], [776, 244], [763, 224], [743, 205], [730, 196], [713, 176], [680, 155], [665, 148], [639, 146], [632, 152], [619, 189], [600, 209], [589, 229], [569, 255],[563, 272]]
- ]
- }
- },
- renderedOutputs: [
- '0bd88b55-6e6f-d766-0bb2-292a6949fb21'
- ]
- },
- {
- id: 'afbadee2-e194-3b07-afd0-7c9de6b21740',
- entityType: 'VIEW',
- elementId: '299b478c-4091-0165-29f1-e5f347b72d22',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1630,
- yMin: 330,
- width: 66,
- height: 59
- },
- polygons: [
- [[1635, 335], [1633, 342], [1639, 353], [1639, 361], [1661, 385], [1670, 388], [1683, 383], [1694, 370], [1692, 361], [1685, 355], [1674, 351], [1666, 340],[1646, 333]]
- ]
- }
- },
- renderedOutputs: [
- '4df4bd91-b34d-5da2-4d9e-1feeb46b71e5'
- ]
- },
- {
- id: '7145ebd3-58f4-2328-712f-49ac5fd20f6f',
- entityType: 'VIEW',
- elementId: 'fff6bd07-f76a-1597-ff9c-1f78f04c39d0',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1,
- yMin: 203,
- width: 66,
- height: 45
- },
- polygons: [
- [[65, 205], [4, 244], [8, 246], [23, 233],[43, 224]]
- ]
- }
- },
- renderedOutputs: [
- 'fd4a9f9b-7e8b-59e2-fd20-3de479ad75a5'
- ]
- },
- {
- id: 'b0b552b2-f1b6-1bcc-b0df-f0cdf690378b',
- entityType: 'VIEW',
- elementId: '833efbf2-dda5-8c31-8354-598dda83a076',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 158,
- yMin: 45,
- width: 973,
- height: 258
- },
- polygons: [
- [[202, 126], [269, 122], [271, 120], [287, 120], [317, 113], [334, 113], [363, 107], [415, 107], [417, 105], [456, 105], [471, 109], [508, 111], [539, 118], [567, 118], [569, 120], [606, 122], [628, 131], [667, 137], [698, 152], [722, 170], [765, 211], [785, 224], [948, 239], [965, 246], [998, 272], [1026, 285], [1041, 285], [1050, 290], [1065, 292], [1054, 287], [1059, 281], [1078, 281], [1028, 272], [985, 233], [983, 224], [993, 216], [1004, 216], [1020, 222], [1039, 220], [1039, 218], [1035, 218], [1030, 213], [1030, 207], [1037, 200], [1070, 198], [1078, 189], [1087, 185], [1070, 170], [1059, 165], [1037, 172], [1015, 163], [998, 148], [998, 139], [987, 128], [983, 118], [965, 105], [941, 96], [880, 81], [830, 78], [800, 72], [780, 72], [778, 70], [739, 68], [717, 63], [696, 63], [676, 59], [598, 59], [595, 57], [478, 59], [476, 61], [408, 65], [406, 68], [380, 70], [334, 78],[271, 96]]
- ]
- }
- },
- renderedOutputs: [
- '015826ca-e188-6e37-0132-84b5e6ae4270'
- ]
- },
- {
- id: 'ca7c9b47-530d-6eb3-ca16-3938542b42f4',
- entityType: 'VIEW',
- elementId: '02ef856b-c9bf-3932-0285-2714ce991575',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1595,
- yMin: 419,
- width: 110,
- height: 83
- },
- polygons: [
- [[1696, 427], [1692, 422], [1685, 422], [1674, 429], [1659, 433], [1620, 455], [1605, 470], [1600, 479], [1602, 488], [1618, 499], [1635, 499], [1666, 483], [1687, 477], [1700, 464],[1700, 448]]
- ]
- }
- },
- renderedOutputs: [
- '57d9e4d8-030a-c10e-57b3-46a7042ced49'
- ]
- },
- {
- id: 'aba1ed91-94b8-c4d3-abcb-4fee939ee894',
- entityType: 'VIEW',
- elementId: '3a4f083c-10fb-b483-3a25-aa4317dd98c4',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1026,
- yMin: 183,
- width: 136,
- height: 43
- },
- polygons: [
- [[1033, 213], [1041, 218], [1085, 216], [1107, 224], [1113, 224], [1133, 216], [1150, 213], [1157, 198], [1154, 192], [1144, 185], [1115, 185], [1113, 187], [1089, 185], [1070, 200], [1037, 202],[1033, 207]]
- ]
- }
- },
- renderedOutputs: [
- '1ffaa2e9-4a52-083a-1f90-00964d74247d'
- ]
- },
- {
- id: '25ac94fe-126f-2614-25c6-368115490a53',
- entityType: 'VIEW',
- elementId: '437b7a70-311c-6ac9-4311-d80f363a468e',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 368,
- yMin: 397,
- width: 169,
- height: 38
- },
- polygons: [
- [[376, 411], [378, 418], [389, 427], [415, 433], [458, 433], [482, 427], [517, 427], [524, 425], [530, 418], [530, 411], [526, 405], [511, 401], [463, 409], [461, 407], [432, 405], [413, 398], [389, 398],[380, 403]]
- ]
- }
- },
- renderedOutputs: [
- 'cc7caa83-c7a7-375f-cc16-08fcc0811b18'
- ]
- },
- {
- id: '78340423-5a5d-6f33-785e-a65c5d7b4374',
- entityType: 'VIEW',
- elementId: '80bac861-01b6-9381-80d0-6a1e0690bfc6',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 216,
- yMin: 506,
- width: 315,
- height: 231
- },
- polygons: [
- [[503, 550], [498, 544], [496, 543], [488, 542], [487, 541], [477, 542], [475, 544], [468, 537], [468, 536], [466, 534], [465, 532], [465, 529], [464, 528], [464, 525], [461, 522], [460, 518], [451, 509], [449, 509], [443, 506], [436, 506], [434, 507], [432, 509], [424, 526], [414, 536], [402, 542], [397, 547], [386, 551], [381, 556], [378, 556], [374, 558], [368, 563], [364, 565], [361, 568], [355, 570], [351, 574], [347, 575], [341, 578], [337, 582], [335, 583], [332, 583], [331, 584], [328, 584], [324, 586], [321, 586], [317, 588], [314, 591], [312, 592], [309, 592], [301, 596], [294, 603], [292, 609], [284, 618], [282, 622], [282, 624], [280, 626], [280, 627], [273, 634], [271, 638], [269, 647], [267, 649], [267, 652], [263, 659], [263, 668], [264, 670], [273, 679], [275, 680], [283, 679], [285, 677], [292, 677], [293, 678], [299, 679], [304, 684], [304, 687], [302, 689], [301, 693], [297, 697], [291, 700], [285, 706], [281, 707], [273, 715], [271, 716], [268, 716], [266, 718], [257, 720], [254, 723], [246, 724], [245, 725], [242, 725], [241, 726], [228, 726], [227, 727], [217, 727], [216, 728], [216, 730], [218, 732], [234, 732], [235, 733], [240, 733], [244, 735], [247, 735], [250, 737], [255, 737], [256, 736], [260, 736], [261, 735], [266, 735], [268, 733], [276, 731], [281, 726], [285, 725], [286, 724], [288, 724], [295, 717], [299, 715], [301, 715], [308, 708], [312, 707], [321, 698], [324, 697], [326, 695], [327, 692], [338, 681], [340, 676], [350, 666], [350, 664], [352, 661], [353, 657], [355, 656], [365, 646], [370, 644], [372, 642], [380, 638], [383, 638], [386, 636], [389, 636], [390, 635], [393, 635], [394, 634], [397, 634], [398, 633], [402, 633], [404, 631], [407, 631], [410, 629], [413, 629], [414, 628], [419, 628], [420, 627], [424, 627], [425, 626], [430, 626], [439, 621], [443, 621], [444, 620], [447, 620], [448, 619], [452, 619], [453, 618], [462, 617], [463, 616], [466, 616], [467, 615], [470, 615], [471, 614], [474, 614], [475, 613], [482, 613], [483, 612], [487, 612], [488, 613], [492, 613], [493, 614], [496, 614], [497, 615], [500, 615], [501, 616], [503, 616], [504, 617], [508, 617], [509, 618], [523, 618], [524, 617], [527, 617], [531, 614], [529, 611], [529, 606], [526, 602], [526, 601], [513, 588], [513, 585], [512, 584], [512, 576], [511, 575], [511, 570], [506, 561],[505, 554]]
- ]
- }
- },
- renderedOutputs: [
- '5a8a4569-ae52-aeb3-5ae0-e716a97482f4'
- ]
- },
- {
- id: '667da482-f302-4e5d-6617-06fdf424621a',
- entityType: 'VIEW',
- elementId: '535c6a2a-f1f3-f91b-5336-c855f6d5d55c',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1290,
- yMin: 287,
- width: 23,
- height: 9
- },
- polygons: [
- [[1291, 287], [1291, 294], [1298, 296], [1313, 294],[1311, 287]]
- ]
- }
- },
- renderedOutputs: [
- '2760d5fe-df9e-bb67-270a-7781d8b89720'
- ]
- },
- {
- id: '3bdc7c4e-75cc-4386-3bb6-de3172ea6fc1',
- entityType: 'VIEW',
- elementId: 'c787d6ce-b4d5-bf5d-c7ed-74b1b3f3931a',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 978,
- yMin: 215,
- width: 138,
- height: 59
- },
- polygons: [
- [[985, 224], [987, 233], [1000, 246], [1033, 272], [1041, 272], [1054, 266], [1076, 266], [1087, 259], [1087, 255], [1094, 248], [1100, 246], [1111, 235], [1109, 226], [1085, 218], [1041, 220], [1026, 224], [1020, 224], [1004, 218],[993, 218]]
- ]
- }
- },
- renderedOutputs: [
- '1567aac5-c8e4-7584-150d-08bacfc259c3'
- ]
- },
- {
- id: '47ba5f70-96de-6fad-47d0-fd0f91f843ea',
- entityType: 'VIEW',
- elementId: '0f6665a0-47fe-6e8e-0f0c-c7df40d842c9',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1517,
- yMin: 380,
- width: 19,
- height: 26
- },
- polygons: [
- [[1526, 381], [1518, 388], [1518, 398], [1529, 405], [1535, 396],[1535, 390]]
- ]
- }
- },
- renderedOutputs: [
- '1df49927-6747-6719-1d9e-3b5860614b5e'
- ]
- },
- {
- id: 'd838da37-94f5-586a-d852-784893d3742d',
- entityType: 'VIEW',
- elementId: 'a6b41fba-df6f-2d6f-a6de-bdc5d8490128',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 839,
- yMin: 588,
- width: 468,
- height: 433
- },
- polygons: [
- [[928, 647], [889, 688], [870, 723], [861, 747], [861, 792], [870, 821], [872, 845], [909, 912], [924, 930], [946, 949], [985, 973], [1037, 993], [1074, 1001], [1120, 1001], [1122, 999], [1146, 997], [1194, 980], [1218, 960], [1241, 936], [1263, 908], [1278, 871], [1287, 825], [1287, 792], [1278, 775], [1270, 747], [1252, 721], [1244, 712], [1231, 688], [1189, 649], [1170, 636], [1148, 625], [1122, 616], [1087, 612], [1085, 610], [1037, 607], [1035, 610], [1022, 610], [989, 616],[952, 631]]
- ]
- }
- },
- renderedOutputs: [
- 'b69e0f13-edf3-0474-b6f4-ad6cead52833'
- ]
- },
- {
- id: 'f5adeae9-5e35-ec2d-f5c7-48965913c06a',
- entityType: 'VIEW',
- elementId: 'bf94b068-56d5-7a8a-bffe-121751f356cd',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1355,
- yMin: 345,
- width: 366,
- height: 596
- },
- polygons: [
- [[1685, 398], [1681, 398], [1666, 409], [1659, 409], [1635, 422], [1620, 422], [1616, 414], [1631, 398], [1637, 396], [1644, 390], [1631, 377], [1624, 379], [1609, 372], [1598, 372], [1576, 383], [1563, 394], [1559, 394], [1548, 405], [1561, 414], [1576, 416], [1589, 425], [1589, 431], [1583, 446], [1563, 457], [1542, 464], [1524, 475], [1526, 477], [1537, 477], [1559, 472], [1568, 475], [1576, 481], [1579, 490], [1574, 494], [1548, 483], [1535, 488], [1535, 496], [1555, 514], [1552, 520], [1546, 520], [1529, 503], [1518, 503], [1502, 509], [1465, 507], [1457, 509], [1452, 514], [1452, 522], [1479, 544], [1492, 549], [1502, 549], [1509, 557], [1505, 566], [1498, 570], [1474, 568], [1463, 573], [1457, 579], [1457, 583], [1463, 588], [1470, 586], [1500, 588], [1515, 592], [1539, 605], [1552, 601], [1563, 581], [1576, 601], [1574, 627], [1581, 642], [1589, 649], [1607, 653], [1633, 638], [1642, 638], [1650, 651], [1650, 660], [1635, 673], [1624, 675], [1622, 673], [1605, 673], [1583, 684], [1566, 697], [1555, 699], [1546, 697], [1529, 686], [1513, 690], [1507, 699], [1526, 718], [1526, 729], [1515, 745], [1515, 751], [1520, 758], [1552, 755], [1572, 768], [1583, 779], [1585, 786], [1583, 792], [1570, 801], [1561, 803], [1555, 810], [1537, 803], [1529, 803], [1524, 810], [1522, 821], [1522, 836], [1526, 845], [1524, 851], [1513, 860], [1457, 858], [1448, 862], [1435, 875], [1431, 884], [1405, 908], [1396, 912], [1387, 912], [1372, 906], [1372, 910], [1378, 914], [1394, 914], [1424, 906], [1448, 895], [1452, 890], [1476, 882], [1513, 860], [1561, 845], [1594, 827], [1609, 814], [1633, 786], [1637, 777], [1672, 745], [1689, 721], [1703, 692], [1703, 666], [1694, 644], [1694, 607], [1700, 597], [1700, 583], [1692, 560], [1692, 546], [1694, 544], [1692, 522], [1694, 520], [1694, 483], [1698, 466], [1703, 459], [1703, 446], [1705, 444], [1705, 420],[1694, 405]]
- ]
- }
- },
- renderedOutputs: [
- 'fc3a666a-8ea9-f60e-fc50-c415898fda49'
- ]
- },
- {
- id: '3179a7a6-8703-f8dc-3113-05d98025d49b',
- entityType: 'VIEW',
- elementId: 'c9a60dc5-6c0c-4ceb-c9cc-afba6b2a60ac',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 731,
- yMin: 797,
- width: 14,
- height: 29
- },
- polygons: [
- [[736, 819], [734, 820], [731, 820], [731, 825], [732, 826], [736, 826], [738, 824],[738, 821]
- ],
- [[731, 798], [732, 802], [737, 807], [741, 809], [745, 807], [745, 801], [741, 797],[732, 797]]
- ]
- }
- },
- renderedOutputs: [
- '9d797036-7e3b-de8f-9d13-d249791df2c8'
- ]
- },
- {
- id: '72d12f4f-8de3-2ed7-72bb-8d308ac50290',
- entityType: 'VIEW',
- elementId: 'ce9382ec-d9f3-17df-cef9-2093ded53b98',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 463,
- yMin: 102,
- width: 1232,
- height: 725
- },
- polygons: [
- [[519, 760], [717, 766], [761, 775], [776, 782], [791, 795], [789, 784], [793, 773], [793, 745], [806, 677], [815, 651], [826, 629], [843, 605], [876, 573], [900, 555], [930, 540], [974, 525], [1000, 522], [1015, 518], [1076, 518], [1091, 522], [1109, 522], [1128, 527], [1181, 544], [1237, 573], [1272, 601], [1285, 605], [1372, 536], [1398, 518], [1450, 494], [1494, 481], [1518, 477], [1542, 462], [1563, 455], [1581, 446], [1587, 431], [1587, 425], [1576, 418], [1561, 416], [1546, 405], [1561, 388], [1566, 388], [1598, 370], [1609, 370], [1624, 377], [1631, 374], [1626, 359], [1620, 353], [1618, 346], [1631, 342], [1633, 335], [1639, 331], [1629, 318], [1620, 314], [1594, 309], [1572, 300], [1561, 298], [1520, 300], [1494, 276], [1483, 272], [1459, 270], [1457, 274], [1446, 281], [1433, 281], [1426, 274], [1426, 270], [1437, 259], [1428, 253], [1415, 255], [1413, 266], [1392, 287], [1361, 305], [1355, 305], [1339, 311], [1322, 311], [1300, 316], [1272, 316], [1259, 311], [1248, 311], [1235, 296], [1235, 281], [1231, 274], [1222, 274], [1211, 281], [1207, 287], [1209, 294], [1207, 303], [1100, 303], [1061, 292], [1044, 292], [1020, 285], [998, 274], [965, 248], [948, 242], [785, 226], [765, 213], [709, 161], [667, 139], [643, 135], [639, 144], [659, 144], [680, 152], [713, 174], [730, 194], [743, 202], [765, 224], [778, 244], [787, 263], [787, 276], [774, 285], [743, 285], [715, 279], [702, 279], [669, 285], [643, 285], [641, 287], [600, 281], [598, 283], [585, 283], [578, 287], [578, 292], [582, 292], [595, 305], [600, 320], [595, 385], [593, 388], [591, 418], [580, 468], [578, 520], [574, 536], [572, 564], [561, 605], [561, 649], [556, 657], [545, 703], [535, 723],[528, 747]]
- ]
- }
- },
- renderedOutputs: [
- '23585679-dd88-b9fc-2332-f406daae95bb'
- ]
- },
- {
- id: '260594c3-2b62-74c0-266f-36bc2c445887',
- entityType: 'VIEW',
- elementId: 'fd98bc6d-7eb9-d8f4-fdf2-1e12799ff4b3',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1275,
- yMin: 278,
- width: 16,
- height: 19
- },
- polygons: [
- [[1283, 279], [1278, 283], [1276, 292], [1281, 296], [1287, 296], [1291, 285],[1289, 281]]
- ]
- }
- },
- renderedOutputs: [
- 'f958525f-3602-3369-f932-f02031241f2e'
- ]
- },
- {
- id: '858717fe-a458-fbb3-85ed-b581a37ed7f4',
- entityType: 'VIEW',
- elementId: '640b0825-60c5-887d-6461-aa5a67e3a43a',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1269,
- yMin: 453,
- width: 397,
- height: 478
- },
- polygons: [
- [[1568, 477], [1550, 475], [1537, 479], [1520, 477], [1450, 496], [1420, 512], [1415, 512], [1381, 531], [1287, 605], [1296, 623], [1318, 644], [1341, 679], [1355, 708], [1359, 712], [1370, 745], [1378, 760], [1378, 779], [1385, 801], [1385, 823], [1378, 843], [1378, 880], [1372, 899], [1374, 906], [1387, 910], [1396, 910], [1405, 906], [1428, 884], [1433, 875], [1448, 860], [1457, 856], [1492, 856], [1494, 858], [1513, 858], [1518, 856], [1524, 845], [1520, 836], [1520, 821], [1522, 810], [1529, 801], [1537, 801], [1555, 808], [1561, 801], [1581, 792], [1583, 786], [1581, 779], [1552, 758], [1520, 760], [1515, 755], [1513, 745], [1524, 729], [1524, 718], [1505, 699], [1513, 688], [1529, 684], [1546, 694], [1555, 697], [1566, 694], [1583, 681], [1605, 671], [1622, 671], [1624, 673], [1635, 671], [1648, 660], [1648, 651], [1642, 640], [1633, 640], [1607, 655], [1589, 651], [1581, 644], [1574, 636], [1572, 627], [1574, 601], [1563, 583], [1555, 601], [1539, 607], [1509, 592], [1487, 588], [1463, 590], [1455, 583], [1455, 579], [1468, 568], [1474, 566], [1498, 568], [1507, 560], [1502, 551], [1492, 551], [1479, 546], [1450, 522], [1450, 514], [1457, 507], [1465, 505], [1502, 507], [1518, 501], [1529, 501], [1537, 507], [1526, 496], [1515, 492], [1529, 483], [1542, 479], [1561, 479], [1570, 485], [1574, 485],[1574, 481]]
- ]
- }
- },
- renderedOutputs: [
- '707c5d25-0878-8683-7016-ff5a0f5eaac4'
- ]
- },
- {
- id: '461084a6-d7e8-4430-467a-26d9d0ce6877',
- entityType: 'VIEW',
- elementId: '56c74ad5-c077-1d08-56ad-e8aac751314f',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 0,
- yMin: 740,
- width: 847,
- height: 155
- },
- polygons: [
- [[0, 762], [0, 766], [6, 771], [21, 771], [23, 773], [50, 773], [52, 775], [87, 777], [102, 782], [174, 786], [176, 788], [204, 788], [219, 792], [271, 795], [274, 797], [302, 799], [334, 808], [337, 812], [350, 812], [367, 816], [465, 819], [467, 823], [548, 825], [574, 832], [589, 832], [611, 838], [632, 840], [641, 845], [661, 847], [667, 851], [687, 853], [743, 871], [750, 875], [756, 875], [789, 888], [804, 886], [806, 882], [806, 866], [791, 797], [776, 784], [761, 777], [717, 768], [558, 764], [556, 762], [498, 762], [495, 760], [437, 760], [435, 758], [378, 758], [376, 755], [317, 755], [315, 753], [256, 753], [254, 751], [195, 751], [193, 749], [134, 749], [132, 747], [54, 747], [52, 749], [30, 751], [34, 755], [26, 762],[10, 755]]
- ]
- }
- },
- renderedOutputs: [
- '123a4495-771b-8a5d-1250-e6ea703da61a'
- ]
- },
- {
- id: '8eab6f21-2c6b-59a7-8ec1-cd5e2b4d75e0',
- entityType: 'VIEW',
- elementId: '3ed12b36-252c-98ed-3ebb-8949220ab4aa',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 800,
- yMin: 529,
- width: 569,
- height: 522
- },
- polygons: [
- [[865, 638], [843, 677], [830, 714], [828, 766], [826, 768], [826, 827], [843, 871], [865, 906], [920, 967], [948, 984], [972, 1006], [985, 1012], [991, 1012], [1011, 1021], [1026, 1023], [1035, 1028], [1083, 1032], [1100, 1036], [1122, 1036], [1146, 1030], [1157, 1030], [1178, 1017], [1185, 1017], [1209, 1004], [1237, 999], [1254, 993], [1263, 982], [1287, 967], [1313, 936], [1322, 921], [1324, 897], [1344, 864], [1344, 851], [1339, 836], [1337, 775], [1328, 738], [1315, 705], [1315, 697], [1320, 686], [1318, 677], [1302, 671], [1283, 653], [1274, 634], [1257, 623], [1237, 605], [1183, 579], [1146, 568], [1137, 568], [1096, 553], [1020, 555], [1017, 557], [1000, 557], [998, 560], [976, 562], [924, 586],[896, 607]]
- ]
- }
- },
- renderedOutputs: [
- '5ed2f756-0753-6abc-5eb8-5529007546fb'
- ]
- },
- {
- id: '71dbdd16-1c49-864a-71b1-7f691b6faa0d',
- entityType: 'VIEW',
- elementId: 'f78b2228-70ce-da31-f7e1-805777e8f676',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1085,
- yMin: 213,
- width: 95,
- height: 47
- },
- polygons: [
- [[1174, 235], [1161, 229], [1150, 216], [1141, 216], [1111, 226], [1113, 235], [1100, 248], [1094, 250], [1089, 255], [1089, 259], [1109, 255], [1122, 255], [1124, 257], [1126, 255], [1146, 255], [1154, 259], [1167, 253],[1176, 244]]
- ]
- }
- },
- renderedOutputs: [
- 'c5b13d52-feef-1524-c5db-9f2df9c93963'
- ]
- },
- {
- id: '8074a493-4434-7b65-801e-06ec43125722',
- entityType: 'VIEW',
- elementId: '68d766cb-9bbf-c196-68bd-c4b49c99edd1',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 552,
- yMin: 125,
- width: 244,
- height: 172
- },
- polygons: [
- [[563, 281], [572, 290], [576, 290], [585, 281], [598, 281], [600, 279], [632, 285], [669, 283], [702, 276], [715, 276], [743, 283], [774, 283], [785, 276], [785, 263], [776, 244], [763, 224], [743, 205], [730, 196], [713, 176], [680, 155], [665, 148], [639, 146], [637, 144], [641, 135], [628, 133], [626, 155], [602, 183], [602, 187], [585, 220], [582, 233],[565, 263]]
- ]
- }
- },
- renderedOutputs: [
- '311115d4-789e-625e-317b-b7ab7fb84e19'
- ]
- },
- {
- id: '439bd668-c016-75e3-43f1-7417c73059a4',
- entityType: 'VIEW',
- elementId: '2d917d5d-6636-5ea7-2dfb-df22611072e0',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 0,
- yMin: 74,
- width: 657,
- height: 718
- },
- polygons: [
- [[626, 131], [606, 124], [569, 122], [567, 120], [539, 120], [508, 113], [471, 111], [456, 107], [363, 109], [334, 115], [317, 115], [287, 122], [271, 122], [269, 124], [197, 128], [171, 139], [158, 152], [132, 161], [115, 174], [82, 192], [52, 220], [23, 235], [8, 248], [2, 244], [0, 246], [0, 760], [8, 753], [19, 755], [26, 760], [32, 755], [28, 751], [30, 749], [52, 747], [54, 745], [132, 745], [134, 747], [193, 747], [195, 749], [254, 749], [256, 751], [267, 751], [269, 742], [282, 731], [313, 714], [328, 710], [337, 712], [341, 718], [343, 753], [495, 758], [504, 760], [532, 714], [541, 697], [548, 675], [550, 653], [558, 629], [561, 592], [567, 577], [567, 566], [569, 564], [572, 536], [576, 520], [578, 468], [589, 418], [591, 388], [593, 385], [593, 368], [598, 346], [595, 311], [593, 305], [582, 294], [572, 292], [563, 285], [561, 272], [576, 237], [580, 233], [582, 220], [600, 187], [600, 183],[624, 155]]
- ]
- }
- },
- renderedOutputs: [
- '8d051e26-285e-30e7-8d6f-bc592f781ca0'
- ]
- },
- {
- id: '66937b3f-dc7a-f700-66f9-d940db5cdb47',
- entityType: 'VIEW',
- elementId: '4d9cb158-d7ed-c73e-4df6-1327d0cbeb79',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 947,
- yMin: 417,
- width: 269,
- height: 36
- },
- polygons: [
- [[959, 425], [978, 432], [994, 432], [996, 433], [1013, 433], [1015, 435], [1115, 438], [1139, 447], [1145, 447], [1162, 452], [1186, 452], [1199, 448], [1204, 445], [1197, 438], [1191, 438], [1170, 432], [1162, 432], [1132, 425], [1108, 425], [1107, 423], [1065, 422], [1063, 420], [1045, 420], [1043, 418], [1006, 420], [1005, 422],[976, 422]]
- ]
- }
- },
- renderedOutputs: [
- '337b4153-f26e-59f9-3311-e32cf54875be'
- ]
- },
- {
- id: '058c0533-0a74-dcb3-05e6-a74c0d52f0f4',
- entityType: 'VIEW',
- elementId: 'fff6bd07-f76a-1597-ff9c-1f78f04c39d0',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1220,
- yMin: 154,
- width: 119,
- height: 259
- },
- polygons: [
- [[1227, 167], [1226, 170], [1231, 177], [1237, 202], [1259, 241], [1266, 262], [1284, 306], [1284, 311], [1293, 326], [1299, 333], [1306, 351], [1333, 401], [1335, 400], [1331, 385], [1333, 360], [1330, 355], [1331, 323], [1299, 274], [1293, 259], [1269, 227], [1249, 184],[1234, 165]]
- ]
- }
- },
- renderedOutputs: [
- '325ba3da-5e3e-40d0-3231-01a559186c97'
- ]
- },
- {
- id: '740607eb-7996-67f0-746c-a5947eb04bb7',
- entityType: 'VIEW',
- elementId: '299b478c-4091-0165-29f1-e5f347b72d22',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 441,
- yMin: 367,
- width: 971,
- height: 618
- },
- polygons: [
- [[524, 425], [522, 442], [520, 443], [520, 462], [517, 475], [503, 504], [497, 524], [495, 552], [490, 572], [490, 594], [485, 606], [485, 616], [503, 636], [510, 649], [519, 683], [519, 699], [520, 701], [522, 721], [530, 755], [547, 795], [549, 807], [560, 818], [567, 838], [577, 857], [602, 889], [622, 907], [653, 925], [679, 932], [691, 937], [711, 939], [726, 946], [743, 946], [755, 941], [782, 942], [795, 937], [810, 937], [832, 946], [850, 946], [870, 941], [892, 942], [894, 944], [912, 944], [936, 937], [968, 937], [969, 939], [984, 939], [1015, 949], [1040, 951], [1056, 957], [1075, 956], [1100, 944], [1134, 946], [1154, 937], [1169, 936], [1186, 924], [1204, 915], [1217, 912], [1253, 894], [1271, 874], [1293, 828], [1308, 812], [1310, 803], [1318, 788], [1323, 771], [1333, 755], [1343, 718], [1345, 699], [1350, 686], [1358, 648], [1360, 619], [1363, 609], [1365, 586], [1367, 584], [1368, 530], [1367, 529], [1363, 482], [1358, 468], [1358, 462], [1350, 432], [1336, 410], [1335, 417], [1326, 425], [1313, 432], [1256, 448], [1191, 455], [1189, 457], [1152, 460], [1150, 462], [1088, 463], [1087, 465], [1070, 465], [1068, 467], [986, 467], [984, 468], [902, 468], [901, 467], [867, 467], [865, 465], [827, 465], [825, 463], [778, 462], [777, 460], [725, 458], [723, 457], [708, 457], [706, 455], [693, 455], [691, 453], [678, 453], [676, 452], [644, 448], [631, 442], [626, 442], [614, 437], [574, 430], [554, 422], [549, 417], [545, 405], [549, 395], [542, 400],[537, 411]]
- ]
- }
- },
- renderedOutputs: [
- 'f85c1c27-7bec-9d6c-f836-be587ccab12b'
- ]
- },
- {
- id: 'be3948a9-777a-be37-be53-ead6705c9270',
- entityType: 'VIEW',
- elementId: '0f6665a0-47fe-6e8e-0f0c-c7df40d842c9',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 291,
- yMin: 312,
- width: 278,
- height: 524
- },
- polygons: [
- [[550, 336], [535, 351], [519, 378], [507, 391], [498, 406], [493, 411], [493, 415], [455, 473], [453, 480], [436, 512], [416, 534], [408, 539], [373, 572], [363, 584], [363, 592], [358, 606], [341, 616], [333, 632], [331, 641], [326, 648], [319, 666], [319, 674], [312, 689], [309, 708], [306, 715], [304, 763], [324, 783], [334, 788], [339, 788], [346, 781], [348, 763], [349, 761], [349, 746], [363, 701], [373, 679], [383, 668], [400, 663], [420, 668], [433, 674], [468, 710], [478, 726], [487, 755], [493, 768], [500, 802], [505, 812], [508, 813], [532, 813], [547, 807], [545, 795], [529, 755], [520, 721], [519, 701], [517, 699], [517, 683], [508, 649], [502, 636], [485, 619], [483, 606], [488, 594], [488, 572], [493, 552], [495, 524], [502, 504], [515, 475], [519, 462], [519, 443], [520, 442], [522, 425], [535, 411], [540, 400], [550, 391], [555, 380], [555, 356],[557, 351]]
- ]
- }
- },
- renderedOutputs: [
- '03a8e4a9-d417-c1fd-03c2-46d6d331edba'
- ]
- },
- {
- id: 'd64d13af-dd3c-cc9e-d627-b1d0da1ae0d9',
- entityType: 'VIEW',
- elementId: '3a4f083c-10fb-b483-3a25-aa4317dd98c4',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 508,
- yMin: 138,
- width: 866,
- height: 344
- },
- polygons: [
- [[547, 405], [550, 417], [564, 425], [587, 432], [599, 432], [614, 435], [626, 440], [631, 440], [644, 447], [663, 448], [658, 448], [641, 442], [627, 428], [601, 427], [599, 425], [601, 417], [624, 415], [638, 410], [644, 410], [646, 411], [691, 417], [721, 423], [733, 423], [738, 427], [740, 432], [751, 443], [748, 448], [741, 450], [666, 450], [723, 455], [725, 457], [777, 458], [778, 460], [825, 462], [827, 463], [901, 465], [902, 467], [1068, 465], [1070, 463], [1087, 463], [1088, 462], [1150, 460], [1152, 458], [1179, 457], [1204, 452], [1232, 450], [1234, 448], [1256, 447], [1305, 433], [1326, 423], [1333, 417], [1335, 411], [1335, 405], [1328, 396], [1315, 368], [1311, 365], [1298, 333], [1291, 326], [1283, 311], [1283, 306], [1264, 262], [1258, 241], [1236, 202], [1229, 177], [1224, 169], [1172, 159], [1124, 157], [1122, 155], [1080, 155], [1078, 154], [837, 154], [835, 155], [815, 155], [814, 157], [741, 157], [740, 159], [721, 159], [720, 160], [684, 160], [668, 164], [661, 169], [638, 216], [634, 219], [629, 232], [621, 244], [604, 283], [601, 286], [591, 309],[572, 343]]
- ]
- }
- },
- renderedOutputs: [
- '4c2bf631-5213-7c6c-4c41-544e5535502b'
- ]
- },
- {
- id: '6805436b-c50b-756f-686f-e114c22d5928',
- entityType: 'VIEW',
- elementId: 'bf94b068-56d5-7a8a-bffe-121751f356cd',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 266,
- yMin: 542,
- width: 1339,
- height: 537
- },
- polygons: [
- [[1563, 723], [1559, 715], [1551, 666], [1548, 654], [1537, 636], [1536, 627], [1516, 587], [1497, 567], [1489, 567], [1497, 594], [1502, 601], [1506, 612], [1526, 622], [1534, 636], [1531, 653], [1516, 669], [1514, 674], [1516, 676], [1524, 676], [1534, 681], [1554, 703], [1554, 716], [1549, 726], [1549, 735], [1553, 740], [1553, 761], [1548, 773], [1534, 785], [1522, 783], [1519, 778], [1506, 731], [1506, 723], [1501, 710], [1502, 721], [1506, 728], [1507, 751], [1509, 753], [1507, 785], [1506, 787], [1507, 808], [1506, 810], [1504, 832], [1497, 855], [1497, 864], [1492, 877], [1470, 909], [1454, 925], [1444, 932], [1422, 952], [1393, 967], [1375, 964], [1358, 956], [1345, 944], [1338, 930], [1336, 910], [1343, 892], [1345, 879], [1353, 848], [1351, 830], [1350, 825], [1323, 820], [1313, 817], [1310, 813], [1294, 828], [1273, 874], [1248, 899], [1217, 914], [1196, 920], [1169, 937], [1154, 939], [1134, 947], [1100, 946], [1075, 957], [1056, 959], [1040, 952], [1015, 951], [984, 941], [969, 941], [968, 939], [936, 939], [912, 946], [894, 946], [892, 944], [870, 942], [850, 947], [832, 947], [810, 939], [795, 939], [782, 944], [755, 942], [743, 947], [726, 947], [711, 941], [691, 939], [679, 934], [668, 932], [653, 927], [627, 912], [601, 889], [571, 848], [559, 818], [547, 808], [532, 815], [508, 815], [507, 813], [505, 837], [510, 859], [510, 887], [515, 907], [515, 925], [510, 939], [497, 954], [475, 969], [458, 974], [445, 969], [428, 957], [393, 922], [384, 907], [364, 880], [354, 853], [349, 827], [348, 776], [348, 781], [339, 790], [334, 790], [324, 785], [304, 765], [299, 780], [299, 792], [297, 793], [299, 875], [302, 887], [304, 919], [311, 946], [319, 962], [319, 971], [329, 996], [333, 1014], [338, 1024], [338, 1038], [349, 1078], [1512, 1078], [1517, 1059], [1526, 1041], [1531, 1014], [1536, 1004], [1537, 994], [1544, 982], [1544, 977], [1549, 967], [1551, 954], [1558, 932], [1559, 915], [1563, 910], [1566, 813], [1569, 797], [1569, 781], [1568, 780], [1566, 751],[1563, 738]]
- ]
- }
- },
- renderedOutputs: [
- '2a5bdb7c-a086-b288-2a31-7903a7a09ecf'
- ]
- },
- {
- id: '65beb0eb-d1ea-7d19-65d4-1294d6cc515e',
- entityType: 'VIEW',
- elementId: '6d6b4bcd-e710-686c-6d01-e9b2e036442b',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 341,
- yMin: 649,
- width: 180,
- height: 338
- },
- polygons: [
- [[390, 666], [378, 674], [368, 693], [351, 746], [349, 805], [351, 807], [351, 827], [356, 853], [366, 880], [386, 907], [395, 922], [428, 956], [452, 971], [462, 972], [475, 967], [497, 952], [508, 939], [514, 925], [514, 907], [508, 887], [508, 859], [503, 837], [505, 813], [498, 802], [492, 768], [485, 755], [477, 726], [462, 703], [433, 676], [410, 666], [401, 666],[400, 664]]
- ]
- }
- },
- renderedOutputs: [
- '8e49cf30-378e-1cf1-8e23-6d4f30a830b6'
- ]
- },
- {
- id: '5084700a-043a-6074-50ee-d275031c4c33',
- entityType: 'VIEW',
- elementId: 'a2bd7491-a911-ffdb-a2d7-d6eeae37d39c',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1297,
- yMin: 299,
- width: 267,
- height: 548
- },
- polygons: [
- [[1333, 324], [1331, 355], [1335, 360], [1335, 375], [1333, 376], [1335, 396], [1336, 398], [1335, 403], [1351, 432], [1367, 492], [1367, 512], [1368, 514], [1368, 529], [1370, 530], [1368, 584], [1367, 586], [1365, 609], [1362, 619], [1360, 648], [1351, 686], [1346, 699], [1345, 718], [1335, 755], [1325, 771], [1320, 788], [1311, 803], [1310, 812], [1313, 815], [1323, 818], [1350, 823], [1355, 815], [1368, 770], [1393, 720], [1408, 703], [1442, 679], [1462, 671], [1475, 671], [1482, 674], [1492, 684], [1499, 701], [1499, 706], [1506, 716], [1507, 731], [1521, 778], [1526, 783], [1534, 783], [1546, 773], [1551, 761], [1551, 740], [1548, 735], [1548, 726], [1553, 716], [1553, 703], [1534, 683], [1524, 678], [1516, 678], [1512, 674], [1514, 669], [1529, 653], [1532, 641], [1531, 631], [1526, 624], [1507, 616], [1504, 612], [1501, 601], [1496, 594], [1487, 567], [1496, 566], [1482, 552], [1454, 530], [1437, 504], [1392, 415], [1363, 375], [1355, 358],[1343, 343]]
- ]
- }
- },
- renderedOutputs: [
- 'ce9f0c5d-2b3e-d5b1-cef5-ae222c18f9f6'
- ]
- },
- {
- id: '0383f405-66a7-0af0-03e9-567a618126b7',
- entityType: 'VIEW',
- elementId: '17fd8970-6229-d165-1797-2b0f650ffd22',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1267,
- yMin: 1019,
- width: 47,
- height: 36
- },
- polygons: [
- [[1269, 1031], [1269, 1036], [1278, 1044], [1289, 1048], [1301, 1054], [1306, 1054], [1310, 1051], [1313, 1038], [1310, 1029], [1305, 1024], [1289, 1021],[1276, 1024]]
- ]
- }
- },
- renderedOutputs: [
- '02d8800f-7866-58aa-02b2-22707f4074ed'
- ]
- },
- {
- id: '29ed50ba-aa7d-351a-2987-f2c5ad5b195d',
- entityType: 'VIEW',
- elementId: '833efbf2-dda5-8c31-8354-598dda83a076',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 632,
- yMin: 49,
- width: 628,
- height: 123
- },
- polygons: [
- [[661, 155], [661, 162], [666, 164], [684, 159], [720, 159], [721, 157], [740, 157], [741, 155], [814, 155], [815, 154], [835, 154], [837, 152], [1078, 152], [1080, 154], [1122, 154], [1124, 155], [1172, 157], [1174, 159], [1204, 162], [1219, 167], [1226, 167], [1232, 164], [1229, 150], [1224, 140], [1204, 112], [1187, 95], [1167, 82], [1135, 68], [1119, 65], [1105, 65], [1103, 63], [1068, 62], [1067, 60], [1036, 60], [1035, 58], [1011, 58], [1010, 57], [922, 57], [921, 55], [849, 57], [847, 58], [835, 58], [822, 62], [807, 62], [772, 68], [762, 68], [731, 77], [718, 83], [701, 98],[669, 139]]
- ]
- }
- },
- renderedOutputs: [
- '79c36708-b835-5fdb-79a9-c577bf13739c'
- ]
- },
- {
- id: 'e7a16ffb-a22c-8705-e7cb-cd84a50aab42',
- entityType: 'VIEW',
- elementId: '4c70355e-6197-3c0b-4c1a-972166b1104c',
- imageRegion: {
- specification: {
- boundingBox: {
- xMin: 1330,
- yMin: 658,
- width: 186,
- height: 322
- },
- polygons: [
- [[1475, 673], [1455, 674], [1442, 681], [1408, 704], [1400, 713], [1390, 728], [1367, 778], [1358, 810], [1351, 823], [1355, 848], [1346, 879], [1345, 892], [1338, 910], [1338, 922], [1340, 930], [1346, 944], [1358, 954], [1375, 962], [1393, 966], [1422, 951], [1469, 909], [1489, 880], [1494, 869], [1497, 847], [1504, 823], [1504, 810], [1506, 808], [1504, 787], [1506, 785], [1507, 753], [1506, 751], [1504, 728], [1501, 721], [1496, 696], [1491, 684],[1482, 676]]
- ]
- }
- },
- renderedOutputs: [
- '662bc970-70d1-83de-6641-6b0f77f7af99'
- ]
- }
- ]
-}
diff --git a/packages/public/network/test/api/requests/inspections/mappers.test.ts b/packages/public/network/test/api/requests/inspections/mappers.test.ts
deleted file mode 100644
index 786bbdab7..000000000
--- a/packages/public/network/test/api/requests/inspections/mappers.test.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import getInspection from './getInspection.testdata.json';
-import getInspectionParsed from './getInspectionParsed.testdata';
-import { mapGetInspectionResponse } from '../../../../src/api/requests/inspections/mappers';
-import { ApiInspectionGet } from '../../../../src/api/apiModels';
-
-describe('GetInspection Mapper', () => {
- it('should properly map the getInspection object', () => {
- const result = mapGetInspectionResponse(getInspection as unknown as ApiInspectionGet);
- expect(result).toEqual(getInspectionParsed);
- });
-});
diff --git a/packages/public/network/test/api/requests/inspections/requests.test.ts b/packages/public/network/test/api/requests/inspections/requests.test.ts
deleted file mode 100644
index 634811d1e..000000000
--- a/packages/public/network/test/api/requests/inspections/requests.test.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import ky, { ResponsePromise } from 'ky';
-import { mapGetInspectionResponse } from '../../../../src/api/requests/inspections/mappers';
-import { getKyConfig, MonkAPIConfig } from '../../../../src/api/config';
-import { getInspection } from '../../../../src/api/requests/inspections';
-
-jest.mock('../../../../src/api/config', () => ({
- getKyConfig: jest.fn(() => ({ baseUrl: 'test', headers: { test: 'hello' } })),
-}));
-jest.mock('../../../../src/api/requests/inspections/mappers', () => ({
- mapGetInspectionResponse: jest.fn(() => ({ inspections: [{ id: 'test' }] })),
-}));
-
-describe('Inspection requests', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- describe('getInspection function', () => {
- it('should pass the proper params to the ky request', async () => {
- const id = 'inspection-id-test';
- const config: MonkAPIConfig = { apiDomain: 'test', authToken: 'wow' };
- await getInspection(id, config);
-
- expect(getKyConfig).toHaveBeenCalledWith(config);
- expect(ky.get).toHaveBeenCalledWith(
- `${getKyConfig({} as MonkAPIConfig).baseUrl}/inspections/${id}`,
- { headers: getKyConfig(config).headers },
- );
- });
-
- it('should return the ky response', async () => {
- const status = 404;
- jest.spyOn(ky, 'get').mockImplementationOnce(
- () =>
- Promise.resolve({
- status,
- json: () => Promise.resolve({}),
- }) as ResponsePromise,
- );
- const result = await getInspection('test', {} as MonkAPIConfig);
- expect(result.response).toEqual(expect.objectContaining({ status }));
- });
-
- it('should return the response body', async () => {
- const body = { hello: 'world' };
- jest.spyOn(ky, 'get').mockImplementationOnce(
- () =>
- Promise.resolve({
- json: () => Promise.resolve(body),
- }) as ResponsePromise,
- );
- const result = await getInspection('test', {} as MonkAPIConfig);
- expect(result.body).toEqual(body);
- });
-
- it('should return the mapped entities', async () => {
- const result = await getInspection('test', {} as MonkAPIConfig);
- expect(result.payload).toEqual({ entities: (mapGetInspectionResponse as jest.Mock)() });
- });
- });
-});
diff --git a/packages/public/network/test/api/task/requests.test.ts b/packages/public/network/test/api/task/requests.test.ts
new file mode 100644
index 000000000..bfd87ed2a
--- /dev/null
+++ b/packages/public/network/test/api/task/requests.test.ts
@@ -0,0 +1,84 @@
+jest.mock('../../../src/api/config', () => ({
+ getDefaultOptions: jest.fn(() => ({ prefixUrl: 'getDefaultOptionsTest' })),
+}));
+jest.mock('../../../src/api/inspection/mappers', () => ({
+ mapApiInspectionGet: jest.fn(() => ({ test: 'hello' })),
+}));
+
+import ky from 'ky';
+import { ProgressStatus, TaskName } from '@monkvision/types';
+import { MonkActionType } from '@monkvision/common';
+import { getDefaultOptions } from '../../../src/api/config';
+import { startInspectionTasks, updateTaskStatus } from '../../../src/api/task';
+
+const apiConfig = { apiDomain: 'apiDomain', authToken: 'authToken' };
+
+describe('Task requests', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('updateTaskStatus request', () => {
+ it('should make the proper API call and map the resulting response', async () => {
+ const inspectionId = 'test-inspection-id';
+ const name = TaskName.WHEEL_ANALYSIS;
+ const status = ProgressStatus.TODO;
+ const result = await updateTaskStatus(inspectionId, name, status, apiConfig);
+ const response = await (ky.patch as jest.Mock).mock.results[0].value;
+ const body = await response.json();
+
+ expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig);
+ expect(ky.patch).toHaveBeenCalledWith(`inspections/${inspectionId}/tasks/${name}`, {
+ ...getDefaultOptions(apiConfig),
+ json: { status },
+ retry: {
+ methods: ['patch'],
+ limit: 4,
+ backoffLimit: 1500,
+ },
+ });
+ expect(result).toEqual({
+ action: {
+ type: MonkActionType.UPDATED_MANY_TASKS,
+ payload: [{ id: undefined, status }],
+ },
+ response,
+ body,
+ });
+ });
+ });
+
+ describe('startInspectionTasks request', () => {
+ it('should make the proper API calls', async () => {
+ const inspectionId = 'test-inspection-id';
+ const names = [TaskName.WHEEL_ANALYSIS, TaskName.DAMAGE_DETECTION];
+ const result = await startInspectionTasks(inspectionId, names, apiConfig);
+ const response = await (ky.patch as jest.Mock).mock.results[0].value;
+ const body = await response.json();
+
+ expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig);
+ names.forEach((name) => {
+ expect(ky.patch).toHaveBeenCalledWith(`inspections/${inspectionId}/tasks/${name}`, {
+ ...getDefaultOptions(apiConfig),
+ json: { status: ProgressStatus.TODO },
+ retry: {
+ methods: ['patch'],
+ limit: 4,
+ backoffLimit: 1500,
+ },
+ });
+ });
+ expect(result).toEqual({
+ action: {
+ type: MonkActionType.UPDATED_MANY_TASKS,
+ payload: [
+ { id: undefined, status: ProgressStatus.TODO },
+ { id: undefined, status: ProgressStatus.TODO },
+ ],
+ },
+ response,
+ body,
+ });
+ });
+ });
+});
diff --git a/packages/public/network/test/auth/token.test.ts b/packages/public/network/test/auth/token.test.ts
index d48f1d244..6b561143e 100644
--- a/packages/public/network/test/auth/token.test.ts
+++ b/packages/public/network/test/auth/token.test.ts
@@ -1,10 +1,10 @@
-import { jwtDecode } from 'jwt-decode';
-import { decodeMonkJwt } from '../../src';
-
jest.mock('jwt-decode', () => ({
jwtDecode: jest.fn(),
}));
+import { jwtDecode } from 'jwt-decode';
+import { decodeMonkJwt } from '../../src';
+
describe('Network package JWT utils', () => {
afterEach(() => {
jest.clearAllMocks();
diff --git a/packages/public/sentry/README.md b/packages/public/sentry/README.md
index 3217561b5..3d8406160 100644
--- a/packages/public/sentry/README.md
+++ b/packages/public/sentry/README.md
@@ -53,7 +53,6 @@ A Sentry Adapter logs everything in the Sentry platform. This can be used in you
```tsx
import { SentryMonitoringAdapter } from '@monkvision/sentry';
import { MonitoringProvider } from '@monkvision/monitoring';
-const adapter = new DebugMonitoringAdapter();
const adapter = new SentryMonitoringAdapter({
dsn: '',
diff --git a/packages/public/sentry/src/adapter.ts b/packages/public/sentry/src/adapter.ts
index 0b972da40..7a4b2b338 100644
--- a/packages/public/sentry/src/adapter.ts
+++ b/packages/public/sentry/src/adapter.ts
@@ -110,7 +110,7 @@ export class SentryMonitoringAdapter extends DebugMonitoringAdapter implements M
Sentry.captureMessage(msg, context);
}
- override handleError(err: Error | string, context?: Omit): void {
+ override handleError(err: unknown, context?: Omit): void {
super.handleError(err, context);
Sentry.captureException(err, context);
}
diff --git a/packages/public/sights/README.md b/packages/public/sights/README.md
index e84d2079f..24cda702c 100644
--- a/packages/public/sights/README.md
+++ b/packages/public/sights/README.md
@@ -4,7 +4,8 @@ this package, please refer to [this page](CONTRIBUTING.md).
This package exposes the Monkvision sights info. The exported data contains sights label translations, vehicle type
info, sight overlays (SVG) as well as miscellaneous sight details. This package is required if you are planning on using
-the standard Monkvision Capture workflow, of simply if you ar planning on using sights at all.
+the standard Monkvision Capture workflow, of simply if you ar planning on using sights at all. The complete list of
+available sights in the SDK can be obtained in the official Monkvision documentation.
# Installing
To install the package, you can run the following command :
@@ -17,6 +18,35 @@ If you are using TypeScript, this package comes with its type definitions integr
anything else!
# Usage
+## Sights
+Once the package is installed, every sight available in this package can be obtained using its ID in the following way :
+
+```typescript
+import { sights } from '@monkvision/sights';
+
+console.log(sights['haccord-8YjMcu0D']);
+```
+
+## Sight Labels
+Each sight has a custom label, available in all languages supported by the Monk SDK. The labels can be obtained in the
+following way :
+
+```typescript
+import { sights, labels } from '@monkvision/sights';
+
+const sight = sights['haccord-8YjMcu0D'];
+console.log(labels[sight.label].fr);
+```
+
+## Vehicle Details
+Each sight is associated with a specific vehicle. The details for each vehicle can be obtained in the following way :
+
+```typescript
+import { sights, vehicles } from '@monkvision/sights';
+
+const sight = sights['haccord-8YjMcu0D'];
+console.log(vehicles[sight.vehicle]);
+```
# Contributing
Please refer to [this page](CONTRIBUTING.md) for more documentation if you are a developer contributing to this project.
diff --git a/packages/public/types/src/state/image.ts b/packages/public/types/src/state/image.ts
index c142f1489..bf71c0532 100644
--- a/packages/public/types/src/state/image.ts
+++ b/packages/public/types/src/state/image.ts
@@ -3,6 +3,22 @@ import { MonkEntity, MonkEntityType } from './entity';
import { VehiclePart } from './part';
import { TranslationObject } from '../i18n';
+/**
+ * Additional data that can be added to an image when it has been uploaded.
+ */
+export interface ImageAdditionalData extends AdditionalData {
+ /**
+ * The ID of the sight of the image. This value is present only if the picture is a beautyshot picture and if it was
+ * taken using the PhotoCapture component of the MonkJs SDK.
+ */
+ sight_id?: string;
+ /**
+ * The label of the image. This value is present only if the picture is a beautyshot picture and if it was taken using
+ * the PhotoCapture component of the MonkJs SDK.
+ */
+ label?: TranslationObject;
+}
+
/**
* The type of image.
*/
@@ -193,6 +209,11 @@ export interface Image extends MonkEntity {
* The type of the image.
*/
type: ImageType;
+ /**
+ * This key can be set when coupling two pictures together for the 2-shot damage detection. This key must be unique
+ * for each pair of image accross the whole inspection.
+ */
+ siblingKey?: string;
/**
* The labels (one for each language) of this image.
*/
@@ -225,5 +246,5 @@ export interface Image extends MonkEntity {
/**
* Additional data added during the upload of the image.
*/
- additionalData?: AdditionalData;
+ additionalData?: ImageAdditionalData;
}
diff --git a/packages/public/types/src/typeUtils.ts b/packages/public/types/src/typeUtils.ts
index 64cfbf859..6cdc2c524 100644
--- a/packages/public/types/src/typeUtils.ts
+++ b/packages/public/types/src/typeUtils.ts
@@ -24,6 +24,19 @@ export type RequiredProperties = T & Required>;
*/
export type PartialProperties = Partial> & Omit;
+/**
+ * Utility type that returns a union type of every required key in T. If T does not have any required key, this type
+ * resolves to `never`.
+ */
+export type RequiredKeys = {
+ [K in keyof T]-?: NonNullable extends Pick ? never : K;
+}[keyof T];
+
+/**
+ * Utility type that either allow T or no property of T.
+ */
+export type AllOrNone = T | { [K in keyof T]?: never };
+
/**
* Standard enum type that can be used to enforce enum-like types in generic parameters.
*/
diff --git a/packages/public/types/src/utils.ts b/packages/public/types/src/utils.ts
index 71ff91474..36fc12629 100644
--- a/packages/public/types/src/utils.ts
+++ b/packages/public/types/src/utils.ts
@@ -11,3 +11,9 @@ export interface PixelDimensions {
*/
height: number;
}
+
+export interface PromiseHandlers {
+ onResolve: (res: T) => void;
+ onReject: (err: any) => void;
+ onComplete: () => void;
+}
diff --git a/yarn.lock b/yarn.lock
index 55991d2bd..34ac4b5d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3631,7 +3631,9 @@ __metadata:
"@monkvision/eslint-config-typescript": 4.0.0
"@monkvision/eslint-config-typescript-react": 4.0.0
"@monkvision/jest-config": 4.0.0
+ "@monkvision/network": 4.0.0
"@monkvision/prettier-config": 4.0.0
+ "@monkvision/sights": 4.0.0
"@monkvision/test-utils": 4.0.0
"@monkvision/typescript-config": 4.0.0
"@testing-library/react": ^12.1.5
@@ -3721,15 +3723,17 @@ __metadata:
languageName: unknown
linkType: soft
-"@monkvision/network@workspace:packages/public/network":
+"@monkvision/network@4.0.0, @monkvision/network@workspace:packages/public/network":
version: 0.0.0-use.local
resolution: "@monkvision/network@workspace:packages/public/network"
dependencies:
+ "@monkvision/camera-web": 4.0.0
"@monkvision/common": 4.0.0
"@monkvision/eslint-config-base": 4.0.0
"@monkvision/eslint-config-typescript": 4.0.0
"@monkvision/jest-config": 4.0.0
"@monkvision/prettier-config": 4.0.0
+ "@monkvision/sights": 4.0.0
"@monkvision/types": 4.0.0
"@monkvision/typescript-config": 4.0.0
"@types/jest": ^29.2.2