Definición de arquitectura con Application Composer para análisis de documentos con Textract
Tags: aws
Published: 2022-12-09 19:57:23

Este mes (Diciembre de 2022) AWS lanzo un nuevo servicio llamado **Application Composer**, que nos sirve para la definición de arquitecturas Serverless de manera gráfica e intuitiva, donde podemos arrastrar los diferentes servicios en el espacio de trabajo con el objetivo de definir nuestra arquitectura.

En este post vamos a definir una arquitectura que nos va a permitir extraer el texto de varias facturas para llevar el control de nuestros gastos.

Iniciemos buscando el Servicio **Application Composer** 

![Init Page](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/initpageapplicationcomposer.png?raw=true)

Y en la pagina principal le damos click en Canvas, que nos va a mostrar las opciones de creación de nuestra arquitectura. 

![Create Project](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/createprojectapplicationcomposer1.png?raw=true)

Vemos dos opciones para la creación de nuestra arquitectura conectada y no conectada. La opción conectada va a sincronizar los cambios que realicemos en una carpeta de nuestro equipo y no conectada nosotros somos los encargados de guardar nuestra arquitectura. 
Seleccionamos la opción no conectada y damos click en crear.

![Canvas](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/canvas.png?raw=true)

Como podemos ver la pantalla es muy intuitiva y podemos encontrar los servicios que podemos incluir, la vista de diseño, la vista de la plantilla de SAM que se va generando de acuerdo vamos incluyendo servicios en nuestra arquitectura y el ménu.

Vamos a crear la siguiente arquitectura arrastrando los servicios al Canvas.

![Canvas](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/architecture.png?raw=true)

Ahora vamos a configurar algunas de las propiedades que están disponibles desde el diseñador.

Para el Bucket configuramos el nombre:

![Bucket](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/bucket.png?raw=true)

Para la función Lambda configuramos el nombre, el runtime, la memoria y el tiempo de ejecución:

![Lambda](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/lambda.png?raw=true)

Para la tabla de DynamoDB le definimos un nombre  y el nombre de la primarykey:

![DynamoDB](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/dynamodb.png?raw=true)

Ahora podemos visualizar la plantilla generada y guardamos el archivo desde el menú.

![Template](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/template.png?raw=true)

Antes de validar y modificar nuestra plantilla, vamos a ver el servicio [Textract](https://aws.amazon.com/es/textract/) que nos sirve para extraer texto de diferentes tipos de documentos.

Para esto buscamos el servicio Textract, en la pantalla principal del servicio damos click en _Analyze Document_.

![Textract](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/textract%20page1.png?raw=true)

En la pagina podemos ver los resultados de la extracción de texto de un documento de ejemplo que nos brinda AWS y las formas con las cuales podemos obtener la información, que son: texto plano, formularios, tablas, consultas o firmas. Para nuestro caso vamos utilizar consultas (queries). 

Ahora utilicemos una de las imágenes de nuestro ejemplo.

Primero seleccionamos la [imagen](https://github.com/cecamilo00/posts/tree/circleci-project-setup/applicationcomposer/facturas) de la cual queremos extraer el texto: 

![Select File](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/select%20file.png?raw=true)

Ahora seleccionamos como queremos ver los resultados (Forms,Tables,Queries) y agregamos las consultas que vamos hacer sobre el documento. Es importante que todas las consultas empiecen por **_what_** o **_where_**, en este caso utilice una mezcla de ingles y español para que Textract encontrara los resultados de manera mas acertada, ya que si hacia los queries solo en ingles no encontraba de manera correcta los valores.

![Select Queries](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/select%20queries.png?raw=true)

Al final Textract nos muestra los resultados en los formatos que hallamos seleccionado.

![Result Queries](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/result%20queries.png?raw=true)

Ya que hemos visto el funcionamiento de Textract vamos a ver como podemos realizar esto de manera asíncrona con la arquitectura que definimos al inicio del post.

Vamos hacer algunas modificaciones a la plantilla que descargamos de **Application Composer**.

Empecemos modificando la sección de la funcion Lambda agregando las políticas **_TextractDetectAnalyzePolicy_,CloudWatchPutMetricPolicy,S3FullAccessPolicy**  **Lambda** y después agregamos el código fuente de la función.

```
import boto3
import urllib

textract = boto3.client('textract')
dynamodb = boto3.client('dynamodb')

class Factura:

    def __init__(self, idFactura="", fechaFactura="", valorAPagar="",nombreArchivo=""):
        self.idFactura = idFactura
        self.fechaFactura = fechaFactura
        self.valorAPagar = valorAPagar
        self.nombreArchivo = nombreArchivo

    def getNombreArchivo(self):
        return self.nombreArchivo
    
    def getIdFactura(self):
        return self.idFactura

    def getFechaFactura(self):
        return self.fechaFactura

    def getValorAPagar(self):
        return self.valorAPagar

    def setIdFactura(self, idFactura):
        self.idFactura = idFactura

    def setFechaFactura(self, fechaFactura):
        self.fechaFactura = fechaFactura

    def setValorAPagar(self, valorAPagar):
        self.valorAPagar = valorAPagar
        
    def setNombreArchivo(self, nombreArchivo):
        self.nombreArchivo = nombreArchivo

def lambda_handler(event, context):
    # TODO implement
    bucket = event['Records'][0]['s3']['bucket']['name']
    nombreObjeto = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

    response = textract.analyze_document(
        Document={
            'S3Object': {
                'Bucket': bucket,
                'Name': nombreObjeto
            }
        },
        FeatureTypes=["QUERIES"],
        QueriesConfig={
            "Queries": [{
                "Text": "What is the Fecha vencimiento y pago",
                "Alias": "Fecha de pago"
            },
                {
                "Text": "What is the Total a pagar",
                "Alias": "Valor a pagar"
            },
                {
                "Text": "What is the BEC",
                "Alias": "Numero de Factura"
            }]
        })
    blocks = response['Blocks']
    i = 0
    factura = Factura()
    factura.setNombreArchivo(nombreObjeto)
    for block in blocks:
        if block['BlockType'] == "QUERY_RESULT":
            if float(block['Confidence']) >= 60:
                if i == 0:
                    factura.setFechaFactura(block["Text"])
                elif i == 1:
                    factura.setValorAPagar(block["Text"])
                elif i == 2:
                    factura.setIdFactura(block["Text"])
            i += 1
            print(block)
    result = dynamodb.put_item(
        TableName='datosfacturas',
        Item={
            'PK': {'S': factura.getIdFactura()},
            'FechaPago': {'S': factura.getFechaFactura()},
            'ValorPago': {'S': factura.getValorAPagar()},
            'NombreArchivo': {'S': factura.getNombreArchivo()}
        },
        ConditionExpression="attribute_not_exists(PK)"
    )
```

Cuando ejecutamos el código vamos a obtener un error. 
De acuerdo a la que encontré me di cuenta que al parecer se debe a la versión de boto3 que tiene Lambda configurada, para solucionar este error debemos crear una Layer con una versión mas actualizada de Boto3.

Para saber la versión de Boto3 que maneja Lambda se puede ejecutar dentro de la función:

```
print(boto3.__version__)
```
Que en mi caso el resultado es: _1.26.23_ y la version actual en este momento de Boto3 es: _1.26.26_.

Para dar solución vamos a crear el [Layer](https://aws.amazon.com/es/premiumsupport/knowledge-center/lambda-python-runtime-errors/#:~:text=All%20AWS%20services%20and%20arguments,of%20Boto%203%20and%20Botocore.). 

```
LIB_DIR=boto3-mylayer/python
mkdir -p $LIB_DIR

pip3 install boto3 -t $LIB_DIR

cd boto3-mylayer
zip -r /tmp/boto3-mylayer.zip .

aws lambda publish-layer-version --layer-name boto3updated --zip-file fileb:///tmp/boto3-mylayer.zip --compatible-runtimes "python3.7" "python3.8"
```

Una vez creado el Layer debemos copiar el ARN y agregarlo al template.

![Layer](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/layer.png?raw=true)

Una vez creada la Layer y realizados los cambios en la plantilla vamos a validar y desplegar los servicios.

Para validar la [plantilla](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/textract.yml) debemos ejecutar el siguiente comando desde la terminal (textract.yml es el nombre que le dimos a nuestra plantilla).

```
sam validate --template textract.yml
```
Al ejecutar este comando debemos ver en la terminal algo como  esto:

![SAM Validate](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/sam%20validate.png?raw=true)

Ahora que la validación de nuestra plantilla es correcta debemos eejecutar el siguiente comando para desplegar los servicios de nuestra arquitectura.

```
sam deploy --template textract.yml --stack-name textractinvoices --capabilities CAPABILITY_IAM
```
El resultado del comando es el siguiente:

![Deploy](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/deploy.png?raw=true)

Hemos finalizado con el despliegue satisfactoriamente, ahora podemos ingresar a S3 y subir los [archivos de prueba](https://github.com/cecamilo00/posts/tree/circleci-project-setup/applicationcomposer/facturas) al bucket y validar en DynamoDB que los datos se hayan ingresado correctamente. 

**Application Composer** es un nuevo servicio que como tal hacen algunas cosas por afinar como: agregar mas opciones de edición a cada servicio, mejorar como se generan las plantillas, sin embargo es una muy opción para definir arquitecturas Serverless y tener una plantilla SAM base que nos ayude a mejorar nuestros tiempos de desarrollo.

**_Archivos del post:_**

[plantilla](https://github.com/cecamilo00/posts/blob/circleci-project-setup/applicationcomposer/textract.yml)

[archivos de prueba](https://github.com/cecamilo00/posts/tree/circleci-project-setup/applicationcomposer/facturas)

Me pueden encontrar en

[Camilo Cabrales](https://www.linkedin.com/in/camilo-cabrales-31a0bb53/)

# Referencias

[Textract Boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/textract.html)
[Crear Layer Boto3](https://aws.amazon.com/es/premiumsupport/knowledge-center/lambda-python-runtime-errors/#:~:text=All%20AWS%20services%20and%20arguments,of%20Boto%203%20and%20Botocore.)