From c891ed881ac8067326dcab79aa6948d71b37f549 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 2 Apr 2025 14:04:14 +0300 Subject: [PATCH 1/6] MS-828 Update security page --- SECURITY.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 7ccc4da142..08ac522cf5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,24 +2,29 @@ ## Supported Versions -| Version | Supported | -| ------- | ------------------ | -| 2023.4.0| :white_check_mark: | - +| Version | Supported | +|----------|--------------------| +| 2023.4.5 | :white_check_mark: | +| 2024.1.4 | :white_check_mark: | +| 2024.2.2 | :white_check_mark: | +| 2025.1.0 | :white_check_mark: | ## Reporting a Vulnerability -Please report any security vulnerabilities you find in Android-Simprints-ID to [our email address](securityreport@simprints.com). We appreciate your help in keeping Android-Simprints ID secure! +Please report any security vulnerabilities you find in Android-Simprints-ID to [our email address](securityreport@simprints.com). We +appreciate your help in keeping Android-Simprints ID secure! Here's what you can expect when you report a vulnerability: -Review: We will review all reported vulnerabilities. -Response: You will receive an initial response acknowledging your report. -Acceptance/Decline: We will let you know whether we accept the vulnerability as a valid security issue at the earliest opportunity. -Fix and Release: If we accept the vulnerability, we will work to fix it and release a patch as soon as possible. -Credits: We will publicly credit you for finding the vulnerability unless you request otherwise. -Additional Information: +* **Review:** We will review all reported vulnerabilities. +* **Response:** You will receive an initial response acknowledging your report. +* **Acceptance/Decline:** We will let you know whether we accept the vulnerability as a valid security issue at the earliest opportunity. +* **Fix and Release:** If we accept the vulnerability, we will work to fix it and release a patch as soon as possible. +* **Credits:** We will publicly credit you for finding the vulnerability unless you request otherwise. + +## Additional Information: You can also report security vulnerabilities through the security page on the GitHub repository. -For more information on our security practices, please see our Security pages on our [documentation site](https://simprints.gitbook.io/docs/security-and-privacy/security-and-privacy-considerations). +For more information on our security practices, please see our Security pages on +our [documentation site](https://simprints.gitbook.io/docs/security-and-privacy/security-and-privacy-considerations). From 4a28517be281213c6878ea57aa0fc44fac1cd4bc Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 2 Apr 2025 14:28:48 +0300 Subject: [PATCH 2/6] MS-828 Remove factually incorrect information about architecture from readme --- README.md | 124 ++++++++++++------------------------------------------ 1 file changed, 28 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index eb5f4d9109..db7d83083d 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,10 @@ # Android-Simprints-ID -## Architecture +## Technical documentation -The goal of Simprints Architecture is to keep the app maintainable, scalable, easy to work on by multiple people and teams simultaneously, and most importantly - simple. **This architecture is a work in progress, it reflects the planned future state of the repo, it does not reflect its current setup.** - -### Modules - -There are 2 types of modules we can use, a Feature Module and a Infrastructure Module, with a 3rd type which is the App Module of which there will only ever be 1 of (the ID module). This design has been adopted in several places, and there is even a guide you can check out [here](https://dev.to/noureldinshobier/building-scalable-flutter-apps-architecture-styling-conventions-state-management-40c9) that loosely breaks down a similar architecture. But keep in mind it’s not identical. Following this breakdown a very simple application comprised of a login screen could look like this: - -- App (app module) -- feature login (login UI/UX) -- infra login (login infrastructure code) -- infra networking (networking infrastructure code) - -### Feature Modules - -Feature modules contain the logical boundaries for the user interaction components of a given feature in SID. For example these modules will contain the activities, fragments, views, layouts, and navigation components of a specific feature in SID. They should not contain reusable business logic, or things relating to infrastructure outside of the UX of the feature, those should all be inside of infrastructure modules. You might have two identically themed modules because of this which is fine. For example you could have a Remote Config feature module, which contains the screens and components that interact with the user, and a Remote Config infrastructure module, which contains the infrastructure for fetching, updating and storing the remote configuration. Feature modules will have a couple rules: - -Rules: -- Feature modules can depend on the App Module (if they’re a dynamic feature module) and any number of infrastructure modules, but they cannot depend on other feature modules. - - *Note: The circular dependency between DFMs and the App module is baked into the system for some reason. This is not a logic/design based dependency. Melad is going to explore this on a future learning day.* -- Feature modules are accountable for their own internal navigation (they should use a [nested nav graph](https://developer.android.com/guide/navigation/navigation-design-graph#nested-graphs)) and the back stack for their UI. They should have a single point of entry and exit. -- Feature modules can be a [dynamic feature module](https://developer.android.com/guide/playcore/feature-delivery/on-demand) or a normal Android module, depending on its delivery needs. - -### Infrastructure Modules - -Infrastructure Modules contain business logic that should be logically isolated for cleanliness and re-use. Unlike feature modules they should be completely unaware of any UX components such as UIs or navigation. Their goal is to encapsulate some part of the domain layer of the application and make it re-usable to other components. Infrastructure modules will have a couple rules: - -Rules: -- Infrastructure modules should have a single access point which is the contract for the behavior of the module. This should be a top level interface file that exposes the functionality of the module. All other files (except those related to building) should be marked internal and not exposed to its users. -- Infrastructure modules will depend on the builder pattern. There will be two top level nome inner classes, the interface the exposes the functionality, and the builder object that is able to build the implementation of that interface. Say for the *infralogin* module we have a *LoginManager* interface we'd also have a top level *LoginManagerBuilder* class which is able to build the required implementation of that interface. It doesn't matter what that builder itself uses for DI (such as dagger ), and same for the other builders that call it. -- Infrastructure modules can’t depend on feature modules or the App module, but they can depend on other infrastructure modules (keep in mind the dependencies can’t be circular). -- Infrastructure modules preferably are raw Java modules, but they can be Android modules if they need access to certain android components. This is fine for modules that use Jetpack components like Room or Datastore, but they cannot use any components relating to user interactions, such as Activities, Fragments, UI/Layout, or navigation components. - -### The App Module - -In general you don’t have to worry about this section as there can only be one app module (called ID) which is the top level Android Application module. It’s role is to expose the Android manifest, do the initial navigation of the app and house the main Application class. - -Rules: -- The app module should not contain any major business or domain logic, it should just be a thin layer to handle the app navigation and any Android specific requirements such as declaring the apps min API level, etc. - -### SID Breakdown - -Following the guidelines above the end goal of SID should look roughly like: - -- featureabout (com.simprints.feature.about) -- featureclientapi (com.simprints.feature.clientapi) -- featuredashboard (com.simprints.feature.dashboard) -- featureface (com.simprints.feature.face) -- featurefingerprint (com.simprints.feature.fingerprint) -- featurelogin (com.simprints.feature.login) -- featuresyncinfo (com.simprints.feature.syncinfo) -- id -- infraconfig (com.simprints.infra.config) -- infraenrolmentrecords (com.simprints.infra.enrolment.records) -- infraevents (com.simprints.infra.events) -- infrafacematcher_roc (com.simprints.infra.facematcher) -- fingerprint/infra/matcher (com.simprints.fingerprint.infra.matcher) -- fingerprint/infra/scanner (com.simprints.fingerprint.infra.scanner) -- fingerprint/infra/scannermock (com.simprints.fingerprint.infra.scannermock) -- infraimages (com.simprints.infra.images) -- infralicense (com.simprints.infra.license) -- infralogging (com.simprints.infra.logging) -- infralogin (com.simprints.infra.login) -- infranetwork (com.simprints.infra.networking) -- infrarealm (com.simprints.infra.enrolment.records.realm.store) -- infrasecurity (com.simprints.infra.security) - -*Note: There is no longer a core module. There should be no "catch all" module, because it will just become a graveyard / completely overused, like the previous ID module. Every module should have a clear singular purpose.*
- -
+A high level documentation on how each module works can be found in +the [project's wiki](https://simprints.gitbook.io/docs/architecture/system-architecture/mobile/simprints-id-sid). +More details about each feature or how each module work can be seen inside every module README files or inside respective folders. ## Development setup @@ -113,27 +48,26 @@ Replace `` and `` with your ac For security reasons, some files are not included in the repository. You must download them separately: -1. Download the necessary files from the internal [SID Development Resources folder](https://drive.google.com/drive/folders/1OLrGhx3AW91ab2zduy8FzNuE5r5VEs7g?usp=drive_link). **Note:** This link is accessible to Simprints employees only. +1. Download the necessary files from the + internal [SID Development Resources folder](https://drive.google.com/drive/folders/1OLrGhx3AW91ab2zduy8FzNuE5r5VEs7g?usp=drive_link). + - **Note:** This link is accessible to Simprints employees only. 2. Place the downloaded files as follows: - - `signing_info.gradle.kts`: Place this in the `build-logic` directory. - - `debug.keystore`: Place this in the root directory of the project. - - `google-services.json`: Place this in the `id/src` directory. + - `signing_info.gradle.kts`: Place this in the `build-logic` directory. + - `debug.keystore`: Place this in the root directory of the project. + - `google-services.json`: Place this in the `id/src` directory. ### 5. Build and Run the App Open the project in Android Studio, sync the project with Gradle files, and then build and run the app on your connected device or emulator. - ## Full CI Workflow -The aim of the `ci` workflow is to run all tests in all modules, assemble production and debug -builds of the APK, and report to the main CI Slack channel. +The aim of the `ci` workflow is to run all tests in all modules, assemble production and debug builds of the APK, and report to the main CI +Slack channel. When run, it immediately triggers all the other relevant workflows such that all tests are run. -In the mean-time, the `ci` build waits for the other workflows to finish and, if they pass, continue -to the assemble and deploy steps. +In the mean-time, the `ci` build waits for the other workflows to finish and, if they pass, continue to the assemble and deploy steps. -It is triggered upon pull requests and serves as validation of the integrity of the branch for any -pull requests into `main`. +It is triggered upon pull requests and serves as validation of the integrity of the branch for any pull requests into `main`.
@@ -142,12 +76,8 @@ pull requests into `main`. ### How to run tests To run all tests in all modules, run `./gradlew testDebugUnitTest` from the root directory. -To run test for a specific module, run `./gradlew moduleName:testDebugUnitTest` for example to run tests for the id module run `./gradlew id:testDebugUnitTest` - -### Technical documentation on features - -A high level documentation on how each module works can be found in the [project's wiki](https://simprints.gitbook.io/docs/architecture/system-architecture/mobile/simprints-id-sid). -More details about each feature or how each module work can be seen inside every module README files or inside respective folders. Higher level features that touch all modules are documented in the [id module](id/README.md). +To run test for a specific module, run `./gradlew moduleName:testDebugUnitTest` for example to run tests for the id module run +`./gradlew id:testDebugUnitTest`
@@ -156,18 +86,20 @@ More details about each feature or how each module work can be seen inside every Simprints ID has 3 different build types: `debug`, `staging` and `release`. - `debug`: to be used only in development. Uses the development environment of the backend -- `staging`: to be used only for pre-release internal tests. Uses the staging environment of the backend, and can be used to test production builds both locally and on the Playstore internal channel +- `staging`: to be used only for pre-release internal tests. Uses the staging environment of the backend, and can be used to test production + builds both locally and on the Playstore internal channel - `release`: to be used only in production. Uses the production environment of the backend ## Building the app (command line) + The examples below will show how to create app bundles using `./gradlew`. To create apks, use the `assemble` command instead of `bundle`. -| Command | DEBUG_MODE | Encrypted DBs | Logging | Debug Features (i.e debug activity) | Backend | -|---------------------------|-----------------|----------------|-------------|-------------------------------------|----------------------------------------| -| `bundleDebug` | ✓ | x | ✓ | ✓ | development | -| `bundleStaging` | ✓ | x | ✓ | ✓ | staging | -| `bundleRelease` | x | ✓ | x | x | production (CI only) | +| Command | DEBUG_MODE | Encrypted DBs | Debug Features (i.e debug activity) | Backend | +|-----------------|------------|---------------|-------------------------------------|----------------------| +| `bundleDebug` | ✓ | x | ✓ | development | +| `bundleStaging` | ✓ | x | ✓ | staging | +| `bundleRelease` | x | ✓ | x | production (CI only) |
@@ -175,13 +107,13 @@ To create apks, use the `assemble` command instead of `bundle`. To create an universal apk that can be shared you need to: -1. Create a bundle of the app -`./gradlew clean bundleDebug` +1. Create a bundle of the app `./gradlew clean bundleDebug` 2. Create a universal apk that can be installed in any device (warning: this is a big app) -`bundletool build-apks --bundle=id/build/outputs/bundle/debug/id-debug.aab --output=id-debug.apks --ks=debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --mode=universal --overwrite` + `bundletool build-apks --bundle=id/build/outputs/bundle/debug/id-debug.aab --output=id-debug.apks --ks=debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --mode=universal --overwrite` -To install [bundletool](https://github.com/google/bundletool) you can download the jar from Github and execute it using `java -jar bundletool` or install using Homebrew (on macOS). +To install [bundletool](https://github.com/google/bundletool) you can download the jar from Github and execute it using +`java -jar bundletool` or install using Homebrew (on macOS).
From 09d18eef0bb66168144c342d4e723ff98b59ce0f Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 2 Apr 2025 14:30:33 +0300 Subject: [PATCH 3/6] SM-828 Run linting in workflows readme --- .github/workflows/README.md | 59 +++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 9dec8d89fd..fbd16a706f 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,28 +1,34 @@ - # **CI/CD Implementation** ## **CI Workflow** -The CI Workflow is responsible for ensuring the quality of code changes before they are merged into the main branch. It performs the following tasks: - -1. **Unit Testing:** Executes unit tests for all modules to ensure new code does not break existing functionality. This helps identify and fix bugs early in the development process. +The CI Workflow is responsible for ensuring the quality of code changes before they are merged into the main branch. It performs the +following tasks: -2. **Code Quality Analysis:** Runs SonarQube analysis to identify code quality issues and potential bugs. This helps maintain high code standards and prevent future problems. +1. **Unit Testing:** Executes unit tests for all modules to ensure new code does not break existing functionality. This helps identify and + fix bugs early in the development process. +2. **Code Quality Analysis:** Runs SonarQube analysis to identify code quality issues and potential bugs. This helps maintain high code + standards and prevent future problems. **Workflow Trigger and Jobs** The CI Workflow is triggered by two events: -1. **Manual Trigger:** The workflow can be manually triggered through workflow dispatch, allowing developers to initiate the CI process on demand. - -2. **PR Changes:** The workflow is also triggered when a pull request (PR) is created or updated. This ensures that code changes are automatically tested and analyzed before being merged into the main branch. +1. **Manual Trigger:** The workflow can be manually triggered through workflow dispatch, allowing developers to initiate the CI process on + demand. +2. **PR Changes:** The workflow is also triggered when a pull request (PR) is created or updated. This ensures that code changes are + automatically tested and analyzed before being merged into the main branch. -The CI Workflow consists of 8 unit testing jobs plus the SonarQube scanning job. The unit testing jobs run in parallel, each responsible for testing a specific module or group of modules. The SonarQube scanning job waits until all the unit testing jobs are completed, the XML test coverage reports are uploaded, and then starts the sonar scan. +The CI Workflow consists of 8 unit testing jobs plus the SonarQube scanning job. The unit testing jobs run in parallel, each responsible for +testing a specific module or group of modules. The SonarQube scanning job waits until all the unit testing jobs are completed, the XML test +coverage reports are uploaded, and then starts the sonar scan. ## **CD Workflow** + Deployment Workflow Diagram: + ```mermaid flowchart TD trigger([Manual Trigger]) --> setup(Release Setup) @@ -50,39 +56,47 @@ flowchart TD deploy_dev --> deploy_firebase_dev(Upload to Firebase Dev) end ``` + **Trigger** -The CD Workflow can be manually triggered through workflow dispatch on a **release** branch. +The CD Workflow can be manually triggered through workflow dispatch on a **release** branch. **Environments** The CD Workflow is responsible for automatically deploying new code changes to different environments. It performs the following tasks: -1. **Deployment to Dev Environment:** Deploys the latest development build to the Firebase distribution account, making it accessible for testing and development purposes. - -2. **Deployment to Staging Environment:** Deploys the latest staging build to the Firebase distribution account, allowing a wider group of users to test the application before release. - -3. **Deployment to Internal Environment:** Deploys the latest release build to the internal testing track, providing a final testing phase before deployment to production. +1. **Deployment to Dev Environment:** Deploys the latest development build to the Firebase distribution account, making it accessible for + testing and development purposes. -4. **Promotion to Google Play Tracks:** Promotes the release build to different Google Play tracks in a controlled manner, starting with alpha and gradually progressing to the production track. +2. **Deployment to Staging Environment:** Deploys the latest staging build to the Firebase distribution account, allowing a wider group of + users to test the application before release. +3. **Deployment to Internal Environment:** Deploys the latest release build to the internal testing track, providing a final testing phase + before deployment to production. +4. **Promotion to Google Play Tracks:** Promotes the release build to different Google Play tracks in a controlled manner, starting with + alpha and gradually progressing to the production track. **Version Code** The version code is generated from 3 things: -1. A "base" version code of `10000000`. For a time the version code was derived from unix time / 1000. This created a always increasing number, but was otherwise not so useful. Setting a base allows us to establish a clean "floor" for the version code. If the workflow runs ever exceed the base, first of all go us, and 2nd the base can be removed. -2. The `run number` of the workflow. This tells us which run a build came from and is auto incrementing. -3. The last two digits are reserved for the `run attempts`. This tells us how many times a specific workflow was run. If more than 99 attempts are made (something is wrong) the build will fail and you need to start a new workflow run. -- Ex: `100001502` means this build came from workflow 15, run 2. +1. A "base" version code of `10000000`. For a time the version code was derived from unix time / 1000. This created a always increasing + number, but was otherwise not so useful. Setting a base allows us to establish a clean "floor" for the version code. If the workflow runs + ever exceed the base, first of all go us, and 2nd the base can be removed. +2. The `run number` of the workflow. This tells us which run a build came from and is auto incrementing. +3. The last two digits are reserved for the `run attempts`. This tells us how many times a specific workflow was run. If more than 99 + attempts are made (something is wrong) the build will fail and you need to start a new workflow run. + +- Ex: `100001502` means this build came from workflow 15, run 2. **Version Name** The version name follows our versioning convention: + - `year`.`quarter`.`release`-`(optional) deployment`+`run number`.`run attempt` -The optional params are **only** used on none release builds. +The optional params are **only** used on none release builds. - Ex: `2024.1.0-dev+15.2`, Quarter 1 of 2024, dev deployment, time, run 15, attempt 2 - Ex: `2024.1.0+15.2`, Quarter 1 of 2024, release, run 15, attempt 2 @@ -91,4 +105,5 @@ Note: The `year`.`quarter`.`release` is take from the branch name. Ex: `release/ ## **Dependency Updates workflow** -Updates project dependencies using Dependabot, an automated dependency management tool, ensuring that the project always uses the latest stable versions of its dependencies. +Updates project dependencies using Dependabot, an automated dependency management tool, ensuring that the project always uses the latest +stable versions of its dependencies. From b931dcca9d5abe711a13cef4ab8fd4087f537ba7 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 2 Apr 2025 15:30:42 +0300 Subject: [PATCH 4/6] MS-828 Cleanup README files in face modality --- face/README.md | 27 ++++++++++ face/capture/README.md | 51 +++++++++++-------- .../java/com/simprints/face/capture/README.md | 44 ---------------- .../capture/screens/livefeedback/README.md | 17 ------- .../screens/livefeedbackautocapture/README.md | 19 ++++--- face/infra/base-bio-sdk/README.md | 6 +-- face/infra/roc-v1/README.md | 5 +- face/infra/roc-v3/README.md | 4 +- 8 files changed, 78 insertions(+), 95 deletions(-) create mode 100644 face/README.md delete mode 100644 face/capture/src/main/java/com/simprints/face/capture/README.md diff --git a/face/README.md b/face/README.md new file mode 100644 index 0000000000..532491fe51 --- /dev/null +++ b/face/README.md @@ -0,0 +1,27 @@ +# Face modality + +The face modality was created as a way to have contactless biometrics as an option for SimprintsID. + +## Flow overview + +This first thing that the face capture needs to do is initialize the required SDK, loading the SDK library and making sure the SDK license +is in place and is valid. + +To understand more how the capture flow works, check the capture [README](capture/README.md). + +## Detection + +The detection phase is when the app analyzes the (already cropped by `CropToTargetOverlayAnalyzer`) and returns a Face that it +found (or null if none). + +Note that the Face returned also have a template already in it, making it a one step to find a face and extract the template for the app. +It was done that way because most SDKs tested returned a template when looking for a face. + +## RankOne + +RankOne is the SDK of choice for face recognition - after we did some extensive testing with lots of other providers. +RankOne gives an SDK to copy to inside Simprints. It lives inside the respective infra module. + +Current RankOne versions are + * 1.23 for legacy projects + * 3.1 for all new projects diff --git a/face/capture/README.md b/face/capture/README.md index 368830201b..c4609a1a8e 100644 --- a/face/capture/README.md +++ b/face/capture/README.md @@ -1,34 +1,43 @@ -# Face modality +# Capture Flow -The face modality was created as a way to have contactless biometrics as an option for SimprintsID. +This is how the face module works internally to capture the faces, extract the templates, and return the results to SimprintsID. -## Important links +## FaceCaptureControllerFragment -* [Confluence](https://simprints.atlassian.net/wiki/spaces/CS/overview) -* [Jira](https://simprints.atlassian.net/secure/RapidBoard.jspa?rapidView=19) +The shared fragment (and its ViewModel) is responsible for: -## Flow overview +- Checking that the correct parameters were sent from calling module for a correct capture +- Start the internal Navigation Graph +- Managing the next steps of the navigation by using events and the Navigation Graph +- Saving the captures on disk once the flow is finished correctly +- Finish the flow correctly and return the correct result to calling module -Calling module start the face modality navigating with `FaceCaptureControllerFragmentArgs` as bundle. -This first thing that the face capture needs to do is initialize the required SDK, loading the SDK library and making sure the SDK license is in place and is valid. +## When the flow finishes correctly -To understand more how the capture flow works, check the capture [README](src/main/java/com/simprints/face/capture/README.md). +The FaceCaptureViewModel receives all captures and saves the valid ones on disk. -## Detection +## PreparationFragment -The detection phase is when the app analyzes a PreviewFrame (already cropped by FrameProcessor) and returns a Face that it found (or null if none). -The methods on the Detectors are suspend functions because the process to get a Face can be onerous (you should use `withContext(Dispatchers.IO)`). -Note that the Face returned also have a template already in it, making it a one step to find a face and extract the template for the app. -It was done that way because most SDKs tested returned a template when looking for a face. Currently we only use the analyze method that receives a `PreviewFrame`, -the method to analyze a `Bitmap` is there for future necessity (it was used by the R&D team). +It is a simple fragment where the user sees a preparation on how to capture a good face image. Its only responsibility is to show the +message and start the LiveFeedbackFragment. -### MockFaceDetector +## LiveFeedbackFragment -This is a mock detector that always return true and wait a bit (200ms) before returning a face with an empty template. +The responsibility of this fragment (and its ViewModel) is to make sure the user receives correct information on the state of the face on +the screen, i.e. if the face is valid or not and how to fix it. +This fragment is also responsible to start the camera and handle the frames being passed downstream from our camera library. -## RankOne +The FaceCaptureViewModel has a channel that starts receiving frames when the camera starts. The LiveFeedbackFragment subscribe to that +channel and process each frame. -RankOne is the SDK of choice for face recognition - after we did some extensive testing with lots of other providers. -RankOne gives an SDK to copy to inside Simprints. It lives inside the respective infra module. +If the face is valid, the fragment will prompt the user to start a capture and at that time will start saving each new frame as a capture. +After the number of required captures is done, the fragment finishes and goes to the ConfirmationFragment. -Current RankOne version = 1.23 (with OpenMP disabled) +During the live feedback process, while the user is trying to center the face on the frame, the ViewModel will keep any good image as a +fallback image. This is to make sure that the user always has at least one good image, even if they move the phone too fast during the +capture process. + +## ConfirmationFragment + +If all goes well, the user comes to a screen that congratulates them on taking a good photo and ask them to continue their flow. +If the previewed captures are not satisfactory, user can return to the LiveFeedbackFragment and try again. diff --git a/face/capture/src/main/java/com/simprints/face/capture/README.md b/face/capture/src/main/java/com/simprints/face/capture/README.md deleted file mode 100644 index 3aca298c9f..0000000000 --- a/face/capture/src/main/java/com/simprints/face/capture/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Capture Flow - -This is how the face module works internally to capture the faces, extract the templates, and return the results to SimprintsID. - -## FaceCaptureControllerFragment - -The shared fragment (and its ViewModel) is responsible for: -- Checking that the correct parameters were sent from calling module for a correct capture (number of samples to capture, sessionId, projectId) -- Start the internal Navigation Graph -- Managing the next steps of the navigation by using events and the Navigation Graph -- Saving the captures on disk once the flow is finished correctly -- Finish the flow correctly and return the correct result to calling module - -## When the flow finishes correctly - -The FaceCaptureViewModel receives all captures and saves the valid ones on disk. - -## PreparationFragment - -It is a simple fragment where the user sees a preparation on how to capture a good face image. Its only responsibility is to show the message and start the LiveFeedbackFragment. - -## LiveFeedbackFragment - -The responsibility of this fragment (and its ViewModel) is to make sure the user receives correct information on the state of the face on the screen, i.e. if the face is valid or not and how to fix it. -This fragment is also responsible to start the camera and handle the frames being passed downstream from our camera library. - -The FaceCaptureViewModel has a channel that starts receiving frames when the camera starts. The LiveFeedbackFragment subscribe to that channel and process each frame. - -If the face is valid, the fragment will prompt the user to start a capture and at that time will start saving each new frame as a capture. -After the number of required captures is done, the fragment finishes and goes either to a ConfirmationFragment or RetryFragment. - -During the live feedback process, while the user is trying to center the face on the frame, the ViewModel will keep any good image as a fallback image. -This is to make sure that the user always has at least one good image, even if they move the phone too fast during the capture process. This should render the RetryFragment unreachable. - -## RetryFragment - -If the user can retry the flow, they will come to a fragment that tells a bit more about the problem and how to solve it. -Usually the same steps they should take to take a good capture. After that, they return to LiveFeedbackFragment to try again. - -After several tries, if the user still can’t take a good photo, the fragment will show some error message and return to SimprintsID an exit error. - -## ConfirmationFragment - -If all goes well, the user comes to a screen that congratulates them on taking a good photo and ask them to continue their flow. diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/README.md b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/README.md index dc73888515..11abf7f8bb 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/README.md +++ b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/README.md @@ -5,20 +5,3 @@ The main auxiliary tools for capture a good image is CameraTargetOverlay and Fra ## CameraTargetOverlay This is the overlay that is painted on top of the camera (in `LiveFeedbackFragment`) and specify the area that the SDK will look into to check for a face. -Currently it has a size of 240dp that is used as the size for `DashedCircularProgress` in `fragment_live_feedback.xml`. -There was no other way of setting that size and synchronizing between both components and `FrameProcessor`, that is why it is inside this class. -The overlay is drawn only once, when `drawSemiTransparentTarget` or `drawWhiteTarget` is called. - -## FrameProcessor - -The most important piece of code, this class: -- create a rotated rectangle based on the current orientation of the camera -- resize the new rectangle if the image on the screen has some crop in it -- create a new box based on the new rectangle -- crop the frame using just the new rectangle - -All those steps are necessary because cameras on Android are usually rotated or even flipped (front face camera). -Also, what is shown on the screen and the real frame are not the same image, sometimes some cropping might be occurring (e.g. the phone has 16:9 aspect ratio but the photo is 16:10). - -Note that this class uses `CameraTargetOverlay.rectForPlane`, which internally uses `percentFromTop`. If the overlay is changed (or a new one is used), the FrameProcessor might need to be adjusted. -This was done because there is no way of knowing where the overlay is on the screen by other means. diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedbackautocapture/README.md b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedbackautocapture/README.md index 52a0893b97..f5def99d18 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedbackautocapture/README.md +++ b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedbackautocapture/README.md @@ -1,10 +1,17 @@ # Face auto-capture -This `livefeedbackautocapture` package is similar to the manual capture [livefeedback](../livefeedback/README.md) one, except: -* Auto-capture has a preparation period from the moment `LiveFeedbackAutoCaptureFragment` becomes visible and for `LiveFeedbackAutoCaptureFragmentViewModel.AUTO_CAPTURE_VIEWFINDER_RESUME_DELAY_MS` amount of time. This helps prevent startling the user with a too early start of auto-capture - before the user would aim the viewfinder at a face. -* The auto-capture imaging progress starts when a qualifying face image appears in the viewfinder, and lasts for `LiveFeedbackAutoCaptureFragmentViewModel.AUTO_CAPTURE_IMAGING_DURATION_MS` amount of time. -* The number of images to accept is `LiveFeedbackAutoCaptureFragmentViewModel.samplesToKeep` - similarly to `LiveFeedbackFragmentViewModel.samplesToCapture`. The best quality sampled images are selected to keep. -* The kept sampled images and the fallback image are included in the capture events - unlike in `LiveFeedbackFragmentViewModel` where all conventionally captured and the fallback images are included. +This `livefeedbackautocapture` package is similar to the manual capture livefeedback one, except: + +* Auto-capture has a preparation period from the moment `LiveFeedbackAutoCaptureFragment` becomes visible and for + `LiveFeedbackAutoCaptureFragmentViewModel.AUTO_CAPTURE_VIEWFINDER_RESUME_DELAY_MS` amount of time. This helps prevent startling the user + with a too early start of auto-capture - before the user would aim the viewfinder at a face. +* The auto-capture imaging progress starts when a qualifying face image appears in the viewfinder, and lasts for + `LiveFeedbackAutoCaptureFragmentViewModel.AUTO_CAPTURE_IMAGING_DURATION_MS` amount of time. +* The number of images to accept is `LiveFeedbackAutoCaptureFragmentViewModel.samplesToKeep` - similarly to + `LiveFeedbackFragmentViewModel.samplesToCapture`. The best quality sampled images are selected to keep. +* The kept sampled images and the fallback image are included in the capture events - unlike in `LiveFeedbackFragmentViewModel` where all + conventionally captured and the fallback images are included. * The capture events have an `isAutoCapture` flag value of `true`. -The reason for having this package as a near-copy of `livefeedback` is to keep both implementations independent but similar. The layout for `LiveFeedbackAutoCaptureFragment` is `fragment_live_feedback_auto_capture.xml`. +The reason for having this package as a near-copy of `livefeedback` is to keep both implementations independent but similar. The layout for +`LiveFeedbackAutoCaptureFragment` is `fragment_live_feedback_auto_capture.xml`. diff --git a/face/infra/base-bio-sdk/README.md b/face/infra/base-bio-sdk/README.md index 9e5d697dbe..0c826b86aa 100644 --- a/face/infra/base-bio-sdk/README.md +++ b/face/infra/base-bio-sdk/README.md @@ -4,9 +4,9 @@ This is a base module that allows users to integrate new Face SDKs into SID's Fa To use this base module, simply add the base module to your new SDK module's dependencies and then let your new module provide and implement the operations in -1. [`FaceBioSdkInitializer`](src/main/java/com/simprints/infra/face.basebiosdk/initialization/FaceBioSdkInitializer.kt) -2. [`FaceDetector`](src/main/java/com/simprints/infra/face.basebiosdk/detection/FaceDetector.kt) -3. [`FaceMatcher`](src/main/java/com/simprints/infra/face.basebiosdk/matching/FaceMatcher.kt) +1. [`FaceBioSdkInitializer`](src/main/java/com/simprints/face/infra/basebiosdk/initialization/FaceBioSdkInitializer.kt) +2. [`FaceDetector`](src/main/java/com/simprints/face/infra/basebiosdk/detection/FaceDetector.kt) +3. [`FaceMatcher`](src/main/java/com/simprints/face/infra/basebiosdk/matching/FaceMatcher.kt) Here is an example on how to provide the there implemented classes ```kotlin diff --git a/face/infra/roc-v1/README.md b/face/infra/roc-v1/README.md index 1dd1fc444d..cf0df018c4 100644 --- a/face/infra/roc-v1/README.md +++ b/face/infra/roc-v1/README.md @@ -1,4 +1,5 @@ # ROC V1 -This module provides a wrapper for accessing all ROC SDK APIs. -It implements all operations in `:face:infra:base-bio-sdk` module and provides the implemented classes in `RocWrapperModule`. +This module provides a wrapper for accessing all ROC v1.23 SDK APIs. + +It implements all operations in `:face:infra:base-bio-sdk` module and provides the implemented classes in `RocV1WrapperModule`. diff --git a/face/infra/roc-v3/README.md b/face/infra/roc-v3/README.md index 10c99833cb..a779c1ed39 100644 --- a/face/infra/roc-v3/README.md +++ b/face/infra/roc-v3/README.md @@ -1,4 +1,4 @@ # ROC V3 -This module provides a wrapper for accessing all ROC SDK V3.1 APIs. -It implements all operations in `:face:infra:base-bio-sdk` module and provides the implemented classes in `RocWrapperModule`. +This module provides a wrapper for accessing all ROC SDK v3.1 APIs. +It implements all operations in `:face:infra:base-bio-sdk` module and provides the implemented classes in `RocV3WrapperModule`. From dbdc6d85fda77097e161282a5032f7668176cd4c Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 2 Apr 2025 15:55:43 +0300 Subject: [PATCH 5/6] MS-828 Cleanup READMEs in fingerprint modules --- fingerprint/README.md | 20 ++++++ fingerprint/capture/README.md | 25 +++---- .../simprints/fingerprint/capture/README.md | 23 ------- fingerprint/connect/README.md | 63 ++++++++++++------ fingerprint/infra/base-bio-sdk/README.md | 8 +-- fingerprint/infra/scanner/README.md | 5 +- .../scanner/doc/data_pipeline_diagram.png | Bin 80670 -> 0 bytes .../fingerprint/infra/scanner/data/README.md | 32 +++------ .../fingerprint/infra/scanner/v2/README.md | 9 ++- fingerprint/infra/scannermock/README.md | 41 ++++++++---- .../fingerprint/infra/scannermock/README.md | 9 +-- .../infra/scannermock/dummy/README.md | 9 +-- .../infra/scannermock/record/README.md | 6 +- .../infra/scannermock/simulated/README.md | 30 ++++----- fingerprint/infra/simafis-wrapper/README.md | 3 +- 15 files changed, 145 insertions(+), 138 deletions(-) create mode 100644 fingerprint/README.md delete mode 100644 fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/README.md delete mode 100644 fingerprint/infra/scanner/doc/data_pipeline_diagram.png diff --git a/fingerprint/README.md b/fingerprint/README.md new file mode 100644 index 0000000000..f3068f9691 --- /dev/null +++ b/fingerprint/README.md @@ -0,0 +1,20 @@ +# Fingerprint Modality Module + +This is the main module for the fingerprint modality in Simprints ID. + +### Further READMEs + +The main domain sub-packages are: + +- [Connection](connect/README.md) \- which handles connection to the scanner. +- [Capture](capture/README.md) \- which handles fingerprint capture UI flow. +- [Scanner](infra/scanner/README.md) + \- which handles high-level interfacing with the `fingerprint:infra:scanner` module for using a Vero fingerprint scanner. +- Various biometric SDK related modules + +The satellite libraries of the fingerprint modality that are used in this module are: + +- [`fingerprint:infra:scanner`](infra/scanner/README.md) + \- which handles low-level communication with the fingerprint scanner and tucks it behind a `Scanner` class abstraction. +- [`fingerprint:infra:scannermock`](infra/scannermock/README.md) + \- which is a utility package providing mocking and simulation options for the fingerprint scanner for testing and debugging. diff --git a/fingerprint/capture/README.md b/fingerprint/capture/README.md index 89031c1ad9..69cf018708 100644 --- a/fingerprint/capture/README.md +++ b/fingerprint/capture/README.md @@ -1,20 +1,13 @@ -# Fingerprint Modality Module +# Fingerprint capture -This is the main module for the fingerprint modality in Simprints ID. +This is how the fingerprint module works internally to capture the fingerprints, extract the templates, and return the results to +SimprintsID. -### Further READMEs +## FingerprintCaptureFragment -The main domain sub-packages are: +This fragment is the entry point into the fingepringt capture flow and it is responsible for: -- [Connection](../connect/README.md) \- which handles connetion to the scanner. -- [Scanner](../infra/scanner/README.md) - \- which handles high-level interfacing with the `fingerprint:infra:scanner` module for using a Vero fingerprint scanner. - -The satellite libraries of the fingerprint modality that are used in this module are: - -- [`fingerprint:scanner`](../infra/scanner/README.md) - \- which handles low-level communication with the fingerprint scanner and tucks it behind a `Scanner` class abstraction. -- [`fingerprint:scannermock`](../infra/scannermock/README.md) - \- which is a utility package providing mocking and simulation options for the fingerprint scanner for testing and debugging. -- [`feature:module`](../../feature/matcher/README.md) - \- which houses the algorithms for matching fingerprints and provides an interface for their use. +- Launching scanner connection sub-flow +- Leading user over fingerprint capture step by step +- Saving captured fingerprint images if necessary +- Finishing the flow and reporting the capture results to the calling module diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/README.md b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/README.md deleted file mode 100644 index 240f6f4e5a..0000000000 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# CollectFingerprintsActivity - -This activity is the screen where the user scans fingerprint templates and images from the scanner. - -The activity starts by receiving which fingers to capture in the `CollectFingerprintsTaskRequest`. -Once complete, it will return a list of `Fingerprint`s if it finishes normally. -Note that the `Fingerprints` returned may differ from that fingers requested if fingers were skipped or auto-added due to bad scans. - -The activity UI centers around a `ViewPager` that holds `FingerFragment`s, with a `ScanningTimeoutBar` and button that get updated during the scanning flor. -The central state is stored as a `CollectFingerprintsState` by the view model, which is updated and re-posted whenever a change to the UI is to be made. -The appropriate UI is deduced mainly by retrieving from a map the current `FingerCollectionState`. -The view model also emits certain events peripherally. - -`ScannerManager` is expected to already have a connected and ready Vero. -When the activity is resumed, the scanner trigger button is effectively linked to the scan button (except when the confirmation dialog is shown in which case it's linked to the "OK" option). -Should the scanner disconnect during communication, the `ConnectScannerActivity` will be launched with the reconnect mode, which will appear as an overlay. -The scanner disconnects upon leaving the activity. - -`FingerprintCaptureEvent`s are saved in the current session for every scan. -If images were collected during the flow, they will be saved only when the activity finishes. - -Pressing back yield the `RefusalActvity`. Submitting the refusal form will propagate through the activity. -The `AlertActivity` can be launched if an unexpected error occurred during scanning. diff --git a/fingerprint/connect/README.md b/fingerprint/connect/README.md index 4bb01d9c9c..adfe98d7a4 100644 --- a/fingerprint/connect/README.md +++ b/fingerprint/connect/README.md @@ -1,41 +1,50 @@ # Connect Scanner This is the module that handles the setup with the scanner, and any associated issues that might occur. -The desired side-effect of this module is creating a connected instance of `Scanner` in the `ScannerManager` singleton, which can then be used by proceeding modules. +The desired side-effect of this module is creating a connected instance of `Scanner` in the `ScannerManager` singleton, which can then be +used by proceeding modules. -It is a nested nav-graph design using the [Navigation Architecture Component](https://developer.android.com/guide/navigation) to switch between fragments. +It is a nested nav-graph design using the [Navigation Architecture Component](https://developer.android.com/guide/navigation) to switch +between fragments. There is a shared view model for all fragments, `ConnectScannerViewModel`, which handles the setup flow with the scanner. The more complex fragments additionally have their own view models. The primary fragment is `ConnectScannerControllerFragment`. -This fragment acts as a container for either the `ConnectFragment` when it's launched for the first time during the flow. +This fragment acts as a container for the `ConnectFragment` when it's launched for the first time during the flow. During the setup flow several exceptions can be triggered by the `ScannerManager`. Most of these are turned into issue fragments, which represents a particular screen to show to the user for them to deal with. There is a fragment for each issue, and they can be launched from the main fragment during the flow and sometimes launch each other. Several specific case are handled by calling `:feature:alert` module.These are: + - `BLUETOOTH_NOT_SUPPORTED` - we didn't bother to update the UI for this as it's so rare (and inconceivable in production) - `LOW_BATTERY` - this can currently only be triggered by Vero 1, and in practice was never seen, so left as is - `UNEXPECTED_ERROR` - this occurs when an irrecoverable programmatic error has occurred -After connecting is successful or if the connection reached unrecoverable state, the `ConnectScannerControllerFragment` finishes with the appropriate result data. +After connecting is successful or if the connection reached unrecoverable state, the `ConnectScannerControllerFragment` finishes with the +appropriate result data. -Pressing the back button at any time triggers the exit form, and there should be no issue returning to the connection flow if the user changes their mind. +Pressing the back button at any time triggers the exit form, and there should be no issue returning to the connection flow if the user +changes their mind. ### BluetoothOffFragment + This can be launched at the start of the flow when bluetooth is off. The button programmatically turns bluetooth on. This makes use of the `BLUETOOTH_ADMIN` permission, which appears to be granted automatically upon install. -Returns to the `ConnectScannerMainFragment` upon finish. +Returns to the `ConnectFragment` upon finish. ### NfcOffFragment + This can be launched when we want to reach the `NfcPairIssue` but NFC is off on the device. -There is no way to programmatically turn on NFC, so an `Intent` is launched to the settings screen where NFC resides and we check upon resume if NFC is on. +There is no way to programmatically turn on NFC, so an `Intent` is launched to the settings screen where NFC resides and we check upon +resume if NFC is on. Always leads into the `NfcPairFragment` upon finish. ### ScannerOffFragment + This is launched after the user has confirmed the scanner's serial number but it can't be connected to. The default assumption is that it is off, so the user is prompted to turn it on. @@ -44,17 +53,22 @@ A "Try Again" button exists, but does nothing. The user is provided with a "link" in case we are trying to connect to the wrong scanner and got to this screen by accident. Upon successful connection, always directly finishes the activity. -If the user presses the "SPXX is not my scanner" link, this leads to the appropriate pairing fragment, which can be either `NfcOffFragment`, `NfcPairFragment`, or the `SerialEntryFragment`. +If the user presses the "SPXX is not my scanner" link, this leads to the appropriate pairing fragment, which can be either `NfcOffFragment`, +`NfcPairFragment`, or the `SerialEntryFragment`. Can also go to the other error fragments if an issue arises during connection, such as OTA. ### NfcPairFragment -This fragment is launched if the user confirms the wrong scanner is paired, if there are no scanners paired, or if there are multiple scanners paired. + +This fragment is launched if the user confirms the wrong scanner is paired, if there are no scanners paired, or if there are multiple +scanners paired. Additionally, this fragment is launched only on projects that are not using Vero 1 and only on devices that have an NFC adapter. Whilst started, detected NFC chips and reads them for the Simprints MAC address. -Once an NFC chip is detected with a valid MAC address, all other Simprints scanners are programmatically unpaired and the we pair only to the provided scanner. +Once an NFC chip is detected with a valid MAC address, all other Simprints scanners are programmatically unpaired and the we pair only to +the provided scanner. When pairing, the fragment is listening for `BluetoothDevice.ACTION_BOND_STATE_CHANGED` to detect if the bond was successful. -Even if pairing is successful, this action sometimes never activates (suspected bug in Android on some devices that can be fixed by restarting the device). +Even if pairing is successful, this action sometimes never activates (suspected bug in Android on some devices that can be fixed by +restarting the device). For this reason, there is additionally a timeout to check whether the pairing was successful. The pairing will fail if the device was off. In this case, the user is prompted to turn the device on and try again. @@ -64,33 +78,44 @@ If the user is struggling to detect the NFC chip, there is link to the `SerialEn Upon successful pairing, restarts connecting and returns to the `ConnectScannerMainFragment`. ### SerialEntryPairFragment -This fragment is the fallback to `NfcPairFragment`, and is triggered when the user confirms the wrong scanner is paired, if there are no scanners paired, + +This fragment is the fallback to `NfcPairFragment`, and is triggered when the user confirms the wrong scanner is paired, if there are no +scanners paired, or if there are multiple scanners paired, but in projects using Vero 1 or if the phone does not have an NFC adapter. Can additionally be triggered by the `NfcPairFragment` if the user had difficulty detecting the NFC chip. -The user can enter a serial number as a stand-in for the NFC chip in the `NfcPairFragment`, and the consequent pairing, listening for bond state action, retry, and timeout behaviour is identical. +The user can enter a serial number as a stand-in for the NFC chip in the `NfcPairFragment`, and the consequent pairing, listening for bond +state action, retry, and timeout behaviour is identical. Upon successful pairing, restarts connecting and returns to the `ConnectScannerMainFragment`. ### OtaFragment + If an `OtaAvailableException` is thrown during setup, the `OtaFragment` is shown. This fragment executes the OTA procedure, which updates the firmware of the various chips on the scanner. The progress bar tracks the state of the OTA steps. If multiple chips are being updated, they share a portion of the progress bar. The scanner is assumed to be connected and in Root mode upon entry to this fragment. -If the update is successful for all chips, we return to the `ConnectScannerMainFragment`, where the scanner reconnects and the flow proceeds normally. +If the update is successful for all chips, we return to the `ConnectScannerMainFragment`, where the scanner reconnects and the flow proceeds +normally. -If the update for any of the chips fails, then we go to the `OtaRecoveryFragment` if we still are retrying, otherwise we go to the `OtaFailedFragment` if the final attempt failed. +If the update for any of the chips fails, then we go to the `OtaRecoveryFragment` if we still are retrying, otherwise we go to the +`OtaFailedFragment` if the final attempt failed. ### OtaRecoveryFragment + This fragment is entered from the `OtaFragment` if OTA fails and user action is required to reset the scanner. -Depending on the current step that failed during OTA, a message for either a soft (simple turning off then on) or hard (long press of power button for 5+ seconds) reset is shown. +Depending on the current step that failed during OTA, a message for either a soft (simple turning off then on) or hard (long press of power +button for 5+ seconds) reset is shown. The button attempts a reconnect to the scanner. -If the reconnect succeeds, we go back to the `OtaFragment` to re-attempt the remaining OTA updates needed as well as incrementing the retry attempt counter. +If the reconnect succeeds, we go back to the `OtaFragment` to re-attempt the remaining OTA updates needed as well as incrementing the retry +attempt counter. -If the reconnect fails for whatever reason, we assume the scanner is in an irrecoverable state and take the user to the `OtaFailedFragment`. +If the reconnect fails for whatever reason, we assume the scanner is in an irrecoverable state and take the user to the `OtaFailedFragment`. ### OtaFailedFragment -This fragment only displays a message to the user to contact their supervisor as the scanner may be in an irrecoverable state after a failed OTA update attempt. + +This fragment only displays a message to the user to contact their supervisor as the scanner may be in an irrecoverable state after a failed +OTA update attempt. Pressing Continue will exit the entire fingerprint flow failure result. diff --git a/fingerprint/infra/base-bio-sdk/README.md b/fingerprint/infra/base-bio-sdk/README.md index b72bd6e52e..dde40dc75c 100644 --- a/fingerprint/infra/base-bio-sdk/README.md +++ b/fingerprint/infra/base-bio-sdk/README.md @@ -3,10 +3,8 @@ This is a base fingerprint SDK that provides three operations: init, capture fingerprint images/templates , and match images. to implement this SDK, you need to implement the following interfaces: -```SdkInitializer``` - initialize the SDK using the proper initialization params - -```FingerprintImageProvider and FingerprintTemplateProvider``` - capture fingerprint images/templates - -```FingerprintMatcher``` - match fingerprint templates +`SdkInitializer` - initialize the SDK using the proper initialization params +`FingerprintImageProvider` and `FingerprintTemplateProvider` - capture fingerprint images/templates +`FingerprintMatcher` - match fingerprint templates diff --git a/fingerprint/infra/scanner/README.md b/fingerprint/infra/scanner/README.md index 5932db804c..ea47a5efda 100644 --- a/fingerprint/infra/scanner/README.md +++ b/fingerprint/infra/scanner/README.md @@ -8,6 +8,5 @@ It is split into two subpackages depending on which generation of Vero is being ### Bluetooth Component Abstractions -It's important to use the -[Component abstractions](src/main/java/com/simprints/fingerprint/infra/scanner/component/bluetooth) for connecting via -Bluetooth and acquiring a Bluetooth socket so that mocking and replacing of the Bluetooth adapter can occur easily. +It's important to use the[Component abstractions](src/main/java/com/simprints/fingerprint/infra/scanner/component/bluetooth) for connecting +via Bluetooth and acquiring a Bluetooth socket so that mocking and replacing of the Bluetooth adapter can occur easily. diff --git a/fingerprint/infra/scanner/doc/data_pipeline_diagram.png b/fingerprint/infra/scanner/doc/data_pipeline_diagram.png deleted file mode 100644 index 8c1229d46f2585dc4bf1d862421d9c6ac0140e84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80670 zcma&N2{@GP`#&5_85IVhvNT3$wGG4AjTvK|u^Y0DeXL_JW{_QxvR0BP?I~HxzGN$; zP$|1?71>G3`d-uXeE;wNJ$~Q!IDW@5X_ouGmh(KXbNPJEJIR!Q=h}B<->zM|xD52M z=DT)5(7SfATSE7OZ;mQ%73|uzZ%;6e6zs?Fp!mA)l0@qM^G;Gh&MP1&SQ3eqR8SyO zsWNU9vL}t~7bN5F9t^$$zxxHaQ9LN_ZvR}PAg3UAMqc5JoQkEKoFr054h{Y&Dat4* zqU`>;p6u!F|L=fGGIC&m<0N@`NhAimGp6{5Fu?x|6_gW7@dS9ML#6t<+qkk5-CcbGShwhU(E|LyH+m{4895n61w}a-d9(`n z!Gi2TrcwT{VOXno4<>v5vvOZs3tKOwf}Llu-x*vW&05LaMCYFs)7@!7lmP#KE|W*f z$jh_V&AL07Np=6{Yd3c~#TCq|Ac@2Q>HRAva0%sK!GtQ{y-d*o3SK_cGcE=gD^pWL zuol)J{$#M+zXCP|vN*_M_eu1{Sp$cHHdL&~*V?#T#FICaNTE#q+5s1-Iad$@>=whtA zNTeVlg%X4{a|>>;t72q}Qr00U%e%PYT@9>!LhVcl9@hRS zCXyIngt7=ETF8YUb@k{G?3j@5B7#wz**Q}nRGC@XU!#t%cZ#n~zaxMSRc zlvHT`OcR=d4;qaO39+M^dK>AQxzYT+P`*KO7@$nOQ1H*)MZr*+6d>=Wfb>;Spa&{2 z6-?aZeMyR;u2fG9gW~BcuVNOcFK4VmrDBP?@+ds8VhrBI+Rc}265{HrXzOESt?Pl- z^ChBi1a~~ymSV>wyXzVUg3ElA!5>??f=57*8H40Sa3NSSb;to!GaY?H-C(?eahNsJ z-;x+=;1%j=;qMY;9$=_qVXS~e`7k}rOymOfu`$*2rs$A@yzyXO@S+fKD5{@pp*qTLZq~LzW*%g4FICyV+>nV;GA8MRPoWI` z5NjW6SF(|nxpg4L#Vk}26GYL&BV8;lJZU5X!&BEfz<`dz5HLnK1)zUB+W>}(0hMB- zYl{vsKqCV@LxW97HkQa>WlyjaqKA*al?&0_-vXteWE)~k^6(GRF~^&FScX!}{Jl(U zeSLuWTN6#?{DPDSb~LjP-4KEW6VQn`c{61C?l#7#>2;ptYQhQ)&%D*XGt(O zv_=_{=~gZ}6e|liyk#(jX&#F8@iNoFyOX^MBn1k_L{G=W-`mhc-`9d_VHQLoAYGZk zSfn)t@2@X!5?~GX6k>u?0rGcKkPpFN6uqfLPXi_)Fh~!{(AOusQUl~!VhsxN@M9Pg zlngvv8CWW~%fo;|RkkLZfZr_qD3-3FWH~zvysMF?91bs!3iP7{SbC!=G;qlEyn;-S z@{0a+bALN;9K}k(pUjXa5W@VdQMNjPe(s9O6hDGH%9^1NL`9Q44cuJyt#!$kBwKfD zl&d_-5X|7KqGEujkx{{N#&#$o-rbC-Z-OJ}5cFtd9VKIV6;pE$rX|(ghOCeEViE`* zZgMO$GBq+#3dI`}jDy46=*otsU@7`~MhZT9;Ejt9)5q0^>E~%6Z;CRu4GAM#+J;dr z6augYiUD5U0d9Dtyd{;aZ|sRP)K~KKSN1Z~BU#a@Bz?NBfs2Vnum_kxpAj13ZQw~W zL(4IOjFt4vX>>(*7e%J7PJoB03z0?+F$|X1M}`sYi zR4Zjat030N3?L~R7-8fvx?yJElc!mbkB*+QvWk@)mSJItSE26?W zyM;2%vF_f%6g0tH2@~YuO7vCn4-3)p)gvhBppk|Yf;Y~}MZv^c*T>jP$(QI(w{r`T zH^9r$31pO2kWB!_%+}o$ZAms(^0QLFfN+9QVd#XKxRFiV0<4I3p=6|;ie;#mqBo94 z$C?IXRg{r7MpSbZO9g_jtsA)98f*pQZQ&9Sq^F=_Y^)fmr+;m(NLc%kX+G3LwOe^ z6|@cp9gO!kPa?W)v;ta&gODfT3c-FEM!??aE2IhtU)%yLfrMR#RL8G$90p;51L&uM_e=& zw`jLK{4sj+-OpbSpa$Ysyi8BN-&udU6L4#7nvoZj$J7d6Sg4k<=|qPwlzsLb27l`} z>K9gvOma|u3Ie<^w*T^is>DPYOhSJ{3;rPB?EmwX5{>0P91Bg1{vR*x<52xc=qtGb z{{MLw_=XSdw)?;SkPnBaOB`%|MEEabMuQ>Kxc=)8@v+g^6w?EDRmK0yDiQE>UDSVD zJ_5E6tOO%}=|}wk*Z^xwSnmF4Xnb13=ARY6x7X!S%w@M6<$%0GyRwtLP+%rH0eR~^A z$vlE!N2Eouhb>IrVw!sQgd>hi+^1jERslmmzgHI6@Jd8KSsW3wEhAHkgY_>ky0W91IaaGS3?ywz!2r@&z$34Zz7 zz)AKoabSk(u8;GuS9Zz2-`ZHKkTSV}y%JqLCloXN@!{FE`QfWM%|&dcb%;@ZFn*uY!*VE4A$d11Saf^!lU^9#uca9Z`7`9LcF7)ley9J8?_QV!3m{V+B~<8$g;;A1H+~@KhOiJV2Rh~e<@z+|Mj@h z>cGDy=xAs5HXY(9!$6h|wAapQQaE19^cyXsXJ8^>5xns)^hk_DXWKUS0yEybe%(c)~|-n>^-jcYi{UcIn2 zCe~x;F8i+$8v!Gxwi3<=0ri!#ec|+4{;>gj#D+)<1(&X*sCNszab(ws@UDeIGQhCf z+&M?iu{53XvZuz>t$sfH`Kj&m_~TmZQxa+m zwJWnTMK5J|Lm#XjY$9!Mt$jgxb;HAdzQ@lE2D~g2rS~-w)aDdxW}emfwu*+HE=#(L zPc1i+sk+xQ!6#ZAIH4ClU-f;qX}@gng@|AMp7dre<-Xbe=W|6bw@ZgZzYLdsjprv& zqq<(yzYsq19c$wF+yEKcI}rNiT%luK7%Fn}_Z`E}&m4Tz=3Zom{OD<4|23CW^CskL z1Meuaul`*4pnuKWczNB6jmYK6_Iaj~ch93Jb>R|R-jjv8HOD0T@?rYfjqzkz-(H7M z6NWwN$5J@e`YDOr>l9TNE?soCqO<%Eh6a<^k9>GbJ}A(~P6+Hh!YBAyAPUFg}B=btZ;E>YBMws_SKD3{>;6&<|t zPI*Un_V`>qKBDsH2ctqluIhaM4}rXpGj=bEHGcKowM=)N{&0VO%i=@O8R6C68ix`M zY6FfkM%kX2=EsIFEV8l7Z%k$q2?fkc0twkM1uZ z-eKWZKw##Q4<>N88=zN?(Ta;t*gU(b@q0Ax*(c-ZBPd@lupxS~x0LzO$lv)PQ{9b` z8*|DP*FEoq%$BsOinpvxRxZ?U%)Z>&D%yDS!nu)MS$ray3etiD=@Qvt^_$B^Ywj@l zXxZSkFHtW=16z2@$<2FfW=lI}fvtvSdP5GYpCqNd+R(^ckj`T?&DX9FHdeZ{3O(N4 z_yx8w0LTFmoV zW}@(f(N1rX(}GlA@ja(LuA#8Cc|ReCU^DxU-pR5CF(~JaW{t292GadNTHj~0V=MSj zuX@9<+`dIoJKOWwjazGr%{(<5Gx-<9XlpRnx_79-zK=w$xz2!(4=&e4l{m~M%+#-R zs?k51D5HhHMYf#hR-J-NKeG=V4)K2<_T%xxfx5Mk8ElP`&*#b@rp&1Ahw2cY(3=-u zLKsrP3nRm%28DU`W|vnfALhebF2wBSsQ%viHXE6omor=)64ZsuyU(B%%PoAkpB(&*&24No2z?nG=8Kjb0S4;zBpY1U%g%x5Vifsb4|7@ z7CUs(`e|i<%>_^0JLWg)e*4T7H~_P~1VUGQpyj8B`n<;tCZ`6DWE{1F5Vfod}F;i&+!g zZ-~_vOnG>dcJaAri+p^NE{{eJI8yGX9ig{w$dO|&A4a|&Ub&=I{_YN6j_y6zr36xp z%Imt;p!k(a*{aV*vUQU!yxU*V&38%-GNtAZ>mJ&jtToRsXg;)-_)w%(BS|?E*c!>r zRr+8{G^u2_@E9}qk+ZbA%}j66LJKv|T_pAPP?3|CG);o$?3#GuXw+s&O(aNQhnliq zRZG(t{JEaH#q<+J`T|Q=;dkI4;17ZvT3lKpFv=xmxMaa9&!fM7B>62xrXEV4 z%5ATST@zoBs=d!heRoUiOG1QH!BL%nwlr}%@TWDAw;1C|^LKT84SpJbAE=!W3!jx& zTN?^e4)@GzDSJT`XfZkzNX+V)A79AJcxtu48MWa4Nrv5wm3~~j`%Lq~MQsBFN2amV z6&t5>WgB5{VsFW!j320{Po!#XQ~62tL#4+(F>iYN#}OPdt;USw#-`a#ro(y~pe z6jvO#ZCUCQ*x}oIfutiEVWzjavD)i&`TKsSFG~$~H0MJFW8UQ`haxz>kH2bcN{&4z z(YF+OwMnA*ll9qt5S-`L|77r@Yu9+j58uJgZnUmUF3zTnAnaDSF=>ec=*7jAJe|a! z3~_N|1_Do6W#l6hnOA_RRjqYBDXRAdc>;Bz-MY{Y&6XNF{CcBqt%9*&oY&7-tXpi_ zKYzqgnOe$lvZTYydZ0G(a{wNAej(EcDcM1!{t8S??lEfs4#`Fs2gHvJW+Ok-YKBlbWR zP4M$+-^HJ!Ux40|h3bEplMaS!g(L6=t8$NBc*SzGQ#cQyba zG{=FZ^S6sh&B!>}8|@cGO6HtP-`&eLkagKlRw>DoFjfrYXmyR3H|ptn-leG~uKYSP z=+8xiMom&*>7qN^n_e5K4@(!NikS6dAD?#eqp7dHcc`rGci~7rh2<8!oS5IIQybeW zFm^gZr!{VWcvIZ&zyljCYRN&IA>;M0xOw$SJqQ|g$GPM5>&1OKL)w!KIq_j7N>`f@ zXD67s+ zwa+W15~$dWd6(z4p)fRTneru(SHiwC4f~S#v-9VOGNZw4@yOV@_%<{1t&+QhwgWER zz}3w|_QvmEyMkpQT;lP38=W#qK8W<)G3jduk8(3OObxw5XKNlRc6BRbfx^*L_eGB}pJ5{ymB_(4|C8;P#~ydHkT^t$Q988dbJ zuQdzxL75D}sFDN&4X5{K%YFawq{p?ZO|<=%{2|TXiyb;foBZ=&HD8rDIT{H!zPv{a z0U0su;}YK;pAEmhRIAnS0aGvbtm8{yue7a< zxarW@KFMS)LEk>{{RV#(f1cj8~EX4=^^ zmP#A$4;i@#&Q$5FjWfXlQ?oYWUTOM`o#Gc4IUP(I8vWb}x!QDZU*n$8QN~NWeM54b zY&RoNPQJOLAt=*x?DZGUy2h(QCDE@0?&#L8|0%t&|9O^B9PZHgp1BydF~p06qV!`| z?#e<>9c!A{f5iWHCD(-EWmpr0FO^F$j*;GwV<00YF{>Dz*eGv|`Li#jP3I6S)2@Cq z{d=^j7$yeF<{WWY4Z@AtOWM`)*(Tyn_M=-+ouo>G$+i`f8Ql;dxP8XeHLF}7MyYf{aW_J-s{m7D%v{m}u zdts#7=f$4sT^LRxm!NXQ8IO`zy~xNe_M>BQ*gW7%JJgaDA+Y*m6CQ7ypuLV#NfEpf zdDn}axQ-}Nd%y2@xwAgj7|U(r?NY!i<=m+BXMFbSKIidd{G0m(*1MU zsaz8I7f$yuIZA$~2%u}zxFH-^N?xG)8Z#1+xN@K+Q#PA=HRH!vfO;2iD(^>FA+rnq zbH!dSqA@exQ+#3ntm4Q*JMqd?uEBNA@1shs;p}X2a%Oo;Nk3YH*ojgYI`m7vpVbrV zkOPd+2K0Mm-7#WHM&$>Y&aZV7brY%RZ-=C*@J>bolg-g#sq(#ll1Kt`p}-1+yPaDA z#kGxvLT+kPGT%jq%6|RE?-5JF=o}wSMmkL{TfuOPyL2`aG3i@4@)hKQM3ZQqs3QSV zdk2QFies{j{ylv2AcdIW%En*ql;pNnTpi;Gu^Wftw0O^NUiI+-q^ zSaD621-wQ_kEks*rp?5`3`cMrqQPk$J`;XM!tSmaby()lPoXTN3d6^KMc-zDl~WRjW`A{T$7)1+*Fot_&zA~vNXxGYGXTq(^1GD4G=Idu#(*x5zK(vS;>Vqt9&Rn4@j&nCwkZa za@pz4vdaZFkW#F6=7rNSJ?aPVcY66^>B z0-u5?GNJ3xy)|2#@Xr9mpTECt`CBYnGFw;M0lHC>H<}`qB#f{)0*)O1N+aH=9WMbjn0J7r< z`@KBE=rUG>`{Ry#IPG6qf~eeM>@iXR#qDm+T6^8$xSV1ya8Y{_WFLvO zoKWpFOYmf!!WD`*ohJzbHPFfYX zB@3P@eRK7c=k&$uOVDF@=T{d6te;q)IST+Tn)K9hfM%J(05E>A`Fa*%N+Vt9Z2=ZI z)b{<2oUyRUrjs?fW95euUKLlpa&!q{sWX8U{=>eVH_-s(WIQput4H|p9tSY9SRL(!T<1#` znr9!cVKpgFWPqZu;AFI+uy8jBRa{KT`^uQqz#ZIvpYGc+kkquxftoeV@gcuv`%!`8 zSRw9D&m7be9!&=Avdn@)SWb)W&^-~9*+P;?)2I7j0&oP=CH{JjeL1@f#$I1K?3yV} zEN8BK>o2f+vahDm+o6%~5vk1fkA5WL>B;^z+!eJ0Kxcx zr7{C{MxbS(-KWn*P61!vTz&4|ajiW^BIDX^LNeeJwdueL+qm%#IM!qeEONmjR@Dhi zn+91NKcvnm_UkKdd~@xLk3)5Ex-uS)H@_^%nxF}gqIdri8sHO{xs>%~8*k$3_@b(8 zKuTM7jOuCDx@=iPK2ZsgXYEiw2ROwS@29cg+S`1rYuDicDYsJ|0Ad4N3)le!o_s0J z?k{8dFCUwZh!({tc3d|t?N0utTCi{&m=L3vC zNwDr%jEf$T{`X`e7K!Wz$s+2Ne*$LvO6UNEOu7%fqCa|Bd88N=)cpW=kqmCL-ghHf z>i!-Lb?=+3EkMqL0b&lHRI&srn1q<~q4OByoIZo)h4CqA!npEk>3oT^$3k-L`MU2Z zW(9}%v1U!=W~(FKL4N-67$}s?uWvBeYPRpiIq3e?ymVS=o6c1}RRxZt_fP3hC4TVYC@Q*fWpBXS9zYGUbo}p2 z9~((1fc&Qy0DdN27jxmjNdRA~vf%3#;J5Mh`cK6E-y6W4Z5lBzaNNYHN`SBctpytD z-PkDE7s17+%B~fkKMO=|%Kb!D5?CvZ-Nqs0>fFnnZTdTdL=fJNsH=0k9Wk zw$DBK+of;WpZo*BulMvpKjj*L=*~UJ@jtoB5dDXPPdgHG3CqnX`nd7)dk;QUw>=4Z zU?2IH69*(%hIyY~{}}BG-2>Rm8~}_f01LEo^n#3iWjDaQJpsW}$8Q?>=c`cYTQJ2K zHWM$#Y)L_XyIyHkyS+a1ItqBu6NQ|Fcya~i*~Tj&r_G7qV26X{eJ;s&z6kA9z1SBC zU{;CL5giUu%Yl4WK@CX$4DHv53-KwY12}@3WWK~hM#$1Z5a;xHuXF!B{YjM{ zkM7>=Hhu14;sYRS)?sB412f(Ear`oUBaz>8L(4(&J4jF&gY@##-mySrPLVhco^n7* zk&)@H0AwZ{VgjaP?mhl!S*d%+fM3P%x6ZjQFJFqET&w`Nb{~+g&x_0Ji%?r|=*s+u z2c7|;BfK|epQmbUE;gT6R&>Dw*wqi9*UuahWjA-KzUTSG?d76j+L+&SzpE1$*N;2Z z*ZO=1NHB}WKs{T1r~IW4HRwCBZus&`_<}}ny`<5#zN0A5!9 z*y5pT(E-Rc01T6=XkVY6t?(}g^jzqH{9z~`E3nh&2C95O5c$XgUj}r>ZUUt8Q;OC$ zL)!9Yl-Dsi0eUosn2dn5nTgM5 z141wy>`*bGNzTSW_AZ`n+$8+P`KS{|BNk)0L{z%pTsh%7xzo@b_cEO$z8qdUT7^;L zHK>i%M}jE379M{KFlEgP`4(HOEu{sq+X{d!O-9sWY+~Y5y16K$s>jkLsJajKvf@$n ze$J#UM$dj5^kkauO%9Gce%ac^*t4TifJvoq|M@P&q`_t21V$crJl*n%^*hdkB?ykn zs@JZm8XiCbrCCmq{Y(>>Ol)_I5bkN4&hkfqVh-57@G9xo2V~eAKzoj1457^Y(Z`Kd z(1VW0_r1h7Xh&zOGfUM{u#W(97-81;Ry1_T>{v8o3{*JsO_uA$@levhAuV)SF$v8a2E^EY%+xH6!049j(lh?|Xz{isv5r>M?nBpZ04sAp7T3!I zj+QX8jKqowFwr)Hv4nW-$t3B#Vi||(%GyJT?D6@qk(fjsZf=?z+${|m*PTkrZ)Ina_wirLe}-DLJCwfm4^ATb5_(_v<+3!!+Aq*P0LF^Z9?ZhgpVOQ@a<&STk0qV{PJ!vSN?8uSokS&B92 zB{m-OQ(#cai`YSH6T>()x_8xIqv8YC?xQI72m%LroWJb}ON(zFTyy*)8GNtR#8Z-} zo%vzXc3S^$5RPd9fj&Lt?<8bcTTR;-r|9fN9b;*{>Go~wgX%GMW!_bVeU@1GAVl%R zyT2GNSoR$Q?S)LQlyHSQ&0l@4AW!vw{=g(hDyTAr7X*mBe>+fE(rD!E-nch<{+{!Zct7>UhTW)$-_#rh_lp2jX=(qlJnlLpQ%5tk! ztUfAb`;bqk`MX=hJG4Wp5bULA4#)kY1{8GaTn5+0v0A;Se)i{9Kb8CTD+sI6eO1(+ zI3{Vc!9dBhEAF<`6@oVZg%5|P6MsV6%0Emzc>a%~QPQ{9l1?LdW5&zh10p!Kf6>Ay)a*an67kfK)_Xo34rQhPG&))2^3Ki7qa&J0!*4O5%B zC=O?(6Oy%SnX@168(l7RSEJ8*#TP$fO~^VZvn4%OX)JHWJ*Kz$p|mhjIJ#Pvb+RaY zq`I&&D{ul?&K>l|6@&JRnp?e~zeHHsKUgcEVwg^GbasrR9L`%EGC0fGJb zzL5}MEN^KiMN61|uy+N>Heu>IFqi#KW-N2N30C{|@Q-~=`0WHQF*yFkzJ6;ghvV?y zQsgwOAtw%!0q&QrwgVx1X#Vi>cpLj@n*E&@Rj(@pC+yqyVep{u0`tKqL>ICNN~312 zUj^tr?Dy+L%qy>O3&gwwT6BZ44j-I78 zGwRznMsRIS5dfz-Mv;~O@!uqL$i`SYa{uwm_6G16D^CL6PL-_|4SE_q0l8}~lk!Md z_vZFP)l|RX>avbi3^e&7I8{G@f4kRd?D<<8{wXVt$Hej~6J8A3MyliOCt6cw3oaL~ z(+4QhGoWR3b#73Zr_fY@wJMn3UwA>@^>yE5d#2w=Ewdbm!fUtL!p?tdH|3`oPQP{S zrzGq^$t>;ZUk2LiVN847g5F!9Ek1r+fVu#Iq09g6!STBwN~bDmfOPWnb5A0xl?sqC zX>~GxOG4PxP}TDlYp?%rN2;I*Z{MYcivp*7|1IW)W_3@zH zwZ*F`qH22Aa#dvjE(T=It(2P?%068yvOJ=1bv=kZQA9d7I0KYPk@QbiHdX7a4lP!* zNp84Yo(QX3Wp&}p7ya$?@MVznp9Kw`e^bl%ckwk?$3frcmo>J2ew>e}m;1l7?wWb9 z16SqY;LNMCP9vZ^_&$W=`9D1)<)F1<;PJye@3oQeqBD-QeV~VIqH_!6od>%3hK$jK zzh!t6#<@L1@^h)X(G#ba^<0PKw43xudr>@F+KW@43v=FOBU_m+pFt|eQzmFFFx}}2 z+7xEuxxb0Cx`3Y&9xEYC8-S({E{;B;fHt=I4xIzdxLxTJORYyMKpgM8bQtLi@_zc8 z6A%0DXE}O*iQ1{-mttkAIZ8g)#!hYw1$FhubMoIj8v<~Oxn$XTDu0mu(#;$gPYWx%YHOD2W>ZdxA+Verw>L01-Rb@gQh- zD+J_guI8V~%tCVW`Dc2N3!reUobaz*(yNdhM1Q~O>X{pIZDsGDA5V&gmJ$Uw!(Siv zR4Iv7-ODZF<^JYMiBr_pr|sVpJl|iHfM&m%^+nj&%s&1|EO+Ot=`+N`^R)fv<3*LbAk3*o_OImSS>|FSVL@U-a;aagXU zgpuTEk_b`r&%Lqji}<}R93w{(MdF{yasHmVn>z03GuD(KbJKmQJD(2fxPgApTXX6v zeVN+{LXO3t-=I6U25hq*w6z3WI2u{WuRmWo!7WXL)yIyOyb?UCQUq(^!gALdJQ7OB z{bN_y=wo*;A5kg=O&uq&lE%AdESwmjy#-bsOnbc(hbgFUCGk&LwaB^R$DnVBDDlV} z2R{U)4RO7R`0~cLRs_K#57`BpF+iRie0Z?mY7cL$96vS%V4XE`{Ptu3AkIO!YEQzD zP-Nq~BWK6=^R-Sy)`6zS0rCY=kEpHHto;<*V&|9|nJDS16YU@RG@?2Cw>K67QZ?5f z@oUXj&&aG<@u%-@j9&q{_H-@2tu+v|mG}rZ!uoDjgOpFY8{`jJInV_Zkn`S{XAU*h zse9wY^zOU)h;13QrvVLgVnIzD>>RPs{4)#}xb|IPS+(7hjQQS%c5#1;pTjwMD63OROV~(`67${2y}dEO4t=y_JG=@ z^ZY8XvoPaDD0B6b&5Bjs58z8-F}3`UEFWiJtd5VUf=)MIm2R#`MNihpGwRG zyu9OK(?2hpSHQSZI{1bK11vZf&{_TPeCT~t)1G@O-a{|9zhZd)TKwO{b{P*JOn$yu zX0Ifm5c8INQisM4nVbrc-E55-t$9ui=ZvD&ciW2a!vSIYD$xV=ojW@S-;+_J3VLNeee-bMZea1PpmE+U& zzP5h$WX(h;;Z(1E&}DwrAD^CN4JXfdBVa7)u&a2P`Ib7Ke}8xtwDqIKM?po+$`)*2 zMsCdlJfPzHt*8-qKIy$z3thWZXHHiX`s52?xd*yFmsVy(F1Je)p0WZZECvn(x`5t2 zSc`h|tW?V*{%IOaG(YZ7=0zwr8wAngsgcp^&$7tTdrvh#7%y1@4!uTb6}WFd76x?= zl&a<5^TGuk{;+T_;Os}Zz!;4<+kxx7``v~Izl`}@LS{`e*ehtn&ycJ3F+XO(1-^;CEL4Mm{ z`nlSb&Y;$hVstlhO+Two86f!6wWc7FzBq7VI+&%eHXgRRsFh~4cUjvr8gjfYd<|7g z4iY$$nPWXufJi$B4wT^^llawYyC3voY1dj&^f5`p4{`u%s_MnL@{*C*KS>& zvMk52cD+=wx$QZS7=vEF@J^!%R%0~_+Kt*?Ia;$ZUsDL4o1xDXRtyL^MT`yU37sa5 zVp9Mc(g&VSx!LybiH6@^3xMgJVEA2e`0D?*&(FQT)LoC2Tm4IrMl`G!W|15Ao{sw_ z#+*ziKpM`n2kuIb%L=KD^(vu#H5B@QVFGYAx47};*?_vTEg-`AH3sn}?ac`~XaH9& z;;EHqwMyPgXIcFv;&YmK`I~Cs?2wtBg;(3LL}d$epb< z&^-d;2)z^iT_t@YhC_@xa;RP8Iq>z>5%9z_jA-B)MR5oYp#^YTdfU?M^Dfo&xJ5zY zWS@JM!_9|D(zI^W59|M7_B1t*V}4p7lsyG}8t$ko?d;uWGVBQ~T$z0Nf?Z~GG&pDS zTo^(pz-rVcE+Oq7@(M^1ZcfGTtwq3;VW$?~z#$1#0c_ftilGmZzp!t;2Fv}-+4U$0 zjuElR?cX5Wuu{%qfaCSoGFN;-3f7q^0a_>$&F&it2tA7(jU@p*^b6P(fJ8&Y&m^Ay z2aSWUhwbboUy1E~9nmlFr-I=bUh{koSQ8zz%%c|whJamhn3vJ?x@Ym5iiuqc?rN1d zDBHbXwzk{qCCK}#hscvZ-@HhBlw0V|=_D?j4Xh@Kb*L}Xqc?68fm6+!@L*a3pAwv; zS63?#aoKss3S)Zx0NmI=;816Y{ES-&K_?v(BEbP4X8EVRb-Q}C35p!s$YI{ zics00I{_MFgKDZ_Qv5*99yxwDcj=CsNm;O(#`(S4W*Qf=+gKoF{$be=Kw`p&fE(pn z`@R0D25bqzakjDoJTdmQD-5`#VUgIyj{u52>6#wLqo5J?lRA<&eej#w%~VhCavb+F zWzc>r9Xu-DaRDyCo45~9KxT)hqt}p5vdz)1`*17$jqwp8lO?0cdZc}SaD5KAvu)?y zv7dN&6ZZmta96(b&%8VEsn(OXQ$ghxdQ==5J`CXEWNZqkGb(fYpyBt^RysDAAOr0b zX6M--&x_@Y=jXfpyjht1cyiu*;KgHB8~nLPjX{60Eyvu087V>B=+}>Sz!L(&t^5}^ zfK%)TdB?XVO7_}lH-;^#~}X%Buzr}$V;8WkDbeqPIQ4&v{9~$ ziAyc~#7dapMWJWr8Et`EtG$D=FE__Kn|A{olRL1gjP)!X=xKksANMMAH}CER3w2_< z5G#kT0q$t-UM7Lo3U=C&Ffs0+vn{U~3D>4ZK9%Mm*D#)!YG{zFPFX*t=0DFqNVgOB zJcPmfraU)(_BaT4I}PLx$0y&L4PJy!&bS|+{6B@{j>PS?qNn0QkY#t9L=lGdXiUt* z<7!3X);B?k2Y9LYoYF1z5y3006e^8Ws{%k{0NK5p8#)igh#yE$wX%~Ea=892e7Wh1R3tOhle+wJEQtZqV{!G92MwBe$F<|kVZ9E@X#I39z5>o&r02E`~%Y^P)lAY{&aIR z^X=j_+>|MAj2x?_GW_D_Q*#l@cA>oY|Tk2nE!TI$E~JdyZ*z6Tg_Tl8x2 zbI$J>#W(Fm57uJ(V#OMd-9m7LNK~f@$HN2#c%i4jNg#{%r>&j>Cn1e&5t;#q4;r#( za05@5csj=IR(jOVR>dG^@*ZJM;lStLjnIQ3QkhurB+GkxyJXhAcU8ZwZ~E>x{GeBV z@A;FxgN%>Y_z5bFpVBGkrcqZWGr-dscdni}``IBjx2CPX=OOWE7Bn;V`z|rIX@8>| zkNnzwL*FzVC9=~jCrgj{*kqUISGL8Z#R%;Ymj>WCGrdixGzf$4%h(1D)pAV+ZO>!c z-UIaIEZ}xZaO(8)j7EU99*rm-l&S~(G97pVHTm6Vb&rc@Pk|WVEunXbYM{ld3nS?3 zy|G?yJsMTZZ}lka*u#aFJN5hBPs~@P`w_WQJ!d`_7J|s(-wUW!fCB=Vh!;j;mu0=_)L_ZT%#s3yy?us5-z=ig0yVygh6(F{Ovtg#}Zr^yY)z|){2@C8|M zydPF~;8sN40o{wl;gd1ZzDD2vQs3E0B2bRXpDPJBUgg`6Dn+RcY`C-jh~Uw$;PIi5 z(8M+5pg(w|r(=Go*D?IvR`RKx|M!`izA<*5-GYx8Xw%wTT7aH=S3W*@#6Aai_q%B0 zlGC}>tLfO~e;%fx=fDjnSws&STo+dcx;@TlFBMeoY;O$vA3B&}jWA4;%Xqa2I9JNC zn{IeH(4NQE#QpNFQ^W~r9?1UoWSQzVKmH#{A|*Y42D)NJQn|Y_<0jLx9O&5_Z+JSu z^IjjAZ{Fr8cgfWMcB#p_yh4^=Z7pf zQ~Nq-Vt{`1I$tRZ{0@V9ZvKXE$9Fcr81 z5b1I8De)e~{Cg6LW*Fy-M|VIj<_8{zqwf!gq%sg3cR2EnlvHtbs;?MVj7sgW@2?`2 z#1&1Ktd40kB1F^Uwo9My*{@aKB5|8q$a+`>XxSe;P?bE$ey{|xlrX_b(H4P=oTvK( zuvA9_X}itTMWkmrnt&yel#o)dd|6mt0fsc23_TLl?5vKNFZPsj@Z3p zLWwYbDPw94y;y?7PeKdA&YdLdx`IzrV-H}5a5f=fcbyZxny!meX90jEQyZJB0i*K; zW@|;&?_Xemn${lE>1L=vzxp;L*w_P7><&S-RD!+(ce;G;8TLJ(YX{H9b^b=ib&yY$ zrr7hQ`F*|k^w}wZ_U&bFSC_DbFO}We6R{M#OU{2tB^(?v3qCX{B6A^{2B2QmL znJ|SFv!)Lk=!G#caK;D|Fk)PY}Ydi(OgqW1eA(8-F=SUQymQQC*bwJQxaSr_I>aP7`BCDH$1s-?_jeE3Rt^sMe&$Y) zGW@eJfVcJs%HWS4>c6OTpfqhaIlM6Gq5(+WZQ+)p%H4#s?eA&cM0Rx0+uyo&jKyqczKpxumTCQp|rRtNgl3@>Hp#22Ph6zi`SGh z?3@yYPX9(^m!1cjfWqT344MkjLn}*TSn-cgMP7ELUz5WqSBjDn;=^jF{9+T|WUC@A zFm0$-Z{Sr3%XFls)SXS4!+|F!^;tU0#d+MK@LW$&sGK@v{Vl>Pr%&{0>vYb)qf@V^*_a z_KD$RsGatXJP2n=x3br$=G~8F*Tr^`C3*$+r##&91T{vD%*l$rV*7(q7^I(nb@S{K zW`gkXDzR`Z7IP)vW22XaEbvEo&auV=j2_zC^U{~N_gT}I$0g#8p^7zai)(c zoSBI7S9IwYIeBX7qB^G?#uh1TK%#J$HQ8mK1;A-)T|pFnCOq3>k1p1DFp0s8968w< zC$)2P70^BNkjTr=Q+fG!QA6&i0~{vYlxF4-=ta7uIspRHrXR@6xnmsN`^k6tKY*^;D>nf65ll%QdBbTv;50HRa07b`TYufE&d zg4%7`UrFybmU;#>i{r`|k>fO+4ThxXH)rwWf(hgT_I@~F?Dak-t_UmDi@9y@4NtfI8p5u4#i!Z1^J1uRO%%% zP6Zdaa=pL>H}5hRIF2RsONM6=e|~gpm`?md*+d9>5rjvycv!AC8`aW{%3yKwedtEv z*+A*=^J=Vx2P;cL_pmD;1`N3w{d*~v$kW1rTGIcRg<5oOzKQacDv)q&zW~F0zOaj$L>)zYzBQgp9#m-4@&BD8 z-zHDw@z*o|e63fz>b)SK30d!-1-JT4ne! z<`d!6Vr7d)RDx(?FOVjindQlYlgaw>soM+#35f<~w({YH z&s%A#nBa`SAE&t-%pcKTC1I8OKlBwL1{pgri!-}rsCn+sqY6QC!4aT3BwS0@+o5ZL z^%Li%hFzpIUGxh=NDds~s79$SWh+S6?2=b6`BKB4acUa~aJn+*9404Y_83eLf(X`y zMKo-2AB(6BT7Vp3f##nMZqz|P7o|mA*!VpY@3q%C@uws_q$VrwP(->~)nn+?(~Ay2E)oX zOFOl+oI*}HGiBC=zGi9w!m_YFz87FY;;8fnXmhd>)aMr%Ca?&`hS%?ULr|K4>$_nF z$+o}opmw3O{P}as_pwh%lOr+yVbYZBFdzMf#xap@T8KhDKkML@IEf)eawTg&F zfg-yy|7aUyqnF2?J7uzQRVIGNIn}b@mAjVoTGm6yNk-Xe9t|_c&EtSZHUfPq55$YX z^zS^cf8|}Urdb(~hAW^M8)`9BnMy2dzY-xT7x|1Dl|n4^d~KRL?q|SGu3AfYzEeGD{}obgjUS+XSajXa?lC9o)<}#Zja39`j=hZ_&Gr7!)e@I- zO1Pf%)pscbOO*;xM5Me3qs?ElWhQJtsrw`$j@3U)j}|q4m|uaNE3UHSz*m>mJp=>v zKI5u!MU7hWN#2<{J)F(*8NV;@Uwi^c_D=4RZ{_m;g-s4aKIZ z)5T;}>VTIr!Fo-8)O#8a77?9D!|)F^Mv6&P+kv$VllZRiH*(}wpS)`+hOFg4=YCSj zdwo%oe&!SQmo>{bPVfSZjY_zlNu?94+HnZa-4F=iM86BMplu~TrSM)p_hf#XAB z;TZPTZ!)xDHE6_UT=xN!3Z9F3>mY+Y34~b(F0*APj7jKZvoiO^D@WqkNFE!2Ynz21 zhi1;p`;jzR3lm%KnTi;>@S)01)Qcs;EL5RC=~oLiFuv|=-+G-TOmsyZQ_EFMl~p~an*T85aY%U z9@}M)&5k7SVduGgnW4)sfFL9ZsjwiB^&)?jK*NTIZwR^UD>?bZ-$^3;Q2`G-g;2iU znnNAE1LWxaryR9Rk$u5sPjkx~8B@&(r#NNU@7qCR3aVK1uwVoqP#4LVqG(EeG#BTuJgzM`rE!lTX;%BOX8IU?95KfmQd78?7%7CP#l=gq92?%Nc#X}4On|EeOWBmD2bCCyVRPlFPq>+)&r|y-{yulY#bjtP z142PefvwUnwC?ar^SpoZ*S}sPRo%>jP{k|y+gQpt&;v4B-V(+%3Yk;P9{UN)5HF}6Er<^jH|&F5-uGMvsqsvj_OnI&rWWBOjmdPS903#lBm`;9+F!bY0iG7aURVzrmjvbilCoSN=3Nz4usIDu?-*>5uI8=| z2ZUTGuNhI|T4YJ!*|qQ{&EJ?_Ps&RCI0PbK2V7Cn}3V%2aqqOL#-$UHvL^rtSS@t zyG*U1Gyb9(LiY5?Zbq6Jviy;%Lw8>(qs{R^smnlaA?s6LJ6WlEe)!M%Nx>Iy$|?kJ zcYdkjS6aiF1NhZ}#qsoNLBtvPZwX&3w<&t=Cx;~qzd%qAr#2@_m=;4j59xjFJ>Y!Z z|0MGacDBGgykJrJ%6|!F;?B#Cy?mfCn5uE5lv_<0{Ar#X|IyGFj~Klm*i2I2)T#$6 zFXUM`JJ3JN0-T9|boLD0I78~BIoNW9*c4Dy@ZE=-934IEyh#e}OHVLMto zz^LfrbX&Cit4EFv{!~`W=!nD~2gDl3Y;ta>B9GNpCY+!X@_UL2!4V|ibh%W|!{l9|?YNh0gpI2K2m&GwDYbZ*YDa7@Of)Ra zJ6ISBv+e+V+Bk23@;{$GhcP-0pMC)hdQXshoO^qF)H>#-AeTDnrADk>Z%UsiRV?;B zcdPdOWfIzD_1%3z*q9$NrT@^JkgrMbqrXb9eqf%3aLs&Yc}_rw3`n3=ma-eaX!>(N zJpApS&3FC^@31aA0PS6(4vr`O<(Fb*sQ-N~jwQV1iiUrig;dK@Z&}BOY@;8GMZUsx z`-#?%86_f2Z>7YU9ntNcaTof}FG$!;8TeG;*=dsc&+b<$l8Zc>A`VZ`CHgzTNj-%S zNsLR_b9AWQcI2g~=jQ8toskIz?m+b?YIGMFq|=aG2}ze;p>H3G&w%#Is4O2P4_fkf zZEw#!HbrRcH1ZAtdCh?tUOT}<&SfDH3?K|I|8J&Kv#edI!yjXH0?G<|eX_sgq-xs1 zvvIdXbS8kgO>G@k2VKlI^B~t4+pp>WZLH30k~~RaiWRbDX%ZJh7VL+mT;s2g1Z2U; zGsS%EK=;h^dyKG*Mz7R^(bIjAVlhOI_n%Wa46W~jwK3aWQ{)GDV4_sF31r-A2=rwtUXk91f|56urUuY^k@}Ff5C1U-BP5);b>&=H= zxS)=xP?~o={=Tc{25jT!t1dPSi3o{SQG(?A11NHJnuSri+RR6?w8!D{E*%qG3ootz z=OO70o6|bE?zJ{t0w$^10rUxDJ6b{?XBG9=v!=T&uoE^!^sBMV(1GL92O}WhwIr5{ z3MF2pcj|fp9iYe8d>t+S^JpM243N_(K=?__f^bM#-FuZdkrzYix_a>tfhorkflJf& zDPIK?koD+a_&)UiScVc@jdAIRiP-t}{%3 z%iaxRkY^b|1)mF6hqJV`@30~1|VnP|aBBe8M4;N+LKHdTD$KQFJ@n|_!JubGnv zJFWzVg|ByO!PumbRrVPAEa2{?RKu+08&B^m&EAH{HuH$@6PYOzk#B%=-38#JmI8nBuSnxMXul8xsL|fw zc&%UMk-~vp6W6w`egS0Zj>75(#Hj{h{Eb6vO@%}=8of}~RBVzgTNAPw7(nWZ z(Kp2=^P72y7P$Spesr+oa0j{=$J%W^r31Z*jD1(Nwf&<$B5I$8x!jwcaJ%@oGD{r? zA?-r!!JcyXLi~4jVq7%0lsYv}Ka$;lv4v?Aq-Ylqwu)&;Sh+7yI7Sd^AyiN42(PpT zjM(78zHS@)O^CAC8DF;X3${ShmM7R)7^VHp%y*+_~rCef5R1TsTc%7 z6N(Z9+N(8CJ-Wo=YqwIQ3=8veXTk{Bl+`m90tJXz8Ts>XyFFsG^Pi=^BKWRSfc}xd z<-*B&|F84mv4$d7-$JyQA$g=4cv>O-d)zwc^P@6lfOYyhUGqcFm!0HxwLZ^$tYH6Fxhfzb)*1#BVL)eV}CmU4(< z#WB;f=*$-8lb+8O?yC3l1NX^)gzHFj(XE2JA?iJu|Ae>o-Ag7k>8gI~plL2RGI=*F zJabj1Bi@}FK83JZw$+8ODs_m|*v|d(xH~!@IS^DfOQ9JJicRduYR9KKI@hGLlZ9Rf z!Rd4NzplW*d&vBl>?x7hPt-}YmdpE<=GeS5gE{LjAieZ@-_7yi9q>=5Q2bcNE` zDBJHUQMAx4SSlgLa(!Y*Q&rbTdeu(dNVhuqG`P;uuiu~sSgObGnF3b7-eoxhXGKo^ zD}!HnzUkHKl+s9QfORfJaHLV)-%M;OOBZk=%S63a^kv1|y*#^P4y|E^D zwRNUo_lc)*|0C2u3=<4L*Z59F@YmyoMO>Sf;W-=DL{=2T;|5qRb+|XR-?d%^UC%_w z`bDHmkpWTAi#-UqiFb}2o&1~m;+`SSkn8z6aPEgesW|nt&kEu0KS>=06=b5?=retj z6GP=b4YWkljSCJj%5HB9@-z=<`i3#e!(o&C^-|$kKDqrZM;7=GURpal732Eid1QI= zkWbv(vU_8@$wZ92zRby*g)^>99EaL6J^9)qp|n@0V=%u7wfs;QRc!qE?v~I<8C)l* zn-<#c0}wkDaCHoX$G?uj|Df{tld=xBM53XK+HeXmYmMVHk{A4evn`t8?Aa1I; zO#ZjUBQYG98gCSvi$Wtca&;R&{sj8Oyzx2m;s(+14*oG!}xc41#2!QJ%a1>R|S9>>eQEyy}z|1 zxbnN3o#@aG*t_2wjiu2nOCh}<7AqH*)3C2%_b9)J7O&;Rc_OZ--}Xc)k~0=Vjcs&# znVi%lv{>hK1`QKrCbo!e3ia0iasf6l_(c=@I?{a&CYR4uZ_EzPo3_TeY_5r*H4jdJZSH|CcgD}IDW+)WQ=APbq z3`Cv2MBmNN07Zah<+HiM+G;V3bMsiLT<8IJhnv3(O~0W+%pJ5@HDa5fVl``-x%nG_ z@9%sGwfxkfB{*l7TaAt7=WTjYPVtSf9pK>S(cw##4wTcZ<6Kk`9Tt@U>8^6t-q`ke zC?*{nj$qO_HHe7mVDC>Pr0x>k2U(-PVk|j-kt+x5?0XI8FJ71xh|XJT zY@FV$zmzUmp(8aN30#!OE8|01+>T`c&N7ciU&trMHI5Y`9yi;$DZzAy0*Y{sdGqy+ zKF-6`u$E`Qk4v{ZP^So<{?pn~iG5iL@Z6Ru# z)q|JpS_kibmy{*jh{Y`W?*H}t%5gR;oFSiKV%bwDOfSjnwsk$0zB&GpKF1X}&nlPa zZBl+OgO9M!%_4xe@cOiy$_xX*m6Rf8Pggj%9~JJW4>K%rt7s3ErQTG}MG??-OkKv! zp%G(D!IwLJisYcEV(<4Mg6el!?NX|HbA;ps>T*J@HhQ`#x^3iT-Px%Yf`~cFTQA)- zj^^C#kLh2&o5Vn6P>@`oR0ngxJuV#ZY;(bW&>>Y%Eoo%GnjDAg!j^!)4y)T);hxO;xGtn%eD zuxMrCmWaZ{O5(--`< z+&?8(M6WH4NX&|SG1Mj|y}8vIeCM(o5KG2-!!K|r3VnaRM`UoV&#cLzbA3`sr3kXf zkDLbOlcp;3d)9V>rLWp-GzCkU67@`AOAIyDx)BlBF?dGd@&4SMr5kd5ik@Su_CuE7 z3c)0G!zUQWf0-By%!S(U6dvINW71Lk9O7%1s_`{s*1bHsY{I88=84%i1)W4z>Ka(4 z=;Z=sG>y!3HvPmnWt}66ULqI{Nk+ z@iB43sPA5(6jSKYT>r}CGEF?`a^!sdzoCCW)Z*%`vLxqS#h=mM-$5j>^7oUz%q8Xb z?zA8|rI>8WE4x#LIfecD(w)_KxV zH+q*{OG9*mP`J8zMu*Chwy`Zu`jN2zEjBxe6|(Dy%!ho$evbU|o1Lk(LKbLkGQbq(p#{0X7Szm+jS z?f8?ajFvyK)u)%%a3a=sAClxmJ}(~oq%XX-o89>;Jh)%T;h9c?n0&nZ|`-YMp$ z+vqng`ZJha!Z2m#%%`PWQpoz zKGlei*Ay1qdpotnCRTk;)u-?)f0|8nyG~|MYy!`>y-uL75~Xmjmd0!kh4~M()vUwp zj`It*rzMoQXX(mb5RMi!UhIrO$h)M9UuaZq+l+R21LsZA#)Ce}%ZX1h4+x(1xk8jLHbZ=I z(Rr3_#BNT4H&6b@Q}>3eu1P|A4lh)2WeBk3~@Qc56$;SKNveK6R zGXnitNeF$N*)EWyYD!QKRsjNVR!_cvBj)HG-Fc;RoC%xvBaTWTDlDX?Ey$KFlKDoL`hfknCqpqhkPRQiVShWF~I|Og1Ir&YNcK1xcKEvM&|u4{bYS z@Q()QT^*I|6U%WP<;ibaBUr~F-P^m69X7}m3RqFLcLTd;eNI2%^WmRKXo7wn%CsV4)+)xcl}|$;)4WUb@-6-ILZuwdyvXOf`q3 z99&so>7VNzJ&ZgYuwF7Uk%#Nt+Rb$H1YgNX&x(k3rCxRk;2%^%$&Xd|^cZX?_GsiS z)PE^8e#hUSUr&Z$!1Q<=`6^~$tU(QQZRsotJd&1b{zC!Y1ht5A-7f9>(c`a7_%;0$g^S~yH*YXv?z?QLVi1>4m< z{t{2=?H5-86pHJb+T9K0nDmAbXf8S&Z$&DbR{lb}vTw@=765hh2j8^a)6Iy_68K+V zTH`-X)PX4#zu=Na42Qnv-jpYKdCR4q3RydyvQA0oK$N{m_kDlq5)B)idvUa;bOLND z24Q}I;a+vWxibK?+BwA5BA#4e)8)r;nxN1nL<^F!yix z{2aXmMAb46HiW;WF>Drv@56%Iko70FudsF9R zJmQ|xVYkU7Om0R1`6p<}ACv_2(+x3akWOkq6RsKSt&bRsfu_cv^;D}EGSdVgTPJE) z1)xCuJM{_}L^(aLz6cEh2g0;r&*CnM2MM^&qX@iFdI|J*Lm*(j0Ft)U45;LO0q$IX zshfET@QLjK3VgrNigpgZ5nrGO*QKW?q9)QCMv#TAri5eaY@SB^on|Va1MxhGY3Ur? zk>HT_WLV~5`ZQO(6Udznq78u*Katy(hsKhxW1U=B(MI5sK6nlAh%x1II+Qp|sL|32 zMqBCsvsxHtaV}Ub+L=Q$$ZCP#~Ohac+{BD3Eg!zm)YklJDwNMzQO zh6?GMrj|6IHdU;bMfn+wjGrIGG9TN%JM(&Ezq2vh+T4o_)ap-l!ZOtdWV`0v1f zt$Kq@8;UTf_*u zG!6g6v9EOP%=3QK=o8v6R&BLi_+S@MCR}QVp1c^J<44-?w7AOxW$D|&QwGPBDHydk zN`UO92?5hUMIT(f~dv3`9K)@ z=V=mngW%oT!L=7I2x#G3$$3FnO+Mb>ILP$#wFvCdZA;zl*#854Tha%tLgFt}o^59l z3Xi@}G~wR;xpo`HHNoJ~s@Sox4c>*smSfoq*?OY+bW{d!ba*@*B~8^Xs-6dfMy&%l z+R0Z*SX0Bkl<#FTwz|pR>hN33Mak1P#yRYI>k|1EkAlll%{`ww39_oesrUl#zZjU; z^xUyJX8Ww zc3L-9Ga{IkMl(7LgBEgqXF9x0tu%FeKr_v+J@D+>R;(r-ZCD3tN>7k{%S)lx2;fcm zh3nMvB-TBmd@t!*f?yH}+AFWOC54W`1 zYk+M)Euud@?*>m3M|lP9-ykHBqvQI;y=NR)-A^uwy1WZXac6|i|JrVuj<9i-n~}8H zhyL%li1b9$@`IHBxqnsG-WLb1w?q4_ zhJP~la`|)RJPUuEKwjCZxFR{Ja%X(&)HAfq9;2{uldxr38*T zF3h8-gqVSBr9%Zg(FM{2*?O|~iJ#m(exJ(v`E-3F{tdoU`>c8IE^$*TDh4E zNnt|9j^4+?*@pWyGE)L3nm{yUz4H;49!LVW>twz%y+3HZ>>x)K3n*5$Vnh9Skf5p= z)1DjpMfBhe#s{XX2G=8xUS6_&Bc*UW&KJc^k2 z_i+|J^gQklCVD~P%Mwyi3a0|iOZQC~cw0;AuIIsH3+ey=%vTSWO$Am6n?`Nc=GBsuX&P~68gTsh&Zh@X!% z6*r@*vSbca5>5fFp>NL5MTvRtsOYtkKezYaEwXge!`{v6hz9F9-FdY1i367x-dv^; z4e2^$k!UcOobo5&oKBLa2>gfegT!(lcFQl!6$U@Ma5&Jw z6J_Nns-yDday2}H3wjP!)@t!ad5zQPZMVY`aus-NO2%CObub#Y-WwwEf0rXS(s2F- z?$@b`&k))R3{;$s!#P^4?2})*u z(PmiU^>_hI<8V_2pp7)ippAQzcxdz5f(k&)ei*SHxdov zn6k^sXo!cM^5YNrc@lLS-(MAt>@9c+xy0>;=Nk4+GU0<)^lBP`4}j!Y+upR-w*Km= z*w!E=kgf@O?r$2`!Z{re&G)vs$m=-wY{N>WgMS>Lm%H{6)(g_}=cPr6p zs+NL?+!)fW*D?GGUO=oqcF1trr|2wn-xIj{HdXWESZjwXN{fgrftOHgJbjDvAS_&4 ztc8%fSb|oJ*s>dZesoyMYdEleT1W3H%5(1@k92i{qbw4t4?~4&C`=+Iim%; z-AbsgSU8&klb;8!VY-CdIoIyz;;SsSD@!!kf?M~`Drj#ikk0V4LF0WV4k0kd;wj`;O2Dtw8%##U z5y?|qtM(Sx#NWgBI{tsZ*XN!Ik_}J5NLz4Ra@T6+fUFY9?2xGQ=GfbRN?|w-yTotk z>ScZr_Yv=48V=eYrNiZY^iLeNr+cM@7)PskZ={InOCzu?`ki}lr=>y~6 z&Ksd99)q^U^qr8bxgzpg#H8<7m4_ulc!jQB?;cgJg*PJr_Aem5x%%w*e2X{fif%WA zpmKApf53JL1w|A|4&}|=9l^{F3c>g1ogj{%_>^( z#P+0I3nN$-KCX26n`;w$i<&8PQG($5Ql)ZnWC-!%1^N6Fu!HD9#u7s15cCSyi8#Z) zT`L5ZV2H^98{&;Aa% z#aAQaTA@7gBt~GmeZlZyIEqtj0jjL!AHyJeka)j4=75pfhprtBCsvIQiZkp??4r_{ znMKOX9~Bj96nKUAxXAcMp%R(6D}eeTyqirYKaF>-q=?Kch&N!j(#I$wKF{VZIR*o^ z%iMVAlkY^>G^Y$vh;6AS%xpwrq6XMvAU$j0Zc5Y=w17toEb}~}U5xY^hx>B$OMssF zw;pk>1uwqs1{E}UMi5FOoXC)?zl`F*vvvfr;f3<-KE%A&h$}dF?;e@s>iZBtSUPiA zzNdTYl{7?v&J$wox)3@Qo8R1D038=GbCC}| z;C%wh{Kla*aBTZ4N$inx@#m@>h`>j#^Cz7)W%P`w}3!JxQ4Kv!{G-TH=EzG|ZP|vg9Kgg6bKsLSA&{SI><`39`b-aT3jZYLAoJw|a05r#>& zQ8w3RyVt^aXK6lXc>eeK7@*hjV7|LuE;e67f?09`njSxBi$lew;|$$}VfQOr*$D^E z<;YRPjBbGu&4~3;5AbFx+5`iF%!yWGDfs7>Jz~@MpB3+fy5ajd*(xANw0G74yL3Nw)rVU%h+PQNm=>*=*R{ZYt{C~)}K(}AzK2_7q%W`vAg z#5jbu0Gn!A&v%!7dztPaC3M1WT*u}v7g&qvmab@IKeyUjpj7N zRJdzCGMWL16t(+fO{s4X)PS))ow`>qik7`7yONDAJmHB7{SmTf^hoHnYPjy#jriu1 z#7ihBuD)GT6rXoU5;Dh(`mCjdBZ2d6D?6TsIg5<7TwluO@yPFTbJ|bEF`dJtzoqQ2 z0?mNC0gJ_fWh7WaF0hAyPMd0s6FME{kZ(loE4%99UnAvEgq z=AoLqsIBwB(_W>@-G&#Opi5srC5|br2l@wEi-K)3{3J=*k?OMr!D&QHyx_49*g^$~ zGk&J)6ucDM^k=>Z@rXKOK(dgU0xPH0*Ym&&oH4viy2tp?xjZMYPmEK*Qyu?p1#AjQ zmJwT*is!)GXD;McaBoEp^xl_lDY`A4)5@B^lz2G*Dsau7QckBYbjA&c>~&V@APV?Y zNpE$;i+oS>(Owc!a*yKOckGq+?@0!o+}qN09g=|&(v+^?C=XGk>mRr;Hy_>r?%r~q zNhF7ea6xs~m{Z_qiB-B&cN#X#&A_oL^1iRU5z-rG`)S9lNOSt-;(Btn*}ojj!kQi zHVU+h$>(*&9V5C}&Wo4VNmkNRP)ii^c^S#>hb!PY5`p;7%ir;wXdKq*?=_`mVz>ekzA%(hz^*L~b$QRB{ouqL5s<^cv$jNsPa)n{4*VF-&Z2!(!l3TyEy9sj0% z=LnFiKDW{n2vsZ*^w0$zBCMcBv7 zA|Cmde7Mk?3G&xH^hJnSHUJ9`MY--Jbj`mzl6lJWUoJq=<~_KQ$o)soAL#>*uPeAe z77xawO`cyPRwbz9-U*~H7HNwG-}OHgHRq#6b!~XBy*wX{45C?zRVE0+rWf7E{>GA@ z4wqfci#^-L11W4XCwd^r9fC1jVga|?pvv=)xq}_coMVhJSB#J?2S`PZy4d|N=n3A-&?@W%n?I&=GcHOK=>-=M@9GG29OT90^b@y zT0_9FIt#QY9}1B@S2e0Fy9qAA!`FyG7K02G;Z+8J&8q`!X};x11dxRmuxk6Bv877q{!I&Y~y}z#u+{4{wtsrsmC5*v%<; zsw?p~!qXFpo0n*T0Gg0I`KWMRoV#ztM9F6tn9*Wn>GcBRUmc}o$2CLX{x(e~m5LR& z=%lv{EnvzO&xE0{^wENE>vfMHe(^Wiskkf{d656``T_P_RJ$psh*6~I+pHh_PNsqA z%Ll!luAQ0jO9n@sQLqs3Mi6==CDRbnFsV23r-u2_<(i|<%^EqxeG@l`kY=-D0UIyd zf>piz4q&aF0k$dYX>q2X1XnFb3F^a7i;?$MYEKR18tJ=^`_{8aRa!i!f9A&e@V<}d z#i0{k*JjlnB|M#OHw}YGqAw85NWp5kN6@Fnf|h?fKTyM<2R2p9-q;!`?gmL*Rt%wJ z7ix#q5u+~!PEGY$Y`)-~@y&*O=$RjHIA>cJGG_XVQ-Wk`wCvmJ?9!cvv6%qYgv>L4 zmV;lkN}q=oBgSLN39<*z_6R=<)tr;^;H6v5ejMLy&m&1!s^_C+T<{HE;bZ(Mfup2&`cFPTr?uaJaJ~R`G zf2+4JRC6(H9UsK{yuW#EGegWDTXXsQgckdE>3ISwQ6DH}B()2-1>mw1-PiGGj`SZY zAbJ<&NW&H`73<}5FXqHFn&w~^!K)6j0D@u}D?9N!a1vFWm>6ATpXE_F8%t}aSuPA$ z-ZxiU){5|WEV63KISQ#_yTN^)k0cX!Kyn^)*^)v`NbilwhdSfbI@O-UL zF2}>qeW~Ujo?g{rjH#>CHeShLXLYl5R<(L2!RLK?=VaKwLec2%t_q#3CsxOK!vix7YQ;GnN7DQZOv z;SJ&-r~PBBlAoMl(pJ$3@bHRJcw^ko9#fj`k1i*jkC4wj0e;pv5I)t-twkfE-sTv6 zfn-xe;)uARUffPef-8vVRG`_TAZ+@Y*D4`u!bLk@YbhSTSR6(sT;=op4C(Y8HSU*6 zFRPxd96$bf#0NPs>^i3#lLJ>?|CUIQAo=)BAXn#9{h`*Vpw(|*RRf?fl0xc5%qjZ2 z{#(gioF9HB*?9+)6os+~)kOk0Qw8>$~(~xI`8$F#ME{(0ExHMErV?20c}zqUOlD zMtpKgDTg-jK9Rg&<3s;3o&u|vp7cc0ArO=3d_-he&pqB3-@Rs~Pf;PV6I7XP!GE|` zGzxRfS`g_7G)YbxQi)%_st$^}_6L zXS0F3&~wcC@qG1Ard_I(&duu2-!+eg)@yqp&a|(^Dy8h`16SU)Uf+RZY9B%*dO&1S zUOo;^jgl$-j-5XWYl53@;p1XCyny!dU=QXjc@J3)2W8_=S*y z>Bg@;Uuc>fc?$JuNuMPKrpf0~(hi-Q79m%ifA@l7*?1=^I{mY(ra6Fx{ryHWc$huH z_Y1b^ramp|lZW#kQBv3}li7zHQHRAb=#18WvAe?vvjVsCTG>SX3xHK&@MI6{y)Yv- z?a$bl(A<+zf=Ff=>Vi;*Pf;eDm##L(nX4n@Xpa3KDd8RoWl#USI2G^FVn_1i+$RvW zXid6^*tkQ2(_rX}F?m5IMp>lBF^Lh`OC@3$=pqic|AZ;!fmM0*q)(U&|Iy zljeIFgUj_s(7hO9(qVYJjONkr$uO>GgMmx?_Ef5iwx`Px15&=%t@AV=#JJ6C>vZhT z{xPIfJASEV{Hj9xSUxGz38#U|)FN#C<5jSru1Ng7>i^tyV%9TVgh0jF56#SOY z_k%T3zcTqK9JMF__;_^q{?eN@Zn5IbX9hNv{VL?6Q1hb6tN5&5qV2L zWn`aepmt1UB#&YUb7lxH0Pp$QFGzgE)x87+oWR230o%~WUZ;)8NYsljQyLlY@V>Eo zmkfiX2q#{;?;Wt8br2KfL)=7mVQr&>Q+l5uNxh;MR=^^B%;;c_uFQq#R#IbTBt|wT z3zRh-_+R-|7X9fVYg!!p1sM&g3RcdQcJ|-FI^Ry+kfwWJsvaRb=NT4^Dgx3`#{JI^ zb%B9--l75n1B75>4*0w4Z0j8~oOt)Wu9C@#7TLgcEQ%;g1sU5T?q33LE4k zH)swMv%mOT%!ZjCQ=8szHCY6xQyiMJi z`mu+WJ&au}5mGkAPXbn_K2W84MRuV+#$e9qYiyWSpvxxbf_-=W5o9)XQjGyD87AL1Vw8VJUY?Tq|8)? zZrsx<=2Y0t?UtImH2l_sZ)lc1-onr2aZPD%_w zu!{<>)>>H=h7gyBhbcTzhnZ_meYia6IJ?#Q7SWxjPm)>~`H#an-RK%#xOM3p_#?O0 z9zmMGA)fmm)*kX28-i`DjnG172A-&I13(^tXzblD(^QH@O;?w4N%*zBd>VS7{{nCe zrVSwt**ISK5bE#Ok`y2AzMp^1UxRq)wQt#Ioc0TRpR56%;2)UYftWe#}i>;0}U znUk)CTKpRV*b780hM>Qf7l!%~B?Ba89~^jg!{|UnF^O;#Mk`O>cefb!$#WgZ?xT`OJ8ykkd ztMGjmQW7N=QvV(mPY=jzD%MtJXJts~;C=K9`r*eBu1o3XiKYfDw0`P`M#1p>|h5;OVt&$nQB3BDI+hP&W^fH3K zifje~t`=0^jc}E1vXMSST{&nf_>ozRqn7}$tV;#X=EI@ok1rj0KsH`;bz`aX7qB~E zH8257kid+~ zxdx16S7Zc1EniqM@XOK46kslles~_`YRyd{V~~*r2zLp_j$q=yRsM;3*q1ZgQ(J#H zBU*O=ss$r;(yrqI^!39$H%`o#>Sa40bb?_T0Mc#UmbPq-K(|GM_eOvXy(KG}4JHyU z4=%4mDmEIp`t73NR1IVXCjhr|NF4&!%I55k=XQJg1>P15fC3Yl#Ud}+7e!M8jZrZn zg5~cBG|vDB{OQ4b-Vsr``GXVIpF!}L9lUU30Z?*zAL0lwNG5Yx`u9dja%lZXfD-(c zVDMs#hTbP-;MbNEw1{Fx@|HcD2oPuJ6*>F~>Z11MDLtZt_0axlay) z4~wb1^5FlW?9IcWZsY$^X)p*gh%7P2ULit=%rF>aO^cq0$X+Cg%1#Zk3{pwS~UkAUa#2uCS<1^!)YjyqUP3iWICIo*k!3;o-V*Ml^+BKi7Hz!Yski&#S8 zCK?0X%Mm)Ja~m9i$Fh2!!aXUW7KP;&tB{M2(LrP_<#6n#oV_KxAL?%*Ycwh)MR>l` z7~1p<(5gugRQZNQFj4@SAL&zqw@q`4V35~nD$oibRgR2BM!ImmhYjrJSEO3O6IIZg zjY~L%KGU5yWD$BnQvBdYF!cTT<6~2%P@4M5aeU52s`d3d-`=6=BoQYHn`ihCr)?65 zbiL}zwQ*Z$c?7NS)KVF?@CJZDCkE>P#nagqE-a=!JH4kq5-PVo0EnkR8Zft)syRU6 z%aMLE52n$xSQ?0My7ohqt_c@sOhgjM67lK>A0EANk3qHpHIe9X5QUWFNBdZFvHpL~V}aGm8z*?R;?7Z>B$6m* zXt2Y@2tTp@b#?I~nCp;A?z>RC~laEVh z-88$8Nxra>o3M9WWR|)@O9z`vW}asL&b;re*{#^kV0ZC5jUObz{sZgapk6k=#Mf_L zvwwy!FADPu-_BrDQ+*N)W;@7^TFKC4WmAkFF@~^jSfJ4gpf|+#fcd@)O(3Hlk zy}EZIjRB_&x6*wRVV*gyzn8rkLtWAL`bybuz2MhpvaJgDPTAN>!a8ZzEQ8%wV9I>< zK8K{==+j+o_EwPEeHbpE_wAwQucJdxsh?&pJoR@pNFlH?=RLe4vin`firjbVb0WK# zGLu<{DPFzcp21Ix_Vhl$15)^}R{03npiehVai7_CCnz%+TMk{mkONQRLkPLT-g7(S zGyNo-4rD2kayvVpOu*@H(DgJ;;0mKj{>;EatUytLpj=uh_)QTIic7snj^x%6p_m>h z;tO+tlLNo6hH+OE<5$|2MCAu43sq&P21>NW`027&*W_;28h+cqWGxUw3WkltEhsV^ zZY0}hzg%2rAII$v&Sj`d!^DSvmkm2({E*c^(vNX%VEXob{;(>z+}1weeth!M8@OXB zTv)!@$PX989(7aCJ+JGa+$Wh_ z#A+n?psPtY{iP6+4s2iRdL_`T=<>+k*DE$#I#UwmrncRmcoVN>Es&x{%j zu%j-y<%x34NB)T>-V0v;2THM{!Mx@n-(=9`&)&W-5O)9h5JWGmoKw|Ua6;y>xq$G&sa4lG`PRHZ#4ne;4B@EHahu-`QzT#YFYKKC~3NSH5}MxyJRBTP$qz z7R(cA%tDu1m0>)FqIbd`cErM6ha+6c=c0t^FNrKRHiZNV9w0(~+c|KFg^8>I>34F| zug5OPGZy^=;S(aZsd1rVqGh7I1|-kzR?hE>(7SgQfPX;c?U%F%P}%w*kMM`y&NnFB z55lByt@`e~1kZI_ECv&EWu|jzvYG+x;+6<);n>|s)zN?vc}!^6!5qfj_4p6Q4)n9s z8nu7mXZ>h_9{s{lF+h{_uuw6RWF}jE_%|W%8rQlawK4s^JV4~IBq3tA8Msjp_+gz# z)|shT^Kc*87!9cORk@OJ2>FHpWvESMZ+!KJYLr5F0zboa?=IN~?{}jyn2@zgk-FAH z#u7KcYHeha^^8Gv8?yc2Kb1s3?<2P$+ymt%6m@T*s__6a%C-PDA>UV^@^oC@|MnIM zIE#~Dqg(;>+ke0~CCV86qZ9%U^ZA2q?dzzS1A1_qbQv*rxYh{$SyN|7s8EMi5YUix z?|tBzq1;3BRLIc!*B1Vw43PfidUYeg`Sx+8< z030}kj&P%m1GfpSl>s(efH+3fc7ky2!Z6)v67csjC>9$&W@_QWK4N!3xGr+O+N$MO z3^PRbB(#!fFV@hOVf}yHDMz%Z@`Ets^NZ5YN?ltjB;z%RcdGp!VJBl=fGGSVl=7u0 zybO$bpWnMS@q2yy!o%Bfj=ZU649f_I9vZW3BW0J4#&8V>u$@nVdBZc*FdR`NCh{|* z;24xn08Sl)c~|nu|L)Q2bZQDg7;Y#hJFhzr#ne^VO+yv?q34m)6eK-UpJqD48qDL z{cOp~!XU>Bq>VoC@MvJZspJm@l*9WeU?`4dO8rYW+J_8lz#@P3^@O?RSN>-}%zFTX z3Kp^C+=|EEUrxaZzq_FT2qIwL?*w(pQ|%=pF+QaRXcUva+A%SRm=CtO`33ehZ@K4~ z(RET$l6HH~(=k=GXwkq6$(QwWG;CreZymg-F`{>pJ4X`|xy866PTYpCE(=TW6@@yq zVa!ZZBtMeHZkW^*08HUY(EF-V;$e>d=OQwdcs3Zbefh~0$gdSFM!-R_Vyq*7Nqc4; zm)h#fkWj?JOWWQ(s@X7X8$Ay%u@}JSgM?%IJ7RObBJYWRcPI*hhQ6}4?ImO4>rYP( z$W!NrHWawEU@7;oC5`3|DTmW$P0o*s}o-?10*0zX)>w$PF5V-7L$2MHw zsR3Pm-2T4e%aWsbx4)IkeN2vkWnn zCMOg0P9~Q9;s9}6Y>BU796WyfB~;Rrg|a=NPe^|CkfQ8_=qkTI zfV47XD;GV&aJ&&ryWJ4v_}jL`*|^BA{>&^05Ke)c&m4aZn3=voLTjLM6vhhEa9BPl zbNrQAZ{l{l<=^s8J7#+NbVep#5%~e4oeGK1bBzO(PW2RLw+w(#vKWSnf95_tJNJ7p zPMMn73&ew!vrS>1*F`Es&vsg^9)0J2618Nd-+N2*-vb+NMKH$&SW1ch==nVnQUy)U z`%|YBAE|>kK5x_giE=v7fo!+$)h>Pi#U&I$E~@@UMX2q6Ey-*>+PqO9|pex)VSf zt5+`tgc0fwEfU%hwf*-WJmme*r&{umS^a z837CAPSulY;xYB!(1d8@aVX2MB{pPYzQ;8kO5x7aSU|OZ#2{B3Hsg8IK4# zjP-$@s^nn3N|rVO#MScd2b0Ag;cR2_c`4I-DBKjD#jh)zr*mPCQFNfR9{`VwA$bv+ zc&~`NI7RCUxQ{FmqoU4>mZcM4!%3mjHwb(D%oih~4D!IUP;#84F~VBw0-zOLU^A)T zG_voQD#sne$#|gptKdQ1sLa#;kDQkY1$&$uF9cHfYq6qOB}z(S60>X$#jPhwV`FkU zeOuJrqlfFTq768M%hY%^-SL8E%nxi~olt(^;oKD1&7mpyW*4`U)6L-vw9w-&a?xwMRMq6V-4yT4OD}_ zFXugs|Nk1+hVgI-I zJI$xbu?;CX8$m=cb;DmQYgi<3;E`Ys&8mCm9^Y^m z>%Isb(=Q_WJ=b~*3%`#M{eyH*;g6W=jz^(K6M5o+OWG~?`>F|^)$sQLwEt_>+k@d% z^c|K)_c>pOO*`8MzHivRv=p5+>8u*F2N?7hTbHuC*5?42Xp%KJ5=FwKgM5Uw_OXFT zVUNMOn-!{loy+(gLm|k8egg z`wu|IJ=r9U=UPQlv2)MCIPKZMC6P%eVT-{CtPk1tKvq%(keL^LxI*PrAqrJm^iJuK zy>pL+cIll(h=2@cHu$A>7Xv`j@GHDSMSTIK5nZ(aDK?g<+Tf;XI9~$ObX|#%kMO()e+s3Tl|Z zfBZf#zXgLE%I|%$i8ge}XE3mo_H|zT6&t*I-iMaJcm!p4FF4J+0c0B=a{D<1tj)t5 z8I2aTlR~N7)inT2YL^V5e8HbXum1rB>5dChY5dLOPU zJ6%+S|EM%FoDLx#L=m+x4pHFQBw@ zqkWry$)o#ZVWxAxMPMh(+Uw67d9+(KjjDe@%<~6Z;3{5#8Ov0Xsp2DK*9`)kt6ztK zPW~AwtA>!PkKynKh{dd`f$F0N#*5ANYRa5u6MdG%$+VgiSjefY@-3SFJYjB zDJ@D*%Qz++gt(ANkPlanBu z4vZ1K5{nJrsh698>}#GQ55fF9&fdl3Vu_@P(}-tq6S90}40n(Zp9j*Y-QmjqaL%I) z}tia(YPIF zEF!T%d}poh9aI2S>8E8iEdSG$^yWYC`w@{K;X%$blqW#^i+=!)+#%*M*qgtBF4_ah zm{-3o&Cg6G zXeagb|L_pSFgJhzLZ6ytkJWW5jmH5ow`M^mLymp1Y3iF-k9XymOigNFVi#GJ0b9j) z`X-7C<;c&$S0@485u?+)7qE)ZQW7<&M%fa)lxkOQAWpy8Hf*Abu~Mc~@Kc6qJ%)F= z@I;vGp4ku)_>Er^R-r2jo<0_?>mY_dN1f7yAXTs0aF)Hg?dH?+AB&TVdRorz%&86i zQ+KtYN_OA%{y}S1g}ymKvtz&*05B%&YL-U=Nyj$(s6)D`5{sCb1 zSu7gUdq8_~ahF*dg)$rf)|em%3eGUB`_VgXjVawpGzuOnX3yuQN}ZHZ`i4KDH(U$V z;{)h7Y?a=w&VZDmx8mdnZG|WX3x&Bp6$aJ;anaPyI>4*7KSfdFZA)FgY6idaySn@@ zUlKp%3rPR@ve)2EFgD0Rm!wz{&ff>=eTOp<=2p+DphwD3VP2z3CPnoLQ7VD@1pa28AC0VdXV(nD`$cDdkD<{IZnVf z{s2f{?80`RU zvt&ulUm}c=InwZYNL^(Wd4lUPGi#nN8Zy+ zr-y)E^-GJNwh1c_(8x@hSl)}o_`1`TfpN!h^ay1fA2=sZ=IG}!zR``u#^edV0DhqL`a3G_0$ofz^y;&Yl) zD2yvOHacQYZ?uX!FHZ4DY}er#`7fA=nZ8Jeq zbGvY?Z~p?hI4EmW>f6#>7J22aFg9>uHspx+Xmr4nsAE-=EpQ^&ZxHda-bq^^k=%f6 zSPsg$?K;vm@Fp7uvUmDcvhzf0W33O*r0h3u)DnthqqMs!KA*L=xJOIhk~H&;|1(9- zA;LnKdmh$DR}@}-J}(G&wN74US={mR;N8CuWES6$Wif7#%hoHyo>eHgN50zvAM;dx zhJ)@9uV%!~x<++vT(SouZCWT~hssyX$P@OG>q#F6_{h~QioP$d72E_;)yE=(!`T(< zK6g29V4ItgH^z!iOWukq5R4WbTMEv)0Y$LXH726Uj~|pt1L5C9^X0+S!gYEPG4Uci zh@_UIk};&C?3C@k^=DggX9YCx3Y&ZVDr~G}SGhPY;F&AKHprS^=Y{MBt&M)HbSN;6#G7_wHb478bP z)IhKL-Pf?R;;v&^A0?ci^0TIdx^a)oTKzC7*RUSOaIFsu*3jt2l#J0chbvFqvvYXF zb0z9yn94EuED5c$9)k9;5~hQy1=1Vk?`X&67j zP#}@XN)&Loa8E216Bfjwi6AJYHy$LZQ12=1-Tm1Ml9En1_~m0P%Fp(!6cF3@uDe1AaCnp#~|3T z6G;3<0N3a=XF(f%L)pDq3;8lt`vsuKJ>oC`rU_O z1Ne=ACxdqG90_I?q7O>Qh8_5d8~0yQfhyqo(K7c}4G6X9(QhfPY`GTNl_@~OI&+7i z!yJw}$5=t73Ya--osoHXqk-Z8!2n3jwghUY2SA2Up1X~PP5(EJP9ue~yCn-bd~*(U zKN9kH>QaIy#5h5zZ~qV343i^4Qn&^t0Pp^L0?z2u*z`tNAHz$9CxEFt(EUs*Ywocr z1;%=LTQ}rrT;ks90f>kBv#0KQKc%MuC+p+d{n3GPreIxNTLNZjFou9%7`TX*Ts%6y}-Q3T}{bMW)-6!_cW<}a$`pj z6)I!?yCxO1ieyg4Ni(w(ET8(GK*o_(Thi08QprpP!-yLJM!JD2m){?T<+lM|G?G_N z7`^!p5VibcY}@9x8W>j?M%qU6N>4)TQa14N@;`{TQjQSu((MnQ5XUtUkp$-A$Dv?| zSxgJ4D$eq6aB8ZXe-t?5qO0N$KFt5KMw4NUI=6QnCMGg5qZSgK)S$rRYkgJ;vs`vG zt44&A3s8Kc!4zR5JMPsZXp;J%d9dEQL}S#l@4PJPON+&IhA%*6?*#~5VBc?~V+3bV zXORD&u{pB(q2MZX)hXS9Me_Oc5#lUwza)sqy$8vbMIyTV0se$2iGT@Hh z|8tEu(KYgQ2M}?t%w9VoI&Pv(6wC_tF;h+>2j@$zcVN9I24Y|&@-?%o1+qL~F{fHH zfe7;o0z9X{TBua1dZ7-v%OYSKc@0lV#SMbAwiMc}z%nx6H98N%XIoIl%qThX4Rliu zlv5Ppo9FCJ^B53?F@>~l$AW;e<_}t#MR_8KiE~jiwkJsU%*a0EAUz2s%1{0peIA`o zq?K|?Q9AZna*$i2pXtXvs4CFFBk*)L)w3<O>4iogwpWBoIFZ8n>rYW{`1 zEb&HGC7;kEEdm>#%4N9krk~|5X$=^SkX3jhkHK6RcO6Dqi~+)wBEIEu&zh z@eBb%6xRgwjfj1YR9&8*tDbV=Ud3$QMO1k*0^j&At;1++67=WmwG1L(Z_iBjOYnqU zPWS<=)3L#=%{{z1xEbQBYB#Imt7w_sKUqL4RiJPYfm8nFZ?w z&hzcEdYf|epw`_#&cq9{C&IKFea?Qa_hd!-D zFqLbJzrW{-h<3o#`X>@Ss&v>-DZItD76)Q!qG&SklZn2Od>qCPt7k!$Wq&iGh+-iM zp%RMkrWa`pL`A!h(g1$5(7^c56k2vWfvq&+YYG28lA}zNKM&rjPh0*VYh~C{>&>;s zq{Odzo(Z-%2J_nz(B1^rq+9C05pR>3eeJGBsRwTC5n05MYmsb+vWq5!xZ59u0=ejX zl)FABw|#&rcnZ1l(2f4yArzKIcNv~X;SM+Ky?y?F4@;k(J1fh)CS43&zUQaP^2=wE zul+sFDC@xtU&O87`x^S<&cgI_(>hxEYY=R&E08o|y`r+p&S-!}$h-Gd1d=<=Vseb~ ztPX{z@aOO;R*n({wTSsQlx2cS9vO9J?G-58R+j#SF9>|8&joCi>;ueK`vi+fp?LUc z_=}&g!(DjO1Otb2dN)3q*W=nTg}Zs(QBIQek%bqMSL41VphTS8EVtVQ;(*lqig6FdCyP|VHKKnzf#7gv1<5G4VP6>E1k`fN#%I76C^`8Dc-}ORitm@0a zoHJuj`RL*6fW`BC8_>-e)KzIl*Mc z7B8E3d1s4F*H?n@SHN}r+Wvhv#%T48d3xcgOdIMBBfBXcV^ohE_d#QJW<%?1H%OtE z-NgCbw&=@~E~0!kCO@N<7L($kmVwh}nu{ zI{6h2Y*R28o9B_&V7->|B3`?gZj{$+?Aj9A6n$Z%LF;C- zG+seBOV}h(M75hH4G5%!<*RH}@U5H|D^A1mUt;<*wC>L2qJ8+@`tfOxp$+yI?BCmF z;SV1b|AyPABe%0?Ym!;W672=HWSe@R&y@!I!z6WaOfD}6cY?3pIL`@ceSl9e&d74EFi`QdezA)h!QBJWiJMdKCL~ zIwaAzsd)Ff^&J9(F2OjKUQ<=^)=x2fTRy|^%9xgNufDs zF8Uf-JqBUv0{sG%5H+8m>O#>Sx+4!k=p%o#aMD!hJl76*s*ac^f|l3Z0n!P}&;-x3 zN4{EEE~{$TZPZ=grRxbcD;6+n*_cHo(14yT?LfVTdA@{R@`cKEx;Z8eJutL@NW!=$ z;Hf?dXVkA7xL36=MHH4=_G_B;Ui(Y#BJ9VmONOU1RZheh8Dh%6g0A!o=*$o`xDSnY z0Y5|LDVeW<5`F@JZ0lLj3AO*#+_a1?gZTUBhs>@MiE)27AHb>P%Rxc`jHc8<=^S3j|f|x?QY63$H zPu;}~_6r1avw0REOQ7=14$M!KPxh&PdBRryRmOK2s&Rx+et;5m5(o$?=B~C+e6l%z z=V{M_89kbj-UEFkV!!kadrpY2{M=v!3g2JoXp@~RScweTLfj(<^W{+ z00eKHc6w(j6m_hd_ov#Su_M5jLZ~i7Y#<7HtD~EHrI_8p7+=rp-6pM?9+&)k&=IJF z!BL$jU*Yk7TVh8u@WR88(7LEF%#Ex{bl*)ch(hxXlt==993ZCxo<8Vikw?&~7Eu!! zYD@r=9=5>eFdx;kG& zOFYZF{lu}f9jaz9$s!cPHU|k7B3{tJgq10~4zwZnk&x9H@gixxK9D`%d*0Ov43F=0 zQFOo~(!a2phjTh{2Fkw`Cwtf$zsS5l0dKql{^g!<7EbMxvKN%C>yr@)Wxqomc#A-q z#Rt$H#{Ju1VYfkpMV`Ad$s49BLBLC>8?oLx=2hD99v*HIcc8m-pl@83#Lrf^ zmH+dmBUa6J(V`SjA5q0ZS!nm>7exf^{OEI#4wYc>9PIE`Mh``9$~jolJq+@-Zf&UG z#2#3cx|oINow^zw5*qt&_>Ey)y9i zqjh5bzt%}KtP@aV{2IMPD2K~{&+VZdSQi4VT3JcU4%&#WOxZN5yCt`b()d1eIIVz4?~~%*&JZ^iTw9*3ZQ>?P`+25 zSwi~nzdKf!)QkRI8Q9&STAl*B40cYty*J=uDU3pt?%Z9+6C6>V&A!h`4~RHDW?ua9 zGe=tgyi(s|r*M4S)bu|t0Bp^LnCd?xA_=^Nv?18|Vnr?i6>JfopB`G*cAxA88=Ok~ z?c0g2!EyVKU4IMY`J`EQsW-aDnKV{Vb2F%Lbt>&sX=PZc1XAnnu@a>~PqO5CLE_IG zP2&#$HWhR&XVJNXmh$+T{rSed ze}?%!An*X}UCU8S3yZ*_#`KGg&l4EGQFnY$ip?M9Yt2YDMdOF_W4C$MQ&{YisNaKq z7ES52RallX!JGHT1nT$^2^03J74hQ?sLZF}LoWt8)vh}yZl}pB4|KeOTit+wFDCOu zc4FeYze{?oYEgAJlr33&v~OcuAu8g=Ss1pWju7NX@tk}hMJkaD00olWj_aCd?jW<5 z`J#}Ow-9dU46UxyL)}a@&x096BWT#B5a1k=S5LUU!?vV8USkj|c(6DdP&2D&QIHXo zA`J>iw4P(P6S=}!y2W9rrWk;04D*Sg)mE9L?r?`h2Pl8)(^2vW3{%Hy5{x7c-3sQL ztr6}8r-I_`um=1Ms5OEc@U&?7sIE`{*|i={G z!<5e~X@*nn>7q1ID7-7SLOP9M=SxHd#O_cvk1GUw(FDGeQm@f`i!NUF-|++!;6CHw zC$JMgfUr4#z;PBQ5L6;)gv3=Z#fZi^W}b~aDmib!Gbzaa?>B!CeS71Yy$Z|{5`VwF zM!lpP%6x}mVp~mQtto<-!L8snvnMES8Py~u!03FbKl+|R$iTR?WSm52qQnD0fTG6J z`1!lO!(m*SBUEwf6UvXB@k@l)6%d0xZZx0T-7;f(h-`zhoMGVC2)KMe{{>-YPW;y8 z9l~@c7&ok&IH7~hU-G^moN6F>dY;T^gGh#i@3fvZkOLitk5yZT3!5@+5f5tF? zgZRt`sO^j;|J^?8Okn#wtRwr(fFOmDBG!)-WX;5$^sm&i$1F%`@P^S3Nf`pg`PXJ; z|FhvQp)(sBF&U;^>+ZDhXQtSsK>V0Nbe-zB1G4BvIT~j*t+;8-{ENdANaH9kqgubA zeQ&{A`iB;T{N%g^d#vTT|BRLFb0v>}-x7XNV>Bzm3(i8t_2CdYk$g9v4*MCyFF#0^ zrpW>nMASs6&MV&9yv^go0FU_>6J11&u&WI4&d1v%wuClN2` z@4T*c_AVL_+wVw86QBfjf-*8Tk?21>KZD?EM~im5f&nYAfv{kcr!mNtn}SrmdRku( z*7JIVItyyj6tlhSQ{kAo;MZUvXg54ZG1nNtpnyEY<(r3`zr{6hot`+(K4Xyn6b#u0 ziM~5vVBF!a(SGgsUl(Qc)^BG=(hzm`_-AoRDr&Q59=7EKrHUDcw)=bQ|tA=%# zA}wCyIaD&7INFGTlC0#t1eKVe-V zki9!ej+@9|pq!m5%A7ZV>;10_%i9Rgd#vA}nT%oH_jmpDze+yd0j!*|x2n9DnB{gV z)U26*Fn|zNvM%L@Mo*hT^AED;+4-ZU(t>p%o1Z&Q-`RUqE3;NI|4o*6<^us z@4GQ*aNsXQVTV9*K?@HtBF>VT{3Y*gb*N(cmgd2N*LLsg`Wcz3RgnlpDq(l zG7gpFj`83YR+YQ)(xvk-zx~$*_y0bZTj(#zdV44maisNnzy#F;&+6=+!+BN`6n$OM`T!DXKN+mAOZ>+sG<1qF?G6S!dANA2W zM+2CUI^fS>&2Z(_p3$tE2S50EC%?b9{s~?1?gWp$MBL8x3#P7Lu-&odhBOP`59OZM zq`gG_TFK9 zJ~9W*<(4^J@C40x&r_%(8ukYWOs_tM>}z=SXYlYvyzBl>yY<-wIfAtd5T8vq1DEsn zfRWS%ps4@PT^vGp@xCJ$WI6YwY7Ti@`9cDEO49NJSMqQh7sBG^byXupOC&e^okd{ z-paSib-%*l(o!|wymNq>6|@xZeA;{gH{-lRmnil?p5^s;qaG3OD#!nNxJonF(2)B2c zdl$`xGVyNtwHqT?vJZ{{DAv$`0J_Y@37i4;o0x`A)(W7FsI6M~aj?k#B$Sxb!W*FH zEd2<_d`d&@(HyoThnz zUrhw(F0fU)rUMn@K@i;@T}wGWXW+QH_iP-Nf+b)|_r5uzH=|a>xvXO^2{jo{A zW-|;kiEx9c=XmGlzNH=-QlG?Yj$FUrtSa)AaVM1;j{pWFLa+!S;T$)84K>FBTJ0dI06;GvWv;zhB$2q_Z&;Vz_C7tUB;5>-dq4;n*6ueb!_kdzW z=S}E-Q=pdZu1Sqkq)Ve-u|^TA&=m(kfVn73d>#<+KYkn?3q%1jItQZ;M{p*)#yk6|2j z8d>pFsW9iy7QC7~bL;v(M`Us~DVDNA#Q89jLq<*S1kpr+1o;G2`MW=W7yP~MQx3b# z%9!fvQA8s&Xw^o%mZrzy<4||3!+a?NK4$0c8VXx+pfq!s#UOq=w7MgD*i3)lt##}{ zkKA2nnhw<^M-w#Q73uIge0*{`qoUSO*y<~)%PrOdx3)2RUD^NX_@~3(;B3GR+Id$a zEp`!5j(qv@ZIdJh7jKn!I)zBAhwkOOUaP6nb7$z8{FR3Q?%%z5{CO+busfkS8B~)` z7-2OFXjV67C4^<8c)!@ZCI)he@5lOp%`{2hTvYWd=7XWtD;P($1_`?_1VT$;zbA4& zb8Y!`5K7NP?a(K6VPY4eo{5&5@?#xGY1>ry&dD@oo~3`#z#fJ3U@j2Z+y--bvVJ%$ zsrk?SYURs#JIpt^|u)9+ga~ZVdixbF>Ey0k$kpsoJ&B!zn>UtX5O zKH24}(T0nmy?6pRo853fWDm+Mm}uegf^^nB%RUf{ILY7m2+%Hztpmc zBDHpaB%v7Tdknbi*@?!qCgi&I0Rkt9_aHL+*vkNlZNCUcq`rhIS>g(;F!i8CKXEpz zD~8TS!%H6<$jn+ksSurC#+q($> zuag%*z@Ysp)DuPVi9Tbn3WN=V-G%R82cEs_a6&JV)RO32z9K<7y9M zg|W#FH|$oQ7@A4%w|HQG>J#ar6e|i*jJBHP!aC`D=iWzf=A8$m%EjOxa2tKsVFy{k zX|nd?o;weDc=(KOAhO94C@~w8#3EV^j_{sUr!i;KKf^Rfm9@^u9$Z)ZCx_5{ed@S^ zVaMP=tXo5yCxvhylN6}SzFiV}ET|+JQ70^vqIL=EyfZ9IcUHQi8fMmpr7)gcb>Bt;DgMfZS5exa0G_o+s{*6MJLKv-6F-FF~VU*V=Jy-2(9Z6{8XPc=uC1na8vBcXebRnJMG(HMAn z?Kdt-s+-lj`L9yet%4k9j8!)rXA$u;ZySGi)^WvQYTJvNz{XqjZG-Z zx-hv`m~2B{AcSK=EE7`y<4NxZ(vw}BmcN;oyBnEjdZV> zUn~>6)im9_{RT0Eh7%&s%`xp!B$XesV|Dvx*2NnXL_)#d9fwqJLM*9Od@=mgG3{%- zuj$CNs95=gq?qvq+Bc1#liOiIYM(SQJVq1kGy}o-MU_jj9#_(}({pS;9J5BEzZvS^(WggfrlpD!Y7tI$y9)7*cpG{|*|v7SlHJw;avt|w4%P$* z71iXV1|lW1cBW?od_c8`MRYxwet?*$jy7FEzom8;34UMV4dF&B>QVORJ8G!h<^kEE z4~Uvgu^abW7%9wP8NY9QJJWm^uH`z^@tAd!?Dk9lKEA#kk$UD7qLDsOlTi5@Dw+40yOz*7trC6!1^R`4bZ``J$4$+dW~EWvH93IR#5c)jeqoq zHNfTDUn_eMW(MV8S^XNEJY7^bah-MmcCIq}UV7jiz2OBIRjq6}6#rX#DhnL?!s^B1 z7-1`-W^emqDwNkgcJ)2Hh}>Uq-@D#^Aji&iY@yN(R;tFi=5@t*IEo-ntdjT#5Pf#c zO16^l67ar9^fnruZ-?mAQ>djkI^2pJ;i2>^4;i<>yo9#a6WN2r4Fam7%^Mo$0{W_; zj;0p5JWXI1Y(G*~V020w?ydqxSs^sh*%~+z{`AbY)r5rTu5Nw?22ft8Y=lG67KjlD zfLUEbqauK6;`Z+Z=DH`u9^8U}fjZdQJrLs!Mny-^*h{0g^6b5}Nf=P=R)6Gm34gqL z`IqS)h>bm3umIC-->Eh5?XN(J(uuLlSiu-Y>lDPhSWklwbQSyMH19>bhFS++Li2-c>Ti;$+$touj{|VLP=LP9M97BM|^2|*g46J^O zcsf9=^E_a)ny-OLwbV!CZ~|=A)>GkEkUxD0&O)D>4jw0Ma3&I4>BuqQya{8#SoQm6 zT?(EELtE7vcSFk1z01@3fG2^@0_J8BRbM;>IG}S^8B&?rCly! z6_d>2KrWMkdWP48Fv=6u)^j=Wg-o)U^g5CYqLb2oDg!#fS^viy= zjKC2gc+6=Cj5bI>>UcEj7hxd46^8LEi}1o1TY9}lt_&mi2{{y1|CGMF@m~X|2r~t` ze|S{W z^__66;jqKj{u$(`BV(REs$SwEbeUg{B3CbW@e}d{s=&Ey5yAg}bH>-o3(9wYs5ifj zm)I;2Q_GX@dgcaN7+*apy!#Pmc&Yx^fgcE5z$w7K+m_Tm0q=mr;O{#)2OP*FFgd#q zG&S2z3z!VKOS|4_yo5RAXy&{m1=%@O2j0fFS%Ne?>=kA(iY~qV3PX- zjo}|wj&Bn*r&(t`8j!Y*YF1P~FTJjlb$(p z_vc0aXY7V$ebN)?f1isPylZf|X3^nJ6Y)|7RdO9mp<-S!NKUdW)Sm+e_RDMxB(4|t zYX?mChs?IPEI@Zsf&3qHHxv)(uKh{KKiYZkP|GokN=^}f^=7O(=rX9hL_VCm8+caC z7kSa3sdG=TZhQ~|Vrko+rf*&}QDoA!^BKdXzt z+^mR===;m#|MwH3&=3`|gG`LGXp~ns`DxXDpodUmeA|5EP2;^nvTd2}^T6{K6aCm7duGw*5;m-}%j4`!cRPXH61~g#NRj)_&RXz=Oo6N!GF4GyZ04-qE z=Z*4$URi-$h3sC(3*~;6tW#%h- z?TwYWAL|A(ZGpojt8OS>0D`*D+K%m~;A&|!amj0QyA5u|>HWhCfWtj!WxG!t6YVj7!%=+_bA6x7=LwG1%NJUNS+uJ@+*3Cao!}M{dPvyDu_+Q@kTVM45~B+cESV z){?CB2sa_?V>r3P8!2ssPzmQpV$&PySUr>G3>G)5k!bS{)eHEWkUv#oeHM&&)-@_L zk|or8*ce&o%h){mFgHr1jk(T|B^s54b&%YW3i^RZHj(kp6$oO9&Ews`=V^L}LH6UC zQ}^wQBZOr|wvzZ+Cqh$rFjp93+xWSfr@nQ*Cwz*YD9ls=9~TSRoOvTvy66J86$?FT zbW^)2?K=6%o8U^0LH0qmIZK=dt_^n+_nL;$^T~O#!%7yrzbSf+M_83H@rIBJpzqo8 z@$*RNx?WBbpq$2Ilo)`TI5D@yU<>td4}$nNNb5v&m>-Wbr*<9PFX3DKN7f{Cl-2xt zbU8d;+WRiQn!N|A2~(G;5+1n}(08jj;m&KEl>4kuRI}Ck0AkvPA4Wns)AGi8MC53qmLIrKOOm=j=}o4y~?*WSSVf#y6e8|0bFd(Od2jU# z;p#1H?_|38v=@Yyp2=P+KJ7HK#R4YB=dNUt#l++90kHF6BPHRmMLdl6{|R*UeUbPjs=f@oNFF=kcWvQx z+A|Qzw_{BC&%uXJM}$p~G~0atfH zB}Mw*d)GfZ)9DPQObOz#bkQfmp}_g~-rXLgHZ`YmIp6 z+VAq$h-@MYJ?WxXqCRRw-Yd4s_uXW#8*rc%>rU+5_E|VG){-k9Sa+#%0v4>=Td47S zAaB*Q+T?)nT`ntYp`U;o4FM4>l=-o$8sUAN#-Jrt zP;)eP+=3>VVq}QTxl;T^n0VPs-0Z{BNd*sW)RQ76$Km;QHv_PUTUI141YHc<6+mKb zVpXDJ>%L$$sR9D}QAq`5M`J{zf2k0B(UiIFx;8z+Yz6ciM?pQRoc9owmo>V3>_x{2+#Zc`ZoIz3kiy)=%cb z0yAr?U)D^w2)vY0-O*eG>E>8OyG!KUMoYY$3msUhX+#ZOFv4uF<}R!Rxhm(rQXT7O zLEYu)h^X-Xw$PnqEJucLWYsJX|9%A!;(bIv3(!OZoN*@M3nT{ikEYz)51{7OJ~Jc~ z9uP+R1!|hKbB10|OeQNwKRhmqN+iTzgn7_I5Epz0_u9v&ktNi;(3$?qfb3SmA%cWA zP!K*LS;FgAB7B?eMxB5Q@(?id)MJr<7In^{(RcbX~t~rYu~?F`P4^^nXU!6m5;fYPv5;)FZLV8J%>+isu#G+AjA)xMC|&R9kqN>7dAKUZydni3`nUN zQV!Pu3C53}^j`^uApZIe(5tF)~cC?dkh79pg_Ua4eDSt4s0El6c4WSOLr zB^8Q#&kKEj%lrQy$NL;}I3CYqX72mC@B6yW>-=nMSI^F3b2!dCJQ&kPjYwRhcF3Aj zOOiT3g-P?wU=28ltS;s&E`W8aHpniqJ`NYM;qVY=G1E=6bEW5xym2-vxKe1tdEFxJ zp)o_wvuUL<4x>pS@Iy6zBs}jIth3Q3D|gS$>(Us!kxV%7rPSQ4Iftx? zK6LqYBo07FkECvY3L$UxN#eCvzk2E_wlFmnNyhBxl7bnhCuls%BY;dkE{%WEZ`$T{B)?6Gpl~+Dd z8?diO*CIBs-Uy1_P?VUVLjPGkTzqGFFCFr4FCCk@`t8nY3EO}-MAu{**uNz~RayM$ z&oGr#R_Nj2sqn{SPe(IzEY+-Gisvj0ZSmKeUx2AsV5m2j@;xB(lWU)jY4>Y87|<1h z+G$bg&hP0DF4`p=j5>yCj?dPoUAc=0Gt{4OlnaP@=7 zg=jKT7@}-W&~7?crsJ9NvwkN_uE1B#!-#W-_8H{!hIR5z|2D{mhrEU*mpw+0jz)*d zY{G~x4pZ}m?`c5A(z^X_IV8L(CM0xd?)QASn}56wHqS(FPM}Yx2CE8x#>6{~YMDQ! z5xa<(T7xCQi2Lu}j{KdJd*G!Eo_vVK z9wh3DCE-u3eg(NsQ&o!>M}+YsyS~=ke+F6_1AxH|qrEWj>pnpz=QDsgDm?(SR)WkI zDJXjYRUTjS(iH6r8)V^5oNvL4fNmOHmH8!|N2SFaFHEpN6`D&awRtcU_y*k6Uu5gP zPlbv3Yu~+AWEcXv_s8Hh-EkInvM9O{>++t7KJmf$ z8^T{+5S0?f`wnk~HtzHWH2n8sc1dA!Nvp$e8K-}VHw=e|;m7P!bc}_sfG=t6f5zS#o2O5R#l@0p>C z=d_lsKc%pF-W{j|dC~#8Z^AwKhCGqS$8Be}V`x*~flr(YS~=o*;FuYwEKg?cXforv zuPB9iDV2DO?GGp|2ayGyC)nf#5&#JQj2am=exF9b7^Tq_(_I8#9=wyVe@^?bnfx(j zoESpu7x__V;ES@FVm5vpY~4O=yaH)gH16zPN;8})dc0URxhIek4KP>E3>h*AgLBAi z1-6hmFUA2{_CvNWNGL~15PNHV8LsvGU-l!Pvp!ng5VrFU+x{VCzm)g<8$BH{VR9=J z7X85FA8UT;W&~Hu3H8k{9DI{WX)wfY`O$EZ?gLJQRlt<00ZzlqW4&N}hxiq?6G~ZP z$OWdlH4P*|*d&zS>D+>P85-aW*lG=)Iv;_kc>E(cJq~^a_9NnbOoDlK`Era4#7Zk# z0RTS-4WAkabgcsFV@4Gf+F3R5&WEsEb?o>xo-Y(sd-R#m_S4Yj*m4BOriFc>lJIIPBZ&6b4*|>V2k$d6#a9SpMash80P>lpBZ2g9kgC~%v~U1W zX9hTYPdouqAA+bWkF@bs0)+YyH3sdDeSDGe5!t0WKZbx>GaPXuau&a5+`X&7SSDc_ zS@2JSBZ3c#8vNa=`^QP;$@}2h__`Mq%W61dapN9MPjGic!8Uuh-X9ZH#z5P2%P{F+ z%XG+Gh=g|olZ?PYNXonm7y_sp=unp{Ic|M1>Alsz>{XTJ%MI9 z_P7j)3vYi+;}_t+C`gDWMflE)=;`IcU$=9CSfh3aL{nQ~ft7Jd)}e)8<^Y?bTb_sJ#WheY#tZLGVKygHr%q%XvqG-sOJeJ&Vaw z^K6};r3q}U;1oNN>kfMAQ;MRss5^bl=gE4R#P85qrf{nV;Us`Zqg;Y-;i7QY8doWj z;;!P3#I13*2`P!QF5!H{U-ZGckyM|P5eKvV zJTLlX$0ajxLR5vpH~D&^Z+mR5A+Gs$Ve*bKr-xuCPK90X~Sa-++DC|TenRgHfMWBTC6zr)`BvqR!bse`e*Tu~a3uNp$p`qd#Ll7yqh z>RC(7!=q=$uZg$Cn@;1dZ1X=+=yAPG;My9@&OBv4ZPmigOLD@j4m(3U-sTh>ONFV- z*4l90J5X5XWNvm+^++2l2r^33`?3Wl%{` z96ljs^933$yN&9UqG-P@v9m&-y`XQ4x@(`Vi=5=Uq;88NW+gFkdfk8@(zj9VEwFhY zr6UWJ)gGv{J*tY%g;ujsy!vp=Btn4)|cEUzsTMGk50PyZ64*T5K z7z}8h;b}Yu0s@&VaDrBzOpkRJ)1SJ0f*a{A_#VYjE`VlNc6|SjrTP2%Gj%bs{^!a= z%SPfUA8ibhl<&!G#5{uLaI^5SP6CBci|yjo0@JZc1ijs{BXhItexL|)1VnaU12mc@ zgnku6(;h9ARFK&}7=>=m7tgvM2Q|>vF1=wiA861&rvQOG4HPsVoZMymQ&+?<8!?vG zsgJsUKLNVt*aOCtw~*uv3FRJwx2o6(`&k5*p><=X^NHqsFaU3T>OUC1r6=$Ii$pvKB^8?0X<5Rh5`2^E;{~Q7iL&w*~j`mo5@s z1+F*gn75e~w%m*$p47}QqgXpl7L~y~hD+=*f=N{XTGlA?mgDDzCfevE#K;&PGS}Gv zStR+}o}iRc@NAwer1}8SYdcjXpovZT@C<$d{6k01P=^BbC8AKA$}g0}J8>1*tO=5t z*F%uX}{5Nz0&Ntld%LqTbAyxxuCtCK?+D$>S56aqcC7+-nm!wa;@#5O{o^B5h zpcMWV`@5yjy0vHkS`2v&o=Yd=|D3q>% zCyFOA`#8}PiDNs4?*q0_4Sd}`vN^rgr-@zGn$!UfJP3+s_hBv@232Nx2d@UO z*B?NCN(U+Q=K!&he%|&H2n0OP27i_GZS)$A1ylcv|NffHB~qF7TS-o?u_0#lryOE9|-`aq>Y3kk$IZS$MJ8T^)AO%2x17UQSbQa$znxmY__x%))OgPp?%FSqZGf-&M&5VyrM-6h`T zfX%cAY#fsmx@r<|loY*fkix@TRk!$5VG}(6!UG=a)PI>Rt+GZQojFoo-x2qvv3JRRD zOWy0%Qy6$;->krS&U^j+*X;8Px#{=4!APP8!b@uE7e6QkI+ZW!(3obM3n%t2}Z|ksP5?x{=y7>gclSDCLul&%zzu!O5sa79#^1y}SRKGEd&Ys=%$w)U;IR8i#q2qwN9KHKh z?@od>+%MQ?4kC9UB?OJdf^u)>Fw0)B9cp-?Hh6AkTW(G4Zu$+cef7Em??JgI^yE8; zTCTD!+bzbp@rvR~&H%0XWEZVW49fMiS(YgY3ahzkgTXk6=Y5cqzqrXCAv|I|=lJ%Fj{@mlRk} zjQkbdoFx+wT-wOssXB)mQKA~_!>@We0NmPPH4+w1PCTfH40qbTm+Za$WK}nP+YK&i3-`NkKPYrjR)p3`_VVFyWQG@Fl}bw5o7i z1RLAgSD+!hRQorLo;3LO!rPZ2e-(GVkkHR8>9)Y%y`d7Pr;ONAHwIKs@Zvbb{B|ZZ zn3^)bSQ|#r78qTxAHqq#j8oA9dbR?M??z^ny1a&~o$@*~+85Y>uqNMm$h zkJ}uz>t!N^U<%zsD)ix7fMW;0_OBM+UFy$r-kh! zfmDmZ^hFh^0R?)Te3AmPR$A~xEb^Y+_lFOW6EYx=?Bg}^q-bpxD(^YIwD}om=1l=@ zck+!|6W`HWb)~s}#TmLCJnTtOj8X47)hG=3B!2u*JKKKm{{GAtCGQ_E674NDNbA)G z%_7~3xLWkSy()N1@1to5yAZ4WVN*C~^9rhrvdlex*+ewshWy zHJ}`Iwohq(ktg8~{p)NiX8twP#_>F6Jjyw-Hbb5-_RNbr#Z5Agz$#0qy-7x{p01DC zUsAI+k3~xj*9S%cnd0WICLu4Gym+anJ9|6Jh;a%&rr|F|J*yh!nTR#Y>gtDLw6%H* z_8y8Xf8d~LGVyB@T?nWZPuqCOu0n15+20%A2k2!c{G+jlOa3}+67xHPQPN@Q2hRLj z*6rKAY-YQ+c)j>inXwgvpc?b#(nNZ&GDA&1VXSA$ImAnr`~=4&nWQ#iUUOHO#CD)z z=%S&Z`{gN-rl=nJS%Y5EflJPJqONa!a6~$bsH&YSCS&phwB*G#EM96kaG0;W(N) zDm^2gJ^UuT%AoCI1ot*N%=`9Z!wrETxKLp_XQtA$W@+SXL<6m7XHk_N9q*uU^l6F@ zazjCxu&E!fmtA@6%giHD>o?u5-}&@#n#fFyFFm8_hAi385&4oUt7bWg=rC?QLa{L^vH(F_*p%0(42Hj}uHn5Z4EE=EVLJ@Z!qc_uRF&-Av zLi%flZpwNp-v;_zAM3@gXSdWnuEwrlo|a+y=|c)Ta(946t&D4PMi6$5{4+bCHBKVd zSv*)Y3A*A1@%+$8IjY+y09aS&)1S$jY42y8XIfaWnD*S;W~<$tBO_cfwr$nnIX*Cn zWh4$sr|tY6EvVI9a@b2k3-WL)?(EG!e=@mGFUXnI#mU36aacjO&7}ko&rJ()fOz_V zS#dY&$R9b{Ep&7=yrAuRQrq{WOR-GPv3HkbCu%^>YZBamgbzU({*rUgON} z+^taA?;H~{8b}?oC*z)hQ{guXo+8w;Rl9LIq)8O(buk>u1fFg`kC=}ENAo^yeL(Tw z`!%a=796FMZ~Wqe3>9EpMs-Im?^mZE(ua5W32wsrJGf#(2CN0okePH;lIV@L6L*Sh zZd4LDs>xStz3CxTM^dkZLeMi5pC)SYVD0KKXjBoZcTHBuX?$M zK%aoG*vOUt2D360W^NOcIKlvCEoJ{;hN9c%%B4tbUQ-}#GWsnx#Y4dlR`}|8ec}bA zRj^<@B7RE~Tez9WsF`ktx6aLd+++^ljjpCREx7-FIWxy=hELP*-+`AV82Dk~$HR4- zsLEcol8$O1_2gLJG+CtN5x$02$>YwgjuQ=N>corUcn$jX7BBG63&rz)~a`UJem<|qs)Fetr9x2cOqeKEs3A& z9sV|@eH~WTgWiEkP>u6eHGM9$swZ~5vZZPg>Z(m;z?-@0N~=n=NbygmUt(6ldht*c z&7Zp{NFEUfkxjR-vzWU`N2VnRQP!83syY~#N#EY;^xO2uaF1Hbt%X|~5YPx+?BNZz zu2LlYZqzYw>n<|e&GYz>!L{$?8NQ2yP+L6++f>|=Ps5hFNykWe^&0mZDROBG(&HLs zHVB_pFY+*D-{{C3J;$OYh+}b*j6*-2Vj>H|t?~$5{R7a6*LV0Jp{<3xJd^x=M7$|oB4pl)zuw3uwv|sp zHd+G0hPbSZEoHWaI**zbhvohJ(4%JgI`C648a;&d=w`T5NO*ZPdPm!f%w?=#nLamk zQq#+-FLA!?x(!SCA`i_^;J)-GqKOW>Hh-4_F{7wvEoQBj&~$ji^=w84>|?zxm5H)X zKovKDB3uRC)=5hHYNfqD42}RCXPx-lL<4I{^tf}CtRF)9Oh>fB^pbUFqF6n?U8Dm~v5H{nA&jQ)Vq% zwJCHagHW6!!@KbgE-Z4!@os8kB~avIV3wL8=liBuiYpUk}dT6 zeOX3Xz|k#Au)*iJsaHXq2*+l56i=UW^a$WBRcXGK-4YvsneI-ne6wJy=#ezbQpm*M zO==$gD5z-f%YJR~7S z`4a((r6wR`?6szD-=d|GnG z#`|`Q1Y+>(=YpIFRJn*1IZ%$!Zf;cD-O({(&CX&{++DtL! zSSy)(1aoe$#qX`b#*e_l1pL&k1tB}{(V{#f*rM-`;#hE#h~5)+SFWRCx3+=3PBa`s zRNDFFk_E=2D8S%-p!$Z`jR$sfbJwnSe&)?rPF|oxq-YiFN41_l8?g^tN7mu~BlulQ z3r;@5^Z+A=+6-oq$7A-uGZ6YrkbFJbIw>DOQlDo>NG%p*L8l06VYKR8zwgBlYZ|vk zF7RD#&XlIo{(P}4U_Und(7GV(^O5&_og&}vLN+YoeZ+|6ZoLI$i9ZP6KKv8+*0p@W z!qju^x05JvA;^iGn3E|)CeJ$O4so(j3y8-myr3Ok@501&Y{O4SmvOD@iRO}Y>0kByG}wX@*qQM-S>>8SaO zUD!o{L8G$o=9k5+x!YO?TAxz{N-Q1=X(?S%$#ZFX7x6!3ht#Ez;e-sRe@Iw9tj zRvudMH9MvcZr2ssjuC3ITgrV}2e&I3+NXP6{kF}7VsT#ymJBG7?!jjSWupx7Tp+pq zKlbWjhafr4`*8CCeZCk1;*rUP4@r}y1@6ydEA(T2eokD7IcJ3q~h>p_Qt)Kk~uxbADkHPmr{uU0&aahF-`e)oD z_-tO?P*v&El{Vcb#{}Ip1)l%aO@oh$7SW--8ocjr)zwc3`Z=LOlyV5StTotuSy9|* z+uIy$m&bb1iivEMl%%gl?sDZfdRlf8zQ7kB5wBL1!@NY8j?K@6UmhBeK&GsLb5a57 z$)0guDv(Wn^X03{JyJF4W|jVZoXg);sX$G@=G6&j($IEzc7u=eaT#Ho5H?yhajQ?@ zS_4@6@tvU&Qly@7?aL)c=J80qkf_i%=CX$E-vDeM_ixv9G~C;@0L(w0-a<9IdZ2-{ zD5Z63QV5H1M6~x$uIAzx;OLQ%Inwi(*~k&VUgKtGXzE|8u;F}PXR3bEVqgCH?w~=` zjYr1bV9j$)1Sc+yS->Isp3hVbbXy-oZj&Ash)P$Z?Ck-9PNa_99{Xxib%jFJ0nXjmBz5$6@fv-}46ZW|IOowf4KpCRo zASZwm1ius?^8fd_ct3>8?-mjAg|I6|tWu-lN^taS}ss1o_Y zly#?XV)fWyv8BZ7^-cx;BYmWtQ{8u`#`Ti)@c1h0<6uRC-vh*{D=>olBsRw{9Ui4& z9BnDA-rLeI4Sum^r!8ewLIhI}YU`IDHDMQbvTs?RDU4}_)^$IqtL$sq61Hk``bz^< z?SFtl6FiH3W4FG*Va#Xf2}}m!#4YJaBe%IDoa#3pWH#pP#~31nnk;<#Anvq>Pm}7z zV!t1e#7KtAq%*yPILdIC?k0jiFI-YRe00~2i!Z)_SH%F}DE8olt6+Q*9GWmrvRqqc zvB;8MD;W8xSmlygda8Yoe;A@`ANzr_5HT=WZ`A7|La`Jni@(nHr)huJ75K%8h<_X; z@!_cOX6qr8dwyzaP+VyL2?;m8|IR;p+H#|9PZV(Aj-8Qb$t2r>*oJ<3=6=LvspckPMo;pTaeX$}YS2l<3PiKZTKdgm`^jx*&&w!74 zM1-|R<3^|6>RW*Sw@0tZvs0mP296i5D;BvA<=9$`0=IDa=y<2OU@#u3M6!~W4+Xy$ z<_gk@05eh5xcH!Q-f7PaASp32In zQBbiXcgWUPFiZ+^D1mGwW(8i1C@B5yOIU)a$lVu|ui-JvbR*;?g_1kjNvKtZ6s!Ya zdG{3>Js$!Zl%W5J!iBG)e9ua7DVYOl>cb4rnzmop0P}qahEsNc;|>7x;PZLT^&`kR ze*`6*enx>Mz>w9R1K6T9=$9G>QT1z^2)3aswo)aSu~mAn2BWfaXgP?m_m zSnj=7HMR-E4q>wwBzC+V0B-3M=xS(Ne8vn1Ws&2_K zl8Z(AZziCm;{HR&uYLG4b1@WEs5O8UKKU^@@Q#wYeX8~qSkXL75CxPKHLxatCI`0* zDpjn;I?-i-7YRM|*!bd&aeCTt>2Ul3zC$s{hIKX%@VeRaFM?hLb}Q-0Co9wyTvZ;S=+4H*o77GH_WPU=kYOikh*_7#~~! zsTIwF(B6M8$y~ zXt!5FZ8!f#(W<2h)Psi%{XGTj54nftxO6C!Dl%*=h8@s-xwbo)U&` zQ|m*%0$~E|a3rAcwTA}Q-P>zPSSOiiF;vY?w;%wd!~!JW)HT*+hHyBWOz@iyMvLd2fCl5I<3HCmZ>h%fh8{!Qa0w z@_J(Mci=!&)QV*O1rM%6gWTVO?R;OT6+;5RB*^FPmtKY>>n1g~u9jkyGsfXWblMos zhT&WyR29O5ETDw^u?~HEkHXsiz(u_qptLT05DMsq=&RxA_#lrN;XM@#ssX$>;V=|* zi{)++!t!gAZ|u1yQfz!e9kE{jH{tEzju`wssCjDO39km&f8hx0AF=a5MSi<8A9|VS zA>@X8+^0)-8Fa}FL`sEp`8`<^e8t1`ZtLKN?i`H&&3i!aefrG- zs}I+}q|Uqw1p&yXwz$`7Jpw)J0761*`R*>KRiL_515$?&33J=+JBvR`Lj4itq^s1@ zAXkO;n=8`L7X!O0tW-GFrnqWk?WPD@aE#Ksa46!tE)zDE;v^N|Y6*(x+-Ut26rG2WIcrFxqjfI+JV&uv%WuAX?ubh zLE4a|88(?$@4Jc6uJEDDn}v4)z2%bq)61b9sdWAO(ogrcQu7PG1KZ$)V0xAL(VC@C z=Aq?};6{CD)syn{MvMCO;#Vg@;+mKGS_fqFUoE>|L9H?gDydXYtApj+ulbll%nk5t z0~OViJl+bwVP8E-Jc^jYILmSE$Byu7f??+X6n$b|p_gLB0uBM39jZcf267>C0-t=k zsl>1L?5_0b+gI@&e~~03BKYQ>YyBuC@^Wh&^Tmr`0`c`Ui7iwY)B^*+?K_dh8xTG; z(>R7!p-EUA{9(`P4JcwhSx%lSaXGlAcZ7_AwZBmjUq3X zCIB@y|MKi|F&&7M$9sPO19%d5`8Lp&YN^s+ifvxgKB%0$t z>~Q9jv<5hTWk|-4@_NnR6mq~spKNO~0u$W&0@AJkcIoUZ**g=$u`i+IcuW3)@=q(l z$>ZV0nLd=e=UFw(`cMOz_Z@s7!ox;;4NU0tH4Qu1L8AzH)h4iN&h^!i4%$V_W|5PB zi)M*}Ib{)|bg>ujzhT;E!WhzQIXro|e>-pROo~D6hpfk16#%}WnX+%HKeSqQ|C63s z21G=Gzttq%&!?b97@NY{Yuk_i?FjvIPZaV8BgLtqkxX-Sh{aYt<4;?jtzn9ZhAC#v zdJ_Y|uth9RdN@}wjF?0ZOPm32uoi_|qDvIEU+Nycj$Xon+rO$cX-rd*dqaM$6wfbh zy;(=I-|yc4IX3wY$H!pkjpq!nCx>vi??95lBdq2e5>0Nj`}jT0%{rl@U!`?W5>LtLuqa`Ocr*qPS}Byfr2h> z$y>pTJG1rJDcE71QVi!26<23E(I~CgW@+9M{o~2B&gk91KgxV1+hRUm+gof`J%ph* z(U&$;z0KQ{qDT53*R3g0o4kZu6OcMwcM7DNS1q2N9fC4YeJ_)W6S(62ycERi;MiY@ z_guIZbX8W7fESR;qGpRLJDy_~iKdGgatn#%85YU1<%R`x6MuFW?ro;^Ob$H!IPeNI zlA~Fl12gPlB?ZEJ@jblwWJxPT(!@wCYDiFL;5sbfAJ+wKnLGO=c8EiAGGp!0wx-KQ zmsd)4`dV1o4mB3VMeEJrhnTKuTr7F?>$d&50IzRu0X$EG&lfgkGy)mwDm_fOfmwTm zsccfa>Q!y;DT(R2(o|rozgEZMSXHT>Lkd2CD^7iA3u|bIQ1{|zS0L6#Dqjk>D-CSc zf)dH_f@TH})p`1S(##C7r`K-?na^dHrQHu8vGIHcGu`sa{K0-G%^F$57ZA-m&5-7T zLWu?yE<}D2c#YRWo^-@5C;mt^h!~}DFpK`9WfJ}))?^{3{T34Sr#%bD?yXhB2@}_E zb#Q_Ge7i}GsRWVI33TRa1oYNLC~LIvOeO_r6L3LuQj>%#vl#&iaaK=8+TiN@()LYHKRYOwj*SmMF z{>2fVmYu@a^2My9Rx5?P`>>B;lyWH|NWejpg*~r7uBtwO=t&zQp5aq(eLE6Dc~86H z9ZF&^1cMDy_!^THv1oL4v{y5jS=_q*{OXdiIY#t-ppQ3C=)ADQ>sGOC z5gPM{?9{%cO~Cu8k{BI%bm6~*qGce|;J2vK9la9e9bXx}ykCN$rJ?R%srA0u=*>)n zkCMDFdn2ASs}W_QakT>!T*J-`A#;XfM2GE*-h!Bxh}r4q8HUM;--edJ3iQg*IS>FR zG4o=Hks5KdRBwr+Hm5->Kq5m5?l*~Ff$mQ;4(Z5xv3czb?0upj=93ThLqeO{ZC^lj z{_cLvW!ZvAsp~VKa=@#%)Gif-4qixZWp;|-644F_GOpriV!JSJjN|l*e6wgsuo89u zn?zrVrXJo)4w0DRaGy|XI>_hMT{1{AnvujcB^yq0cV9+Sy7@?e?9NrHbWZyx#o2H z=+?0Y|7wjc;ZZ&M-NW^dkX7A8Re(!g9>-aW^&C!@7fH{pKHT&DTi1nKsXw=%u*9(| zw+(se@=RDRiATI^!U248-#odX4J~V#Nj!ham^r5Ut@ZCqyF3k3H`x}Ds4e`Iw#Vc> zoU|AJq^2?ffC*fb$@;ckD;}?9thc+Jqn0F>5nsugbxcZS*XCV>w(Nik!i;KtDo&UI zxE>%-&KC{vG8(MT9GEQw!H+vYNgVSpvP%?q8?`-*P^zcUGHEZy$$QNLHC+;Ij z>Rd}@iV3%gNso}zmCCVy{Tvd0Gr~)bD#ahfVhqoj5dxK8Q*xBCT?5v#{91+W5r(W; z5>-Eg0`YidoNW#@(P|09vr&HSeK37b?oRGa4&G9M$`wR)rmBIEe13bDgw)kw&_hiG zG;!f{hkomk?b{#Q(zG=gu?>=OnH*vU4lLSwxcG)UM}P);FtT}*uQp;n7!%&dUv>{q z~nA09^#1$B5F@hLWo<_ z5hMAS8gOy~zz|PpTaK|bS@Rxn4LF+UkxWTtv5$&1O5xtwxe+SzYX*^0 zPfkG*z2xa%;2yVTbBXi%yve}F;Rmj*YM=F|+4B&m^^n zmh=i^dgRPZLpZTawPMplIV4_%zoC*sgfl~KFCngGJ1Y+-Ef)Amj336*oXK%8!CT)u z7BA^{1cGyZUfSjQ9JVgc<08)EVJQ}bwhG4N2r#ZR_17QO-YUltPoDug>Bq?S3Xi_7 z+RcbhF$&5#zrkg(AFeqNdUg}rP`YUSaNqFKY5kr2}(3Ff816i)6Uf4sXu&iH2;D@i5(j^Dnw8mQPCcV}Z7IU_yNG!94`? z-2#J$>XAcaPdp_7Z3xnSN9@YF0CoDnxA4T{%u0BXVFmCNa3KV@@N3m_v^snQ{X&Hg zaiX9L9uoPp=V#Up%r>pa$6;GfFoNrVZL<-%gTp4mWngwy-N2m}Va+%?P!H!RB- zY_N^U6vj!T?J__!zR)i7@7OVy8J}SxKuG{v)>BAVa5?yx#XJQ(`7?xZO;`s4gF(o~ zjb7Oi^wS|r^un`QMipp2MjY69S!QTrl9p*isq-^|4zgupe0|8Vms^GH$#>olH-a2e z?v_-qv=!!|r@*ait?z;l!}E7ChepR|MBcg%q$(-(PmEMT2Wzz)qTkOl92I2vw5GcW zZ8tAxhd-j<)n9M@&B_mQIe=Qn`4q5Yi^hVS&+^gyVg~8Br5_+Y(^i-+Ah3@|^Ga^k zlEW}(tw&r(l)s5Q@D$9U^G06V#1^P|=KHu4yRjQ`=apVJfLQU+fH?Wc@<L9eU0AL*o%dTucL9$qff!t=lcoDU7dD*=u;10UhBE$0H z$0Dy*H>6BDgf992{uCp60o-x-rP1D)=b07<_|$`<@;%i*(B5jbX+%QnJLB@XHBjmE zO%MJNnmX`0$Qt@(n$I$ybYSyD zibo7HCZ}dMydlv{+e^gR;tzZEJOp**t znq6Z(5Fiep_#O*34;JyXwQ{frpn{X-thAJTRbr(J)P(G*8vWt*63AkEE9c&3SGb-Ayv%GXlBJz+=tkrqkDA>5cOn z%^pIlB}J)aaid#C{f8!@jXXAJjzgV8c!8D`_;mYL_C?bQVRjxxC>;PjXg$E+It{Qv zdL^w#?#mf)N3TImNb$4ZUwT5U!(?H8LOHdB<*Wlz8-rO!6`+&7HPe5B;8`q2ZWyRb zfA4t5Db!2}f-KE`hz|V>)73G-qX;Be7VWGvN!wgD>+*N|K}Nc6=%a_9Ze$c~C|>Tb zfim!6oMH{V4qVb@-dYGAg(-gWiKv$!idq#%N|+wdOjklIW)CbKry;DT8d`f*u&uBq zMS0Z=iU8)ea*}!^s39tJR<2yI2|<|D3R<`k)FkLxZHYXDjC3?mSFBpW0dZs<|41W* z>7aJJskYqsEc9X|&0o`%sDPxlUjy4d8E=%&VGq3-q=4cs3;%+e4W7^&n}n&)%$QLK zNJ3=HGIbVYn58PD3*O>SJO4m0-LoTWhm14AZBc%e%yaLrKs@gPMCPfQYi9%32AzKU zGoP-r6i3RVk`5 zf-Pg{TzZ%W_9V#8Ji&nzAto##4v&HtXe72Qp#aNHyu|qOB>cA+8Xkd{Iz#2N#9-F< z1z6;PGCwvBb)-RA?xK{$titSc6x#8VJfZUu_H0hDv6PrhcS-Pp-r{L!q{O{=n3HzW z*7e7K8mQ5r$py@mQ|+4Mayu$f5YsnkEx9F8`;rB>h=X7>8<>Rpp^ntU`H?uY|FCRY zIIJ3*N1(w~X(99$#qDC6LB@FG?^y($Aq5L`xvoo5YvD~ogW=OnS{G8Y%d*6LKeqcr zt@+M<5z>lPOHzsAf~7MWL(i^WqS}StvtsBKUi33?FCm8(QQGC(tDuMjWoVN z@C7GdP2Go8*Ospf+JrBfb|7WQAa}s-U=U|`!m6h*?MsV=`1C7QxHeraiI>ccQU{Wl zldglC%_PXTONULfbq4$)Gl#qv@o=`mGV)@y05pJ;kjCTD#0>GeBv-cT@9Z4ek|)rC zKE3@f=qh|s;)1lW^qismuslE-YI|9dfM?ibLgyi~r1ioACmX#1nJ%!4(dsSMo?4~t zjD0{g;VdYH*7}ajp}qzT@FAA<@JuX;xH|r4dUOMvIEi($I9R;EJ}J>2(Zji$-=9yaw)9S3W6*9a4VOovfcj z>QQnm!BT_W9a2&bK0D#CdU$w6m?T#SOz4TpNykk9q~PY62&GRto=-_%evk+ggyIce zA+JhMr(Z5P^B?S+&IyNoWw_8`^;v5;*NONP&IzdeBMB$jo<~zMOHIXaQ_J4~fnEwcow!VfJqfBVk1H@tuCLtCPEca2V_} zq`BfYLf^LQgi7dq^M)-R<8wOUQt^etiTZAf1K&^uXF Date: Wed, 2 Apr 2025 16:11:32 +0300 Subject: [PATCH 6/6] MS-828 Cleanup infra and feature module READMEs --- feature/matcher/README.md | 31 ++++++++----------- id/README.md | 11 ------- .../infra/events/event/local/README.md | 9 ------ infra/logging/README.md | 16 +++++----- .../com/simprints/infra/logging/Simber.kt | 4 +-- infra/ui-base/README.md | 3 +- 6 files changed, 24 insertions(+), 50 deletions(-) delete mode 100644 id/README.md delete mode 100644 infra/events/src/main/java/com/simprints/infra/events/event/local/README.md diff --git a/feature/matcher/README.md b/feature/matcher/README.md index 37c7bc08c4..de42784a67 100644 --- a/feature/matcher/README.md +++ b/feature/matcher/README.md @@ -1,13 +1,13 @@ # How matching works -Calling feature module provides `MatchParams` in navigation arguments with a list of -either `FaceSample` or `FingerprintSample` and a query to load the candidates from the database. +Calling feature module provides `MatchParams` in navigation arguments with a list of either `FaceSample` or `FingerprintSample` and a query +to load the candidates from the database. -Matching needs to compare the probes templates with all the candidates templates and return a list -of X candidates sorted descending by their higher comparison scores. +Matching needs to compare the probes templates with all the candidates templates and return a list of X candidates sorted descending by +their higher comparison scores. -- Sample list is provided because the Capture flow can return more than one capture (depending on - the configuration). Each probe has its own template. +- Sample list is provided because the Capture flow can return more than one capture (depending on the configuration). Each probe has its own + template. - Each candidate have a guid and a list of samples. What this means is that the number of comparisons will be: @@ -16,21 +16,17 @@ What this means is that the number of comparisons will be: p * c * cs ``` -p - number of probes -c - number of candidates -cs - number of samples per candidate +p** - number of probes, **c** - number of candidates, **cs** - number of samples per candidate Example: if the configuration say that each capture has 2 samples, and the database already has 3 people, 12 comparisons will be made (2 * 3 * 2). ## Sorting the comparison scores -For response, calling module expects a MatchResult with a list of a guid and confidence score pairs. -This means that no matter how many samples the candidate has, only the highest comparison score will -be return. +For response, calling module expects a MatchResult with a list of a guid and confidence score pairs. This means that no matter how many +samples the candidate has, only the highest comparison score will be return. -What is being done is during a comparison of probes against a candidate, only the highest score of -all is kept and added to the MatchResult. +What is being done is during a comparison of probes against a candidate, only the highest score of all is kept and added to the MatchResult. Example: @@ -50,7 +46,6 @@ the `mean` down as (1 + 0) / 2 = 0.5. # Concurrency -Because the process of matching can be expensive - it needs to match `n` probes against `f` -candidate faces - we tried to run it in parallel. That way, we can run multiple matching at the -same time. Also, since the list will be ordered later, we don't need to care about the order that -the results are returned as well. +Because the process of matching can be expensive - it needs to match `n` probes against `f` candidate faces - we tried to run it in batches +in parallel. +That way, we can run multiple matching at the same time. Also, to reduce memory pressure the results are stored in a limited sorted tree. diff --git a/id/README.md b/id/README.md deleted file mode 100644 index 88cb9ae0ab..0000000000 --- a/id/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Simprints ID - -This document should be a table of contents for documents inside the [doc](doc) folder, where the documentation for different SimprintsID features can live. - -## TOC - -- [Modularisation document](https://docs.google.com/document/d/1E-SNLGbqsAjn1IVamQhDEcWQJ092tLnNLLeTCo_A160/edit) -- [How the Exit Form flow should work (refusal flow)](/id/doc/exit_form.md) -- [SimprintsID Request/Response](/id/doc/spid_request_response.md) -- [ID Orchestrator Flow](/id/src/main/java/com/simprints/id/orchestrator/README.md) -- [Modalities configuration](/id/doc/modalities_configuration.md) \ No newline at end of file diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/local/README.md b/infra/events/src/main/java/com/simprints/infra/events/event/local/README.md deleted file mode 100644 index 6cfb3561c6..0000000000 --- a/infra/events/src/main/java/com/simprints/infra/events/event/local/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Troubleshooting Room db with events -The events db is room based and encrypted, so to explore the content of the db you need to created a db that is not encrypted: - -* Uninstall SID (if it's installed) -* Go to `EventRoomDatabase.kt` and comment out `.openHelperFactory(factory)` -* Install SID -* Open the Database Inspector in Android studio and go to `dbevents/DbEvent` - -Note: you can't access a db that was already encrypted with a password that was generated by SID diff --git a/infra/logging/README.md b/infra/logging/README.md index 239eeaf08c..8a1a126bb2 100644 --- a/infra/logging/README.md +++ b/infra/logging/README.md @@ -1,13 +1,15 @@ # Logging (a.k.a Simber) -The logging module is a very lightweight wrapper around Timber which has a few goals: -- A very very lightweight wrapper around Jake Wharton’s Timber in case we ever decide to use a different logging tool +The logging module is a very lightweight wrapper in case we ever decide to use a different logging tool + - Clearly document how logging behaves with each build type. -- Isolate the Timber, Firebase Crashlytics and Firebase Analytics dependencies to a single module. This will make it significantly easier for custom versions of SID to remove dependencies on Firebase if they need to. +- Isolate the logging tool, Firebase Crashlytics and Firebase Analytics dependencies to a single module. This will make it significantly + easier for custom versions of SID to remove dependencies on Firebase if they need to. -Access to logging is done solely through the Simber class. See [Simber.kt](src/main/java/com/simprints/logging/Simber.kt) to get started. +Access to logging is done solely through the Simber class. See [Simber.kt](src/main/java/com/simprints/infra/logging/Simber.kt) to get +started. Refs: -See Timber -See Crashlytics -See Firebase Analytics + +* See [Crashlytics](https://firebase.google.com/docs/crashlytics/customize-crash-reports?platform=android) +* See [Firebase Analytics](https://firebase.google.com/docs/analytics/user-properties?platform=android) diff --git a/infra/logging/src/main/java/com/simprints/infra/logging/Simber.kt b/infra/logging/src/main/java/com/simprints/infra/logging/Simber.kt index 0fbe8e4dd2..54da5d2f5c 100644 --- a/infra/logging/src/main/java/com/simprints/infra/logging/Simber.kt +++ b/infra/logging/src/main/java/com/simprints/infra/logging/Simber.kt @@ -9,9 +9,7 @@ import javax.net.ssl.SSLHandshakeException import javax.net.ssl.SSLProtocolException /** - * A very lightweight wrapper around Timber in case we ever decide to use a different logging - * library. - * @see Timber + * A very lightweight wrapper around logging library in case we ever decide to use a different one. */ object Simber { /** diff --git a/infra/ui-base/README.md b/infra/ui-base/README.md index c967cabeeb..99adb4202f 100644 --- a/infra/ui-base/README.md +++ b/infra/ui-base/README.md @@ -4,8 +4,7 @@ This module serves as a common foundation for all modules with any kind on UI. Things that should be stored here: -* Common UI dependencies that are used in all/most feature modules shared via `api()` declaration in - modules build file +* Common UI dependencies that are used in all/most feature modules shared via `api()` declaration in modules build file * Generic utilities that simplify work with UI-related Android API Things that must not be in this module: