Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Add latest national ID card version in OCR process #2353

Merged
merged 18 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion packages/cozy-mespapiers-lib/docs/papersDefinitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,32 @@
- [`[information]`](#step-information) {object} Step to get more informations about this file.
- [`contact`](#step-contact) {object} Step to select one or more contacts linked to this file.
- [`note`](#step-note) {object} Step to add / edit a note.
- `[ocrAttributes]`: {object} Contains all the parameters necessary for the OCR process.
- `[ocrAttributes]`: {object\[]} Contains all the parameters necessary for the OCR process.

***

## The `ocrAttributes` property:

This is an array containing information for OCR attributes detection. For instance, recognize a passport number,
an expiration date, etc.

Each version of a paper is an element in the `ocrAttributes` attribute. When there are several versions, each entry must have a `version` attribute.

Some papers have 2 sides: front and back. Each side is a dedicated object in the OCR definition.
The `front` and `back` entries have exactly the same structure and attributes.

- `version`: {string} Version of the paper (e.g. "2020.01").
- `ìllustration`: {string} Illustration of the choice of format displayed to the user.
- `versionLabel`: {string} Translation key for the name of the format displayed to the user.
- `label`: {string} The discriminating label.
- `side`: {string} Side where the label is.
- `front|back`: {object} All the front panel parameters allowing you to find the information to use.
- `referenceRules`: {object\[]} Rules that allow you to define the version of a paper.
- `regex`: {string} Regex that will be tested to deduce the paper version. (e.g. "^\[A-Z]")
- `[flag]`: {string} Regex flag (e.g. "/gi")
- `attributesRegex`: {Array<object>} Used to find attributes based on regex rules
- `attributesBoxes`: {Array<object>} Used to find attributes based on bouding box coordinates

## Steps of the `acquisitionSteps` property:

- ### Step `scan`:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import IlluInvoice from '../../assets/images/IlluInvoice.png'
import IlluNationalHealthInsuranceCardDateHelp from '../../assets/images/IlluNationalHealthInsuranceCardDateHelp.png'
import IlluNationalHealthInsuranceCardFront from '../../assets/images/IlluNationalHealthInsuranceCardFront.png'
import IlluNationalHealthInsuranceCardNumber from '../../assets/images/IlluNationalHealthInsuranceCardNumber.png'
import IlluNewNationalIdCardFront from '../../assets/images/IlluNewNationalIdCardFront.png'
import IlluOldNationalIdCardFront from '../../assets/images/IlluOldNationalIdCardFront.png'
import IlluPassport from '../../assets/images/IlluPassport.png'
import IlluPassportDate from '../../assets/images/IlluPassportDate.png'
import IlluPassportNumber from '../../assets/images/IlluPassportNumber.png'
Expand Down Expand Up @@ -56,6 +58,8 @@ const images = {
IlluNationalHealthInsuranceCardDateHelp,
IlluNationalHealthInsuranceCardFront,
IlluNationalHealthInsuranceCardNumber,
IlluNewNationalIdCardFront,
IlluOldNationalIdCardFront,
IlluResidencePermitBack,
IlluResidencePermitExpirationDateHelp,
IlluResidencePermitFront,
Expand All @@ -76,6 +80,9 @@ const images = {

const useStyles = makeStyles(() => ({
image: {
'&--xsmall': {
height: '3rem'
},
'&--small': {
height: '4rem'
},
Expand Down Expand Up @@ -104,7 +111,7 @@ const isSupportedBitmapExtension = filename => {
* @param {Object} props
* @param {string} props.icon - Icon name (with extension)
* @param {object} props.fallbackIcon - SVG filetype
* @param {string} props.iconSize - Icon size (small, medium, large(default))
* @param {string} props.iconSize - Icon size (xsmall, small, medium, large(default))
* @returns {ReactElement|null}
* @example
* <CompositeHeaderImage icon="icon.svg" fallbackIcon="fallback.svg" iconSize="small" />
Expand Down Expand Up @@ -160,7 +167,7 @@ const CompositeHeaderImage = ({ icon, fallbackIcon, iconSize = 'large' }) => {
CompositeHeaderImage.propTypes = {
icon: iconPropType,
fallbackIcon: PropTypes.object,
iconSize: PropTypes.oneOf(['small', 'medium', 'large'])
iconSize: PropTypes.oneOf(['xsmall', 'small', 'medium', 'large'])
}

export default CompositeHeaderImage
Original file line number Diff line number Diff line change
@@ -1,13 +1,73 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, { useEffect, useState } from 'react'

import { useWebviewIntent } from 'cozy-intent'
import { Dialog } from 'cozy-ui/transpiled/react/CozyDialogs'
import Empty from 'cozy-ui/transpiled/react/Empty'
import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'

import SelectPaperVersion from './SelectPaperVersion'
import OcrProcessingIcon from '../../../assets/icons/OcrProcessing.svg'
import { useFormData } from '../../Hooks/useFormData'
import { useStepperDialog } from '../../Hooks/useStepperDialog'
import {
getAttributesFromOcr,
getFormDataFilesForOcr,
getOcrFromFlagship,
makeMetadataFromOcr
} from '../helpers'

const OcrProcessingDialog = ({ onBack }) => {
const OcrProcessingDialog = ({ onBack, rotatedFile }) => {
const { t } = useI18n()
const { setFormData, formData } = useFormData()
const webviewIntent = useWebviewIntent()
const { currentDefinition, nextStep } = useStepperDialog()
const [multipleVersion, setMultipleVersion] = useState({
enabled: false,
ocr: null
})
const { ocrAttributes } = currentDefinition

useEffect(() => {
const init = async () => {
const fileSides = getFormDataFilesForOcr(formData, rotatedFile)
const ocrFromFlagship = await getOcrFromFlagship(fileSides, webviewIntent)
const fileVersions = ocrAttributes.map(attr => attr.version)

// If paper has multiple versions, we need to display a dialog to let user confirm the right version
if (fileVersions.length > 1) {
setMultipleVersion({ enabled: true, ocr: ocrFromFlagship })
} else {
// If paper has no multiple versions, we can go to next step
const attributesFound = getAttributesFromOcr(
ocrFromFlagship,
ocrAttributes[0]
)

const metadataFromOcr = makeMetadataFromOcr(attributesFound)
setFormData(prev => ({
...prev,
metadata: {
...prev.metadata,
...metadataFromOcr
}
}))

nextStep()
}
}
init()
// eslint-disable-next-line react-hooks/exhaustive-deps
Merkur39 marked this conversation as resolved.
Show resolved Hide resolved
}, [])

if (multipleVersion.enabled) {
return (
<SelectPaperVersion
onBack={onBack}
ocrFromFlagship={multipleVersion.ocr}
/>
)
}

return (
<Dialog
Expand All @@ -27,4 +87,9 @@ const OcrProcessingDialog = ({ onBack }) => {
)
}

OcrProcessingDialog.propTypes = {
onBack: PropTypes.func.isRequired,
rotatedFile: PropTypes.object
}

export default OcrProcessingDialog
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,9 @@ import { FLAGSHIP_SCAN_TEMP_FILENAME, KEYS } from '../../../constants/const'
import { isFlagshipOCRAvailable } from '../../../helpers/isFlagshipOCRAvailable'
import { isSomePaperStepsCompliantWithOCR } from '../../../helpers/isSomePaperStepsCompliantWithOCR'
import CompositeHeaderImage from '../../CompositeHeader/CompositeHeaderImage'
import { useFormData } from '../../Hooks/useFormData'
import { useStepperDialog } from '../../Hooks/useStepperDialog'
import StepperDialogTitle from '../../StepperDialog/StepperDialogTitle'
import {
getAttributesFromOcr,
makeFileFromBase64,
makeMetadataFromOcr
} from '../helpers'
import { makeFileFromBase64 } from '../helpers'

const ScanResultDialog = ({
currentStep,
Expand All @@ -36,21 +31,24 @@ const ScanResultDialog = ({
}) => {
const { illustration, multipage, page = 'default', tooltip } = currentStep
const { t } = useI18n()
const { currentStepIndex } = useStepperDialog()
const { setFormData, formData } = useFormData()
const webviewIntent = useWebviewIntent()
const [searchParams] = useSearchParams()

const imageRef = useRef(null)
const [rotationImage, setRotationImage] = useState(0)
const [ocrProcessing, setOcrProcessing] = useState(false)
const { nextStep, isLastStep, allCurrentSteps, currentDefinition } =
useStepperDialog()
const {
currentStepIndex,
nextStep,
isLastStep,
allCurrentSteps,
currentDefinition
} = useStepperDialog()

const fromFlagshipUpload = searchParams.get('fromFlagshipUpload')
let currentFileRotated

const onValid = async addPage => {
let currentFileRotated
// If the image has changed rotation, the process has changed it to base64, so we need to transform it back into File before saving it in the formData
if (rotationImage % 360 !== 0) {
currentFileRotated = makeFileFromBase64({
Expand All @@ -75,25 +73,10 @@ const ScanResultDialog = ({
const OcrActivated = await isFlagshipOCRAvailable(webviewIntent)
if (OcrActivated) {
setOcrProcessing(true)
const attributesFound = await getAttributesFromOcr({
formData,
ocrAttributes: currentDefinition.ocrAttributes,
currentFile,
currentFileRotated,
webviewIntent
})
const metadataFromOcr = makeMetadataFromOcr(attributesFound)

setFormData(prev => ({
...prev,
metadata: {
...prev.metadata,
...metadataFromOcr
}
}))
}
} else {
nextStep()
}
nextStep()
}
}

Expand All @@ -107,7 +90,9 @@ const ScanResultDialog = ({
useEventListener(window, 'keydown', handleKeyDown)

if (ocrProcessing) {
return <OcrProcessingDialog onBack={onBack} />
return (
<OcrProcessingDialog rotatedFile={currentFileRotated} onBack={onBack} />
)
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ describe('AcquisitionResult component:', () => {
currentFile: mockFile({ name: 'test.pdf' }),
mockIsFlagshipOCRAvailable: true,
isLastStep: jest.fn(() => true),
currentDefinition: { ocrAttributes: {} },
currentDefinition: { ocrAttributes: [] },
allCurrentSteps: [{ isDisplayed: 'ocr' }]
})
const btn = getByTestId('next-button')
Expand All @@ -228,7 +228,7 @@ describe('AcquisitionResult component:', () => {
mockIsFlagshipOCRAvailable: true,
isLastStep: jest.fn(() => true),
mockGetAttributesFromOcr,
currentDefinition: { ocrAttributes: {} },
currentDefinition: { ocrAttributes: [] },
allCurrentSteps: [{ isDisplayed: 'ocr' }]
})
const btn = getByTestId('next-button')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import PropTypes from 'prop-types'
import React, { useState } from 'react'

import Box from 'cozy-ui/transpiled/react/Box'
import Button from 'cozy-ui/transpiled/react/Buttons'
import { Dialog } from 'cozy-ui/transpiled/react/CozyDialogs'
import List from 'cozy-ui/transpiled/react/List'
import ListItem from 'cozy-ui/transpiled/react/ListItem'
import ListItemSecondaryAction from 'cozy-ui/transpiled/react/ListItemSecondaryAction'
import ListItemText from 'cozy-ui/transpiled/react/ListItemText'
import Radio from 'cozy-ui/transpiled/react/Radios'
import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'

import { findPaperVersion } from '../../../helpers/findAttributes'
import { makeReferenceRulesByOcrAttributes } from '../../../helpers/makeReferenceRulesByOcrAttributes'
import CompositeHeader from '../../CompositeHeader/CompositeHeader'
import CompositeHeaderImage from '../../CompositeHeader/CompositeHeaderImage'
import { useFormData } from '../../Hooks/useFormData'
import { useStepperDialog } from '../../Hooks/useStepperDialog'
import { getAttributesFromOcr, makeMetadataFromOcr } from '../helpers'

const getDefaultSelectedVersion = ({ ocrFromFlagship, ocrAttributes }) => {
const allReferenceRules = ocrAttributes.map(attrs => ({
version: attrs.version,
referenceRules: makeReferenceRulesByOcrAttributes(attrs)
}))

const { version } = findPaperVersion(ocrFromFlagship, allReferenceRules)
return version
}

const SelectPaperVersion = ({ onBack, ocrFromFlagship }) => {
const { t } = useI18n()
const { setFormData } = useFormData()
const { currentDefinition, nextStep } = useStepperDialog()
const [selectedVersion, setSelectedVersion] = useState(() =>
getDefaultSelectedVersion({
ocrFromFlagship,
ocrAttributes: currentDefinition.ocrAttributes
})
)
const { ocrAttributes } = currentDefinition

const handleClick = () => {
const attributesSelected = ocrAttributes.find(
attr => attr.version === selectedVersion
)
const attributesFound = getAttributesFromOcr(
ocrFromFlagship,
attributesSelected
)

const metadataFromOcr = makeMetadataFromOcr(attributesFound)
setFormData(prev => ({
...prev,
metadata: {
...prev.metadata,
...metadataFromOcr
}
}))

nextStep()
}

const handleCheck = version => {
setSelectedVersion(version)
}

return (
<Dialog
open
onBack={onBack}
transitionDuration={0}
content={
<CompositeHeader
title={t('SelectPaperVersion.title')}
text={ocrAttributes.map(attr => (
<Box
key={attr.version}
borderRadius="0.5rem"
border="1px solid var(--borderMainColor)"
marginTop="0.5rem"
>
<List className="u-p-0">
<ListItem
button
size="large"
ellipsis={false}
onClick={() => handleCheck(attr.version)}
>
<CompositeHeaderImage
icon={attr.illustration}
iconSize="xsmall"
/>
<ListItemText primary={t(attr.versionLabel)} />
<ListItemSecondaryAction>
<Radio
checked={selectedVersion === attr.version}
onChange={() => handleCheck(attr.version)}
value={attr.version}
name={attr.version}
/>
</ListItemSecondaryAction>
</ListItem>
</List>
</Box>
))}
/>
}
actions={
<Button
data-testid="next-button"
fullWidth
label={t('common.next')}
onClick={handleClick}
disabled={!selectedVersion}
/>
}
/>
)
}

SelectPaperVersion.propTypes = {
onBack: PropTypes.func.isRequired,
ocrFromFlagship: PropTypes.object.isRequired
}

export default SelectPaperVersion
Loading