From fe5a0a0cabef216f0b0fc55a9c7743c8a55711f8 Mon Sep 17 00:00:00 2001 From: josephatcatalysis <106772721+josephatcatalysis@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:14:07 -0700 Subject: [PATCH 1/8] Updated Blog 7.4 --- ...-time-voice-sentiment-analysis-system-1.md | 442 ++++++++++++++++++ .../60-days-of-ia/blogs/2024-04-17/7-4-1.png | Bin 0 -> 56252 bytes .../60-days-of-ia/blogs/2024-04-17/7-4-2.png | Bin 0 -> 38141 bytes 3 files changed, 442 insertions(+) create mode 100644 website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md create mode 100644 website/static/img/60-days-of-ia/blogs/2024-04-17/7-4-1.png create mode 100644 website/static/img/60-days-of-ia/blogs/2024-04-17/7-4-2.png diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md new file mode 100644 index 0000000000..bf1320edd0 --- /dev/null +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md @@ -0,0 +1,442 @@ +--- +date: 2024-04-17T09:00 +slug: real-time-voice-sentiment-analysis-system-1 +title: "7.4 Real-time Voice Sentiment Analysis System 1" +authors: [cnteam] +draft: true +hide_table_of_contents: false +toc_min_heading_level: 2 +toc_max_heading_level: 3 +keywords: [Cloud, Data, AI, AI/ML, intelligent apps, cloud-native, 60-days, enterprise apps, digital experiences, app modernization, serverless, ai apps] +image: https://github.com/Azure/Cloud-Native/blob/main/website/static/img/ogImage.png +description: "In today's fast-paced digital world, understanding customer sentiment in real-time during voice calls can provide businesses with a competitive edge. This guide will show developers how to build a robust real-time voice sentiment analysis application using several key Azure services." +tags: [Build-Intelligent-Apps, 60-days-of-IA, learn-live, hack-together, community-buzz, ask-the-expert, azure-kubernetes-service, azure-functions, azure-openai, azure-container-apps, azure-cosmos-db, github-copilot, github-codespaces, github-actions] +--- + + + + + + + + + + + + + + + + + + +![Graphic with a chat bubble-meets-robot head in the top right corner. At the bottom of the graphic is text that reads, "Personalizing Education with Generative AI and Retrieval Augmented Generation: Creating the Chatbot."](../../static/img/60-days-of-ia/blogs/2024-04-10/7-3-1.jpeg) + +## Real-time Voice Sentiment Analysis System Using Azure Communication Services, Azure AI and Azure OpenAI (Part 1) + +### Introduction + +In today's fast-paced digital world, understanding customer sentiment in real-time during voice calls can provide businesses with a competitive edge. This guide will show developers how to build a robust real-time voice sentiment analysis application using several key Azure services. Specifically, we'll leverage: + +* [Azure Container Apps](https://learn.microsoft.com/azure/container-apps/overview?ocid=buildia24_60days_blogs) to deploy the backend web API +* ASP.NET Core web API backend for processing +* [Azure Communication Services](https://learn.microsoft.com/azure/communication-services/overview?ocid=buildia24_60days_blogs) for handling voice calls +* [Azure AI Language](https://learn.microsoft.com/azure/ai-services/language-service/overview?ocid=buildia24_60days_blogs) and [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview?ocid=buildia24_60days_blogs) for analyzing sentiment +* Node.js frontend for user interactions. + +#### Architecture diagram + +![a diagram of the voice sentiment analysis application architecture](../../static/img/60-days-of-ia/blogs/2024-04-17/7-4-1.png) + +#### Frontend of the sample app + +![image of the Sentiment Score in a sample application](../../static/img/60-days-of-ia/blogs/2024-04-17/7-4-2.png) + +The sample app will manage Voice over IP (VoIP) and Public Switched Telephone Network (PSTN) calling capabilities, converting speech to text on the fly, and will evaluate the sentiment of the conversation in real-time. There are many scenarios where these capabilities could be used: + +1. **Customer Support Call Centers**: Analyze customer sentiment in real-time during support calls, allowing agents to adjust their approach based on the emotional tone of the conversation. This can lead to improved customer satisfaction and faster resolution times. + +1. **Market Research Interviews**: Conduct telephonic interviews with participants and use sentiment analysis to gauge reactions to new product ideas or advertisements, providing valuable insights into market trends and consumer preferences. + +1. **Telehealth Services**: In virtual healthcare consultations, use the system to assess the sentiment of patients as they describe their symptoms or concerns. This can help healthcare providers better understand the patient's emotional state and potentially improve diagnosis and patient care. + +1. **Remote Education and E-Learning**: For online classes or training sessions, analyze the sentiment of students' responses during voice interactions to assess engagement, comprehension, and the effectiveness of the teaching material. + +1. **Financial Services**: Use in banking and financial advice call centers to detect customer sentiment during calls, identifying potential issues or opportunities for additional services based on the emotional tone of the customer. + +1. **Sales and Lead Generation**: During sales calls, analyze potential customers' sentiment to tailor the pitch dynamically, improving conversion rates by aligning with the customers' emotional responses. + +By the end of this blog, you will understand how to: + +* Set up and manage voice calling with Azure Communication Services. +* Convert speech to text using Azure Communication Service Calling Captions. +* Perform sentiment analysis on the text with both Azure AI Language and Azure Open AI Service. +* Use ASP.NET Core and Node.js to tie these services together in a seamless application. + +Let's get started. + +### Prerequisites + +#### Tools and Accounts Required: + +1. **Azure Account**: An active Azure account is required to access Azure services like Azure Communication Services, Azure AI Language, and Azure OpenAI. If you don't already have an account, you can sign up for a free trial [here](https://azure.com/free?ocid=buildia24_60days_blogs). + +1. **Visual Studio Code**: This powerful and lightweight code editor from Microsoft supports development in multiple languages including Node.js and C#. Download it from [here](https://code.visualstudio.com/). + +1. **Azure CLI**: The Azure Command Line Interface is a set of commands used to manage Azure resources directly from the terminal or command prompt. Download and installation instructions can be found [here](https://docs.microsoft.com/cli/azure/install-azure-cli?ocid=buildia24_60days_blogs). + +1. **Postman**: An API development tool that makes it easier to create, share, test, and document APIs. This is optional but recommended for testing your application’s back-end services. Download Postman from [here](https://www.postman.com/downloads/). + +1. **Node.js**: Node.js is a JavaScript runtime that allows you to build scalable network applications. It's essential for developing the front-end of our application. Download and install it from [here](https://nodejs.org/). + +1. **Docker**: Docker is a platform for developing, shipping, and running applications. We'll use it for containerizing and deploying our ASP.NET Core backend. Download and install it from [here](https://www.docker.com/products/docker-desktop). + +##### VS Code Extensions: + +1. **C# Dev Kit for Visual Studio Code**: While VS Code does support C# development, this specific extension provides advanced features and functionalities tailored for C# development within Azure. It's especially handy for Azure Functions development in C#. Install it from [here](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit). + +1. **Azure Tools**: This extension provides a set of tools for working with Azure services directly from VS Code. It's a must-have for managing Azure resources and services. Install it from [here](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-node-azure-pack). + +1. **Docker Extension**: This extension provides a set of tools for working with Docker containers directly from VS Code. It's essential for containerizing and deploying our ASP.NET Core backend. Install it from [here](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker). + +1. **Azure Container Apps Extension**: This extension provides a set of tools for working with Azure Container Apps directly from VS Code. It's essential for deploying our ASP.NET Core backend. Install it from [here](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurecontainerapps). + +1. **Azure Static Web Apps Extension**: This extension provides a set of tools for working with Azure Static Web Apps directly from VS Code. It's essential for deploying our Node.js frontend. Install it from [here](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurestaticwebapps). + +:::info +Checkout the demo bytes for [Intelligent Apps](https://developer.microsoft.com/en-us/reactor/series/S-1308/?wt.mc_id=blog_S-1308_webpage_reactor&ocid=buildia24_60days_blogs) with Azure Container Apps where the product team gives a walkthrough on using open-source vector databases and building a multi-LLM chat application. +::: + +### Creating Azure Resources + +With the prerequisites set, the next step is to create necessary Azure resources. These resources include Azure Communication Services for handling voice calls, Azure AI Language for sentiment analysis, and Azure OpenAI as an alternative for more complex sentiment analysis tasks. Here’s how to set each of these up. + +#### Azure Communication Services Resource + +**Azure Communication Services (ACS)** will manage all aspects of voice calling within our application. To create a new ACS resource: + +1. **Open Azure Portal**: Navigate to the [Azure Portal](https://portal.azure.com) and sign in with your Azure account. +1. **Create Resource**: Click on the "+ Create a resource" button found on the upper-left corner of the Azure Portal dashboard. +1. **Search for Communication Services**: In the "Search the Marketplace" field, enter "Communication Services" and select it from the dropdown list. +1. **Create**: Click the "Create" button. + 1. On the "Basics" tab, fill in the required fields: + + 1. **Subscription**: Choose your Azure subscription. + 1. **Resource group**: Create a new resource group or select an existing one. + 1. **Resource name**: Enter a name for your Communication Services resource. + 1. **Region**: Select a region near you or your target audience. + 1. Click "Review + create" to review your settings and then click the "Create" button to provision the resource. +1. **Configure**: + 1. Once the deployment is complete, navigate to your resource. + 1. Under the "Keys and Endpoint" section, note down your connection string. You'll need this to configure the backend of your application. +1. **Get a UserToken** - For initial frontend testing, we need a user token. In your Azure Communication Services resource, navigate to the "Identities & User Access Tokens" section and create a new user with 'Voice and video calling (VOIP)' services. Note down the user token for use later in the tutorial. + +#### Obtain a Free Trial Phone Number + +To enable PSTN calling capabilities (optional), you'll need a phone number. Azure Communication Services offers a free trial phone number for testing purposes. Here's how to obtain one: + +1. **Navigate to Your ACS Resource**: Sign in to the [Azure Portal]() and go to your newly created Azure Communication Services resource. + +1. **Phone Numbers Section**: On the left-hand menu of your ACS resource overview page, find and select the **Phone Numbers** option. This section allows you to manage phone numbers associated with your ACS resource. + +1. **Get Trial Number**: Inside the Phone Numbers section, you'll see an option to get a free trial phone number. Click on the **Get** button next to the Free Trial Number. Azure will prompt you with the terms and conditions for using a trial number. Please read through these carefully as they contain important information regarding the limitations and permitted use of the free trial number. + +1. **Confirmation**: After accepting the terms, Azure will automatically allocate a trial phone number to your ACS resource. This process may take a few moments. Once completed, the number will be displayed on the screen. Note this number as you will need it for configuring voice calling capabilities in your application. + +* **Note**: The free trial phone number comes with certain limitations. For example, there may be restrictions on the number of calls that can be made or received, call duration, and available features compared to a purchased number. These restrictions are in place to manage the service's use during the trial period. + +* **Note**: If you don't, or can't, obtain a free trial phone number, you can still proceed with the rest of the tutorial. The application will work with VoIP calling capabilities, and you can always add a phone number later if needed. Either clone and run the [Azure Communication Service Calling Quickstart](https://learn.microsoft.com/azure/communication-services/quickstarts/voice-video-calling/getting-started-with-calling?tabs=uwp&pivots=platform-web&ocid=buildia24_60days_blogs) or the [Calling Hero App](https://learn.microsoft.com/azure/communication-services/samples/calling-hero-sample?pivots=platform-web&ocid=buildia24_60days_blogs) and use a user ID or group ID respective when making a call. + +Note down the phone number for use later in the tutorial. + +#### Azure AI Resource + +For basic sentiment analysis, we'll use **Azure AI Language** services. Here's how to set up this resource: + +1. **Create Resource**: From the Azure Portal dashboard, click "+ Create a resource". +1. **Search for AI Language**: Type "Language" in the search bar and select "Language" from the results. +1. **Create**: Press the "Create" button. + 1. Complete the form: + 1. **Subscription** and **Resource group**: As before. + 1. **Name**: Give your AI Language service a unique name. + 1. **Pricing tier**: Select the pricing tier that fits your needs (you can start with the free tier for testing purposes). + 1. **Region**: Choose the same region as your Communication Services to minimize latency. + 1. Proceed to "Review + create" and then click "Create". +1. **Configure**: + 1. After your Language resource is deployed, go to it and note down the Key1 and Endpoint from the "Keys and endpoint" section. + +#### Azure OpenAI Resource + +To leverage more advanced AI capabilities, we also integrate **Azure OpenAI Service**. Setting up this resource involves: + +1. **Create Resource**: Again, from the Azure Portal dashboard, click "+ Create a resource". +1. **Search for OpenAI**: Enter "OpenAI" in the search field and select "OpenAI Service" from the dropdown. +1. **Create**: Hit the "Create" button. + 1. Fill in the details like you did for the Language service, taking special note of the **Subscription**, **Resource group**, **Name**, **Pricing tier**, and **Region**. + 1. Review your settings and click "Create". +1. **Configure**: Before you can generate text or inference, you need to deploy a model. You can select from one of several available models in Azure OpenAI Studio. To deploy a model, follow these steps: + * Sign in to [Azure OpenAI Studio](https://oai.azure.com/) + * Choose the subscription and the Azure OpenAI resource to work with and select Use resource. + * Under Management select Deployments. + * Select Create new deployment and configure the following fields: Model, Deployment name +1. **Keys**: Once deployed, navigate to the OpenAI resource in Azure Portal. - In the "Keys and endpoint" section, copy Key1 and Endpoint. These will be crucial for integrating advanced sentiment analysis into your application. + +### Developing the Node.js Frontend + +In this section, we'll dive into creating the front-end of our real-time voice sentiment analysis application using Node.js. The front end will provide a simple yet functional user interface (UI) for making voice calls and displaying closed captions generated from the conversation. We'll also ensure it can display the sentiment analysis results in real-time. + +#### Setting Up Your Node.js Environment + +First, ensure Node.js is installed on your development machine. If not, download and install it from the [official Node.js website](https://nodejs.org/). With Node.js installed, you can now set up your project environment. + +1. Create a New Directory: Create a new folder for your project. This is where all your Node.js front-end files will reside. + + `bash` + ``` + mkdir voice-sentiment + cd voice-sentiment + ``` + +1. **Initialize Your Project**: Run the following command to create a `package.json` file, which will keep track of your project dependencies. + + `bash` + ``` + npm init -y + ``` + +1. **Install Parcel**: We'll use Parcel, a web application bundler, for a seamless development experience. Install Parcel as a development dependency. + + `bash` + ``` + npm install parcel --save-dev + ``` + +1. **Install Azure Communication Calling SDK**: This SDK is essential for integrating voice calling features in our front-end. + + `bash` + ``` + npm install @azure/communication-calling --save + ``` + +#### Creating the UI for Calling + +Now, let's create the basic structure of our frontend application. This includes setting up HTML, CSS, and JavaScript files. + +1. **HTML**: Create an index.html file in your project root. This file will serve as the entry point for your application. + + `bash` + ``` + + + + + + Real-time Voice Sentiment Analysis + + + +
+ + + +
+
+ + + + ``` + +1. **CSS**: Create a `styles.css` file to style your application. Feel free to customize the styles as per your preference. + + `css` + ``` + body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: #f5f5f5; + } + + #app { + text-align: center; + } + + input[type="text"], button { + padding: 10px; + margin: 10px; + border: 1px solid #ccc; + border-radius: 5px; + } + + #captionsArea { + margin-top: 20px; + } + ``` + +1. **JavaScript**: Create an `app.js` file. This is where we'll write the logic for initializing voice calling and handling sentiment analysis. +* Let's start by setting up a basic structure for making and ending calls using the Azure Communication Calling SDK you previously installed. + + `javascript` + ``` + import { CallClient, CallAgent } from '@azure/communication-calling'; + + let callAgent; + let acsPhoneNumber; + let tokenCredential; + let captions; + + const calleeInput = document.getElementById('callee-phone-input'); + const callButton = document.getElementById('call-phone-button'); + const hangUpButton = document.getElementById('hang-up-phone-button'); + + async function initCallAgent() { + const callClient = new CallClient(); + + // Hard code token for now, will be replaced with actual token later + tokenCredential = new AzureCommunicationTokenCredential("<>"); + acsPhoneNumber = "<>"; + + callAgent = await callClient.createCallAgent(tokenCredential); + attachCallListeners(); + } + + function attachCallListeners() { + callButton.addEventListener('click', startCall); + hangUpButton.addEventListener('click', endCall); + } + + async function startCall() { + const callee = calleeInput.value; + if (callee) { + const call = callAgent.startCall([{phoneNumber: callee}]); + // More call handling code here + } + } + + async function endCall() { + if (call) { + await call.hangUp({forEveryone: true}); + // toggle button states + hangUpPhoneButton.disabled = true; + callPhoneButton.disabled = false; + } + } + + initCallAgent(); + ``` + +* **Note**: Make sure to replace placeholders like `tokenCredential` with actual values obtained from your backend or Azure Communication Services resource. + +#### Testing and Running Your Application + +1. **Run Your Application**: Use Parcel to serve your application. Run the following command in your project root. + + `bash` + ``` + npx parcel index.html + ``` + * Visit the URL provided by Parcel to view your application. + +1. **Testing**: Test the calling functionality by entering a valid phone number and clicking the "Start Call" button. Ensure you have backend support to handle getting a token for Azure Communication Services. + +#### Handle UserIds and GroupIds + +If you didn't acquire a phone number above, we'll add code here to handle user and group ids. This will allow you to make calls without a phone number. To startCall() add: + +`javascript` +``` +// start a call to phone +const callee = calleePhoneInput.value; + +const guidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; +const phoneNumberPattern = /^\+\d+$/; +const userIdPattern = /^8:acs:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}_[0-9a-f-]+$/i; + +if (guidPattern.test(callee)) { + call = callAgent.join({ groupId: callee}); +} else if (phoneNumberPattern.test(callee)) { + call = callAgent.startCall( + [{phoneNumber: callee}], { alternateCallerId: {phoneNumber: acsPhoneNumber} + }); +} else if (userIdPattern.test(callee)) { + call = callAgent.startCall({ communicationUserId: callee }); +} else { + console.error('Invalid input. Must be a phone number, user ID, or GUID'); + return; +} +``` +#### Adding Captions + +Under `More calling code here` add the following code to check for, and enable, captions: + +`javascript` +``` +captions = call.feature(Features.Captions).captions; +transcript = []; +try { + if (!captions.isCaptionsFeatureActive) { + await captions.startCaptions({ spokenLanguage: 'en-us' }); + } + captions.on('CaptionsReceived', captionsReceivedHandler); +} catch (e) { + console.error('startCaptions failed', e); +} +``` + +Add a `captionsReceivedHandler` function to parse the captions and add it to the caption area: + +`javascript` +``` +function captionsReceivedHandler(captionsReceivedEvent) { + let mri = ''; + + if (captionData.speaker.identifier.kind === 'communicationUser') { + mri = captionData.speaker.identifier.communicationUserId; + mri = mri.slice(-8); + } else if (captionData.speaker.identifier.kind === 'microsoftTeamsUser') { + mri = captionData.speaker.identifier.microsoftTeamsUserId; + mri = mri.slice(-8); + } else if (captionData.speaker.identifier.kind === 'phoneNumber') { + mri = captionData.speaker.identifier.phoneNumber; + } + + let displayName = captionData.speaker.displayName || mri; + + // Get the captions area container + let captionAreasContainer = document.getElementById('captionsArea'); + + // Generate a class name based on the MRI + const newClassName = `prefix${mri.replace(/[:\-+]/g, '')}`; + + // Generate the caption text + const captionText = `${captionData.timestamp.toUTCString()} ${displayName}: ${captionData.captionText ?? captionData.spokenText}`; + + // Try to find an existing caption container + let captionContainer = captionAreasContainer.querySelector(`.${newClassName}[isNotFinal='true']`); + + // If no existing caption container was found, create a new one + if (!captionContainer) { + captionContainer = document.createElement('div'); + captionContainer.setAttribute('isNotFinal', 'true'); + captionContainer.classList.add(newClassName, 'caption-item'); + captionAreasContainer.appendChild(captionContainer); + } + + // Set the caption text + captionContainer.textContent = captionText; + + // If the caption is final, update the 'isNotFinal' attribute + if (captionData.resultType === 'Final') { + captionContainer.setAttribute('isNotFinal', 'false'); + } +} +``` + +Test the calling functionality by entering a valid phone number (or user ID, or group ID) and clicking the "Start Call" button. + +### Next Steps + +Continue to the [next part](https://azure.github.io/cloud-native/60daysofia/real-time-voice-sentiment-analysis-system-2) of this topic to further explore building, deploying and testing your intelligent app for a real=time voice analysis system. \ No newline at end of file diff --git a/website/static/img/60-days-of-ia/blogs/2024-04-17/7-4-1.png b/website/static/img/60-days-of-ia/blogs/2024-04-17/7-4-1.png new file mode 100644 index 0000000000000000000000000000000000000000..4e7c214318f9ba3177a42f56c844fd6107b31bcb GIT binary patch literal 56252 zcmeFYg;!g__6ACk;vU?CyE}yf2`)v77uVtrEmEA|!JXoz#i6(p+@VE^6QmSq38A<= zdhhSPKjN*oR?f(dj9lF zGN0-0)7LX^U1bH7+G*OuryFz!c};l~l!j#7J1dN*du$ICV{a4`0@%OnSq3)&4GPMt zjw(oAKfwIB8}kG0NMOaiR^iG@zHt4hWCY7gfp|&o!f;l}(H566FR9z#t-ROAX)ea8 z!Hv8tsjPa?R24`WC{FSN%S+$^-Z5YOaz0Y4@bUeZ%CP?5sOTyvYj1)GU#+fx|Ni>> zi}mmB!DK(6Dl8=^W(4w?aU~r8KBx|FtNzm{;i!BC{q)~vf;_n7zkdroCxiU=$#F`3 z7Owv`ZT|lU|GzRkh5}%kt00U$tH3$~4r7WiXAcs5?3L`%O)0@n6M9%0IUPp!(jyiYxf*!!(1@mX0}5U&}w`$4`(=Wm|DL<;;#8- z6TbQJByDf73=FE~uQ4bdj7jWpa$C7Oqw*^6411|SRXkOsOP|kckOEyw8vQ_lq*#iy2}Igs5;pjukNNtG!B%b{y-}V?CNd#j9xN{wzAJ zFQN^IKOIiZjl&fFJ1fn?>LEFcy`!;q*vC>ieWR8n5pPs|MDl-&s0c2ZGbno|l1i2X zX>MgO;wlZ9|KU6O zN}FU|)06op$6Asb1Sn!@jJ|@diOWzq>?8$HHqSv5HfXdcj+LMD1wEhM$1J)_8&;fdYP@1sQJ4G zBfij|T9?O);k4f2P`ooGp*+^wx=PBXf|^N9;I}SWw=M(zS&5S(*F&V)vVf2FEotTe7gY2 z#FWyBI**cf4HF`m%1Wt}M8%ZUDkbKyoxJ2|&)vjN#1QYLzyu7Bkve9HK*pqjL#5onSy-cZ8BC;91*pn8Pu^B5;P@98 zpDpng!_tX>f*^G(I=npm;OH`N8{wE7 z1lD2W{|8;W3k(j?3rCy`j0F^wppXL3ZxmHUE{8n3xRZtpC4OL%QbOjvazqw~@Q9-& zpRubo=6{;>Es+_C0vZ#>$w7}Pp@EzpYDziUPX>;~{WFk_u9;damSb&$sJ}K%&?irD(;ioZe=kTbl z>G3<#%-7oy(WBR3ktnI{70&3juDqr{?0m-lX)orFb7tq1Z%01f=ruC$3l%VBUYS;l zTtU!M?+xY5F@wCK_Q3fITj$!msTmo%j&uPT`0@N5r;tq*+kQA6duU zoNo{%`HyV-Np5A_A;szHw3$L7o?Y6a;Dts?;yJ2wsd~Q(ia)0G0{7%k6ES_kvFcNo z%~k3?<~Eu!0Wz?1`ZDjjM@@RwzJ zR8i`V&6&q~%1UfLOTzB&SE}H`!=+r0-U9yVn&I|2B^$Pl44E?-KDOqEZexc2x(g%J zxdYEW=B(r@4~DE#x*0dK&}&8IhM&k$(RjAg&#^O=7f~9$N?=KbE1m|PE%u}NqJlB3 zqSX#pLC)5inbS8sTEF9|Mx1>)Er4fXowRF>uWh@J}8$*;f{>Ka@Cm zblS=SZ(xQTe-RgCz|nA^N^HX}M8)%VPsv+qD?JN2zuL7!F)}wLIN9?fao}xM;Lpo$ zK$Hb+|L#ae=@V@Lul0p5ZF2kvwA#R;8Ie5tb%1bD0B~hUYtk4VW`X`z3LxBBm`HiV z#Ona47X#=W7Y)zo(ydZwH2-wgd@$)AO`JF8OWWwlVg^>zR!9~U74BRrbMJRf#vBs% z(2Gtj^GPz0*BVC=@O1UZ-xLUX5XF6o}8COakxgB_WPt=dIi_GAI~rDAqXe8xrh9lkcQ)Xp7e zAY)am)Q&hqlh4}uCe7@EV=|nMGXgjmC5`!zzkMVpNA4VIF0zmO9ns>k`$$nt2quQS zS)dlb2WZDCO-m%(L)`cze3Ai!H>*7J65n-A%VsTb~Y~a0#=KfzipS~8DHdnk4LQ&?<7FnZ)Xant{%%O zD)tSz&^gs4!uxJGDtPSz`S)2G z4eY|a;u|>&B*`Wp86&VwzM9i3jp~XCDL`nCKnQM%-0Tjsw zR~uzXQ-|noh(3o@UpX5GOXH2;ue0%)S$G zd;6vNW#dOjDN6C{{p=^EbiVEmezW3CGaaXD5S2;X5&LO|zN-_=b$YY5md#+U`%{r;L*H5ozWV8p4 zRyGR-f(7H9&6FaAwE>Zly_m?s07t4~ti8Yn+-G@**q5#GuMRHFzGSs&$hNKXg{L0) znAOVNXKo%2CXl%?3-LdX*hV*(BpHBSaCBApqa29KoJf>N21~x%T)CTi7fz1lEDNqc zDg|T3tnvs}bHMuzQiJP2v%E7H7;%P$;vv4U^f1odOd&f)Zcn(?#YB(K7r7#fPOy~- z-pG~9@zo}QMud|v0gXXz*4i)dwM?Mf!n>{dJ39UX#B>dKyfvZ@tXZBn0&UmT5y^P- zcnCZ0TD4NLIb*+_@#DoZwc}RQ z&UZKv*o9=j!`IwcsI5u7?w;|QCZ!{Tj7x_TQsJt^(cs|X^e4E)IPH98C=2Ie=c1Su zV%JXYN^#CC;Dd1ZXsy)Sjk$J;Ir6+X10FD@T_4GC6v2f=M~R6&nspmIN>8!(CFi)K zB6+)9juk#Ilrs=sum=noTbuNx9FQ0~EHsaDL8R@DJo6yhCHpAs_z=b?xkftB=*P z-l7uhW=IodA&;S5c{Z$}T?ELw%j~hF6mb+C%AGY55RAw-seuYhEH zA(z=R=-d&Jw~pJ^q^wykjkeb>xBKE)i@*l6?{eXd^x_%z20I;gd1Q(vf2#vPSEEfs z_3OGljg3m4FbN|axES}2U(N{poo!wq3wBlYg*w%+8pDEpdDfl?cbP_3ppBVWwGWLa z^9(9-qIvn4QK~ck1&A zKl8^PF;CqdL@tZ51QGUl&|E{c7_r)eky~z%TKfv;HT;@TMb@Qw<}&trD$w;xI+7>U zT)d+Hx6#_j<`1(2)yO!|9$i1?hv01vN(rwlws(v5*REP(xT`*+Xy1Z3+;UZwlX>>h zvU@aQ2-$iW-jdQiGXr*9&7OqFhQHtk5v2`8qK+^ru+fDOClX3sCdV8~v+LY0JkP&E ztxVn?)HpyCkk|Sp&QEhAm5&E{gJIPLDnJ@&)Y@%BQ3hHtEc89h?N)o~|xHA0%?F{y2Wh&2Tb@J=I zR6M<|57wFXLp8bDS7yp6K#%2t!OvNz(xaN3~H808{&m>yN}F}4mzEM#AtsE1&7gdPxfS$I!ans3Cb?Lw}9l1Gh5+KZfoi^)5YgGH({;e?#qnPDmO{?3$=$3DzPOl z*kUP~XPVrwICV>8{lH|si?7v9*F)DZ;M02xYdN1UL%()=+77t}W%8U`WVsFn-c<38 zKLfP9H;<(jR4vg>Dq0??6&6H3Sc?}JNV;1;cNljU;u$FHJ!zOalh)l*mwe};2#fXG zKvNznI+*rtK}XPklrL#zg}~2fni%$;Dl0DC^j1s;yf|#x5BO&v7mTN}7Y7xuJ}mx; z&FS%~{+k#bg|n^b8ei)(f4@wtAU_Q4=l*?WbzNXy-7ynW-uf=lQ8k94rt;J((LLZ> z#F)g3W`Q{W8%r|s=}E$5X;$`W zgKaCcFz-D{i+l6l@682VFO?5C>By&>fv>jaa0WJGFv!95xccVmzUdd&dG%P+7S87jo0*%fSAFTyp6PIQM5yYQz{h zah#593?Mp^R}lPNi#s^b+)31CIe-Q7tSX^__T#CInO~754!=pO2H5C9m@kfezuP0X zjF;?$z^0*T-deI>V!1o|pkF9&Q75{_{8;Qh136w$v1YY0iu^-7H`t|1)VSFqz6H{1 zm`D!}Vy|D@zHSUUas65yDne3!%$LXej3vpQGcDV&M=rqCTa6XJojjpPbNS-BEX{OB zWRGbT9qygyzWnFx^3JyW6roreJ@(1o|dM>_lmKJA9*y>QpwLzpk zgZsyzTGBn-wbQ~89!gT|r%1XQO-MHN+a;--(ZsiIMncTE(OwxTg^RU_a}pC9th`*^ z7anH7NzQh>I=t^h@F}+OmCE~QBcn2k(c`>P)DMIqj_aTMZtvzIVo!qp9px%!Ok`%_qj)V|9;d0CaaAWB`8~{F z+J_q2pv!HD#m0yc?y#@MoWcsNTt4BJ`h$R}K>~RAApm!u3#lxPxH>F1@87TwcWm4& z3=3zpl*HO&7gcPBdLy&^d&j=!Ga-RhNa4^ycvpTpH;pWFh0stQXH7!mw)c1(S!Z4S zDkg^UD|E(NLc+hs8HaeB+?;NX!Id@=epB^e%0K<5DIhjPQ>R)IOkOaJiOk&3)T)&p zhmzi*I)@~w1|N`5zcS;HEWjIGDfh?rVjVY5!VX-p$Jcr2feFq~RUdbzpAafg5aF01 zFbzUYCAbw7=6x?Bo|l+YJKFFuIP;x;s^3hLz2^}VJM!H9MX3n`C}=C%;JL8p+_Q$vQvUgvuWoerEOOb;Tfe^ zm>w2VMh&FV_?`gc9cd?*O5yx%u-5ZkEuDX>8vzcwtRn!-1NdpAXa8(a^7>|#S6I$j zJ1F+Dms)h+U#zvh9zHlOD~1Ux+L_T1aLvL!>uacQ!j(myLLg4tt=1=cT)qQ(=-43Xy9FM~RehxKDi39k$F&~%k5XMAt%CXBLdN zOQs5`2dWdO#a|<@SCK*AB5itM$YN)Xq{<6?_M-4o@X_<9qJ|Tr^l?$v9%xssYaqHOto6fCpE~zxQ@I<)jJT=Vb# zUN(@tHAFXN%PFUIW|(P=*o#M~N0p|CS{n#>V(&k+BAElrn+8|Iw951R2qHDBmBoh2 zyeP#L(jvZb3{xg~o>Bq%lIgacj?Znp5;+{PlLZYF-dB1kJ-~{R$kyhTGcNNftNxb-M_R5O~LJU2(-lP^}=)Hmo zxrvReW+V4SwTFf-&HWm%ww?Hp1Xk@u{MOlM0cYc8!?|N;P^HAN$Rk{ftAPW%!*84( z>9|q%OKJ`snl-cJqGEs#tc}TMV%LWYKUinjj&|V zSBq|ew9du$P{R?=|SLqKGixdX!OPet+_(XF!cC0aqNh#pU)g zj;31;sd6Gai3&S$yQblp3KlV91Y|q)U_2;&y776E0R8@4Qu35mLYih%7rjiO`hM0y zpYv#lSVFR>`BR)Zy=t_?&j{Q{06wmb+%+-U1ekw0zbjW{pdUHX+|DEb3&5>=t{Xpk z*qc-rG>yehDY%F=sbgq84(8rlugo})$uv;K1(t2?|AAG71k~innP6|=T!;n*%@EOV zh(&hrO?Vv&4$N}JPHpPvOk%_v@EJ?2-nLD9^u`Pn)@l|rJ|V!T`dQKu^v<%yX-ak6n_acS~ZuMm9O z-9wA$dIXR@%e_{3z(A~ert3vD#szZHetnv>kry>O^M!EVHjAm5W0e=BPX9A9JMXo9 zD(Qx$9~E|S5lbA)+3Y6GS1yeiEaZ*jLji}3bpk(=k<(p|NgHaPHE%)rc}v!cVjGLv9k`uO+5{6jo+bwYl|!*e+4v6MSgX6 zwmx9*V_=D9tfjZVvTpCVXG8zA*PZZsk=|CN&9=3w#isef3r8f2^S}w+ikp&qnP*UP zG`3fKVq+F%@|(Miwuxe3NCjS2s7s?m$W>kro5L7CZz}D%!WZ^BhE19@jQ_;jfkeWA zfSt(=Wny_xn6GR?OVb}Q9PnZvhsmT^9*%B!jFr(=f_MQAd@EuYbqLQSFy0}FPhn*rcSYln>qrvi0_Nfs|~9#Z9F}^1^|O zE9niwu200Mukygb=VAJTXMv5}CBmeMZL&umph3Gnbp$(~w{q|ki+DYKpw9Z;Q;rvF zp}uxo@+|E+d+o!}Z!4Jwf653bOAv6MbvWG-E@ym3m{sL#I-7HEZ!s^zj9i_{@!}JY zUw$|h;ve5N-&xXkd|jRDoO$G5Qfq1+=hBgB^GSXrKK}7aE>p#tF&z}T{Hy6_#^xyd zrRB{vmm&S#{YvEI_)$N;&H_kPP$`}=impZ}h%c#!SkyWNRp3is*ve9X=1QOH<;qY5 z5Q=z};F)K@XVT3f<~UaQ!PY`DPBz&PJx+iBs#;e95om|x9HQuqtJnCL5vT6wTP$$o z++^a;k#yY5fmO!?(_PgG9p zmVfQaEbWWHaj%BQJOH)F6C>lIgR%85A^L^JVzY6h60Xlia~K@$Yy!u;?IiVi5D4mv zuck}Uyd*rz3sz0yy*}XWRVX#FIn%*V4wF$!qhyy3IuB-LeH6rwb4UO=^o$qK;)1a) z%E}XwgXt$~EsPYYx|>f9Pq{Af;49o#@(UB?hC$X1T3HD)OPM|lKQq8e>gs>k08ALj z>6!a)p!u${){%&ZrsZ(2?_V}=Vx~PS&@k1h^|%^Xxm44~;j`@jySTejYE`5~J{<4X z!gd|PUVnXg&>4L?SQD`y&u-3HY^+i#l1tngogN^)ptyK#0i!t)C$^FI*&^rsB-iyV zUY9u5#iYU)s_f}DWL34-YrrrxD9tRv$xoSFW=Dlp)0UtnGE~Hq0~#-4{>&*rkrjAa z*#3;C?un&}#z2iXylbr)g?llLN%K%(DPIn?nN876nUN zHA7z_yvLAxnYW90QpCq<0=Hig4`qyS-icbl<6qW>CY<5RLor7cshr%$OQZJ*@tT9p z1ZR{LnnziOY%Hq@dF#O%7H`L-2lIMjQu#0vRrT(uk~B9K+r2+V`{K1y6~6IbE{F=I zS|NmvoZhu>?z+E9YI_DnbR#Ht@pU`It#XjfDUFc0AOW%{H+^LZDhkN< zS+3Iq+CKz7B*Y+)62l{#tljx=cQwS=(a@3W-R?VtRDEcvD~iApn06*6Bk;DhSPsls zCp{r#LX*Q+6Sr-pjv`E($aJ6wDOCBSq7N7XSS!&Klmi5Aqt3(so}3*Q6U#IQqWBgF zqVGm2I7irR*z7Fg!hl~j`K2wYe37Z?3UY?#T>g`Yt*|KbbO-ffo)EuBj)B3e0Xitf z4$a8ZR&DL42ClO&Tn^1Fm8rmlJmwmkGodmHuw!5njrimld?QNU_<+Vp2VnQhn>g+t zi6jjOJ^0L)drwi^{F62gK6OL%R$ABM6)|}D8c*_+g-`7pkGACMV>1H=BX888&KJTR zI`6dr4=6>3qzwjen{80|*jJRkE3^Ugd%G4M#IcOXHZ5PYwWNT6Hyy~cXLb*?yiZlu zD)(-C5vS}&uoJWTe0GgN*poZ1M8o$Vz3H{G93u3@029MNwA9a(t?XgO<2oYaVngOP z^H&qZCHTa|GvOc6F8NNky5oaBk%Uo_uMgM5`hY^sUoq?G&3}(NEs)3b<6QfrW zZ1ZYGPDT^i1q>_um%}R#jm{ED77UaFgR7X}<8iGghJ`cfKP((Bz%EZaQfHG+)FQJX zvWOMNfih#(+Ir&aNm3Y05EQ%zs%dwdn+-SGU@g|kN@$_j>Y=`A- zyq*3V=0Or84PWNp_G^WK%Z7C*eXPwJCK7DLrlzZ!m84gTGPU=^48S-;BbRtPD(_)x zpeK;yO3)0`TNAs)YqTulya?Ud}{bP$6s)c8rw?Nda}wL{9+I- z-M&2`DkW1^jP(GMw#_?<;&S?uD&E@2&tvSp&%_vIPZKZ8&iVPMecV*fD-t@Oi0Zo^ zhs%c*e;n5GLlMb!Gefso_CSF#CGICtO8yf+nSmS4Z$5o6v~v2lMQ0iego2c!NjGFL z={Ln=LpNvR)4DxvJv-uBvB5NX*{%}xk(vF;JP5eI0p{fB9xhIQ}`}FaEdGRUjHK_Z%5=zgt5<~90Qmh-$7!`gdA8dXJ z(p{kSw&>cY=qr63Yl6z%&-p$|=;(QoXeLblJf^y?GC%I}TdVO&+x&w1rj}{e>yQOe zYHh}a1K8%edF(9(MosXOc0&*EDV4xbgtq<7BK{Wpl82)B6NftnW@uM{EoJDVwRn;b z2uk5$5^Tbc3Wx;DvxnFiqx4EAsRZqNpocz=_f_rh$3LswMnJai@-6{eHr$Q6J^#AJ z?VM89;lm5O_5xZzk#l9_aq}+XLRZ&N$G-NK7ApN2&+6UGkAZuyotj`3Die>%D66>2Erh~Vz+P6=!J~u6Igv$T0b3Q^TU(xz(iyJ;a&2bje6h8G}VFHRcDap(EN+k3K z3Tm;ed%Q+*P5B>+gY91%8>v5NF>0I!TB%Oh@)N zYmIXIDj5@r?y#Z$n*&cg^O@iFlEK48l$J0ZL0;izlHlA=3C`Nos@A{t>6WaO@UTj11JfR^;%I~1 z-pGFTVpmGEX!rRhM!2m4XHRap4Gbz1=*|Po4{!BD_s zsz%%QW|6%rcdCJg3Z}@CCnMBjcu5>yqYr7~L4V#1Q1qMz8jlCp`S*B*z-(S3izU3& z3r6|o8??uxbO1i3rs%61nJL%gO$%!Z1T3IPo#=+KD2Cth>X3AzgA@TioPlj3q1)%s zh4tjS7B@h%Psyh$G;rgjtINnJRE&0IBRoQSHRb0_6t`KzHlO%&>9kk^sjaM0qsb`N zu#_?Y@rXP}i-7H~wdNpOQ?Y7SXNK)hPW0Zvn$KlBfHXrnZ^2z8Kj{)m-ZQ?Hg@3A3 z+tcmWjlTbiZ0a7?ME{oh$xPtMr#Cp?n+{&hPZR{4ZGEId-|?3&CrVXFElB`FtI?GP zMoDyj;zn&lK-&z|@|KoZR6z1zmy3ANR;+3wSifT7By4amvyCKnXwXQ+9m9Uc3_V#7 z=SSP9wmXSy66M}~uY4m0cGcZiJ^?TATFFwSpKgHj}?z!LjY>62SCn%p(6SSj)N0`Zq7Ep&ZBo|Au1&5fK6-|N?vBo|eC#$OLv-4bUQM}=xrbrJ!96FyN ztj6jdfg0td9C1Q(|B3aQ~IZ>f$W79*9W+o$@{azZb?_J z3P4P5aSlMv0Rh@XW)ihnM)Zwd30#ZrPV^$P9GmB>Phw9qY)7X`j`2=4-`Wu)Qc065 zZvV_JG$3r5w%`!{fmn-&1{C7Q*BG&)1bFv?-kY}fT`JKN?*SuD99js{S5GEy_OcN^UM^Y-3+(Ra$hXlEMA~2ze*iuQV+|)zmT9KsufGlGS+>^QEhkQh zAbDJ~=6_iF8+8>U9ft^I;qUeLmEDI}>ZhwGMnu%-^VyL3OPv3$?z;W?=1C+J*?;I` zsZ~frI5{)$I^$V@tu%G$f%R!uimil4RQM@j-Pi~wgHJLIcd zGG_I4{ZnpcENFYLF+1$KUkQ#>?kM)_{+_nR?hpkoHF>9si4atVOv; zi59n@XUYV>m9z~zi4z`P&c=el?fL(l&u%~-df|pG$~JL54%lmC97vv;Km_1Pw^WY| zZr)S(Tu$-t$n{<hbBRKFxqc9A~J$(PO6SMgsl$-ZU`>&~f z`b60OI(fzTPV#lyi1VVdjqL4Dlrvnl`hXRawRbZyh=$Eu+`GBQ2d!PkZH zDb_mzj*8ynS=`GN_4nSxA7MxQ4>*$T7;PsH$#JjloVyNw6Bc)uKa;!NSZc6Yc<0@0 z3aa$`_?nOP|CEA2#cc}GZ3=?Kk9DoMb=_C$cc-S0u~RgK$|BTZGeuAwRp^1rUrirI z68e5fM?`=LXs`ESuxJ|+^GN#e^11BQ*v$5QFM_J+v4J6!RGIKt1Qn{Fufs?7zy7Un z7(3QJNq13b-`_Fie|iy%p2)sJP8UG=+!djDhe^ zF#Co9AK5#He`4ElaCk{_X);YR1NdaSdtY7N?BV#}dlwKuR6JY1$MlLw_HOerA@z)F z?KW&vfiXL(B3f$Un}#}2SvCt2yX(MSbT!s`S`+#})7+X5t8R}+Y$=Cq9*15ZD$gyg zCOQcpSRMZIl?dG7p#GIKDq93m)Zr8R2V+iKte4F~fjX`9-`+jvQ`a8$y%&j&l(Ar2 z#rbcSHD_9uQOwfP6#bShd(fTP4cCD4n9UC(N8%B$_W6zRL)`eu6LW5ZhQ5n$waOv` zt2?jFk5_t#lY<^NXSozSSAHD{gd(__MHog}#LY!YJpO)dE&0+-mdKvu)C<~6E&jKZ zVaLTPa25~D5k)HwKJlc6i#|;D?r#gGd^5?ww42ih*ilSirh*AHfWD-Fe*vZ_w~WX8 zI*klU2#5$LO4oFJHLN6g(~^&ePubJSZv0YYgy6T>#23)+_em4WMJLT&&lSmQBhM-P z3b~yppmRdvG5y@bW*GV=+v8cw(BAuZaZa#&uAC7pEqmXws;T$2{gG@BSo$B!Vj5~e zJCxK|a9cgxr2J2cu5izM7k352(B!VjG!+)o*wG2W!nd_e)rpHfj1CKNE@~AV39$qv zC{vt%AK!-2crUg!oWC%y?wW`n#~_&@llU?^J4@8!vJ&3?{<0z{y%p`xo4B6*E(MJ| zNrwLF{BQK}lgG5Xv6QxQplHpQ5_iXsnuc9d9*2rIV2!@Df)?!>L3qx<>$BqPgda#^zkNTLX>j~&7qfMUJFyqy)EjUTwP z29TvwZf6t8=OmbBD$?&F(2S$B(x?2a_TLiPzd4cr3v6vKVvoas?pRq`qPV)c0-txC z#(rcR92|6Xbp;DhiUcp^=H(?Gr5q_hv(nOR(7g=}DF(LdC0lxX8SwG(e;yx$EG!uA zLPD->j+dk^PF7=<%->JZ3cGK}z(*wVa&t9gDcN}wjg5`%6&!DbU2BE;*Jio|3x5B0 zYobe3(%xIq%0iROwzxscu%UHF==czVh|}I523cgdkW>&IyErQ--b2&*iKwZR7@0}! z$QDw#6;cs8ct1`}yc%6MI*&tDpvJ>&ZRp?_ zABz6dF`X%Yytxr>^L};q$1FSlC==dAG!b%JveW;zKy9Q58+H(af>?M9n6cWeISeOR1edGJ+b+FGNxm180iFfn(l2y9i5z~|Eui({ml2>QPlOqh$$RE-Zu(O^ zNm3~iOrFXeeDzYxObRm5_Uzs+JoP1g>+6oUJa0^+0w>gUWM5F^w1)&bv4KSz84B7w z(1|@=qWO4vltbL0v5FLpIF?)@-b(=j-d}0{@6JCFm!9Vd zJ0y+Ggk#_)Fx13eYKyWPFn;WoEVW{hYsf7DIkZ^ z72mfqQ)APFnh@k^Ef#k+kuBIyD!R!xKrWlXg4kJDy)PLZWxJJ$$J!{9O}Ri9v5vjR z4ics^T5oq7bs3fNWMb*@8CS?(maNBsR*fhaM)=ngTOVtZilvu4$3%J;Ztr6Mj!MsR zlg(-91pIl$a&`)_SJj6;6=kI6ERC+j)fwJZzZ<3W63n_vV0&HzNB+#&CyM4)&(*#( z>PRvZiXw3vEbXWa%-BnmBH;-bU=FUSNV$by2d@nb;w0781wv~WycgfS5}ZlAQ_DK` z@7v3v1=C^B)wlsLo44lF{7<*!tKW1kiu~2}sWEMx#*#Mq?MSuO?p;Zo0Z0c#10Z-= zP-xhf+KzD}lw?{-Yo4fth3J7K1NocR`udb|IBA)g%L1?i`B+Tr-B3~*q6sE z5d#AXJZ5dPMuKQzABc+pDS)JS3VyM}#;Uxj@6EQ2kR0nw&$F)V%CWvxG6=$G(ufo{ zeqs`|a(9MP$6cI}q5#z#brIs@;1PX@EU9a)Bz*$}zVTx4xS_d02!~>m_f^ScyD}58 zkGu#r$PV^Ih>4eceFl?~i6vwmf)uqabBGsObTlQa6N_A7%89#f@=`RHDv*8e(&h=bg6-MxQJD8L6-Z{TU6hx*EXRqLmg15Bx$J%&hjl^ zff_7+6f4FgT$MH{C!i z3-%03`$gNo+u#JuJcr5+qlVM^6p-hfsX{_cuXGLUA}#b3Ylvg*vv?$l%6nUJhxT4` zaB+%T4HjERHp(c^y%Gyq3-=lbDgkdHO3;&to*IgiLULM;@39Qs63Z zA=vG`SSt7LGrccRn9wDOqgS9o0jLBUwrZKujHPETcC(6hR|qC zy9KX$M_&xLHg|%|P5p$OLpO<$q#1#85%yUYGCEE;I}ae%MQWds@B-hq_a%;u$*yC2 zsVKB$GTJ&hLW$$Tv8D-A@RiE)BDcvEhfsWc{FGR+qfL_-Z&W4ZtgA!CN5WfwcR_E?-#=QOX~irUF@2&Af&fxf=&^M-nNM$Fjd@OWVS4m22@^Q=t^p;WAFHGECLi9(e`-nn^Quqha! zb-ZsokVTu9;z?aB90BUKx}Q*&qmyRWey+1dkz!A*OPIL-Z!=-X z$l&LaTsfs#oXd{uHcjqsZnd6(bQc4A(neQJjhF}%M~t#4D5?mZIRm|^uC8@;+S8$s zsab8=YnG6!L5AEe9T^f663(|LY|g{-6YNpk$QgUb3UIvROUe7w+SDT% zQHP9r3<}J$g4U+NqDxPR=N;cSW6xI}xg%L-*Tk-FTb15spWeqEqwlkh=eNFH&{cMvE45^9 z#uJjccYM98Vs~Lh`(|=M>NS;{@_9kRiw|VOQ?54) z@5XE>Ge;KQjS5VX@wSBx#x%J3zFH^gj|u=pP6dzD=q&jFf_4+cC1& zlDqq4+Xr%Y(N1yqlNKRV!UsHBkgA4(P~FsyGR50*TAlHHOBV{I&WWvkwqTF~#BG_9-@aF;_YTG%gq|2Po~3a5ZpXtNFr1fW)P8ev&( zQ_L-LLlksNu8#6$uWTqynA?L}fQ5@MOxRqBvk8*zI?R3u&<~GPcvMWlkK5XJTh=#I zR$DBa2R-rv?P$}Y{6#$M0LKxdfrLn0^(mH>r!AFVN%R8b19N!z=fw}p^2qic40Xf2 zhlIQIqM0J5_nQ?YIk9DYPR;9_f8Vo{oFx@wiB$&hjYPQ)b-r{ckt-4zmbgwc5f2yG z4(HB}>|}ZFFZNlOg_TRB(QdARq!)ha|8#;4HB+!!?Me=mZizE@$CmTcdTG;as zW(a1{1Ck$t)rB)HBIvfT;YGqPhm6HKL0Mc6;`c(vgY##teFKp}W5mUy-a=X-gnTt& zJO>_ZYlrY*d~b3GIvYCajfW;_lRGW%PeeEU@1 zE9y95g!gM;!z1}q!anD&7`5EI<1zk9P)@c0j@Qr(8=atb^8{+BTc@+e@$O9T)2fU% zjT?teS^`M%^O6JeyJduFO%JG&jI7$42PZ;N^J)B4sJ z->uc~m|qUp>8i;Ws`|qM*taZR)cF~gD)RZ`Z%wr+Z-3oxBPBfl-pK^4{h0NV6y)D* z7UyXliC8SvYt53}*Wl^*sHh{m%&Gsg1RZ9&n_wQ9os|vG*?Y=P@?*+{yg;1u|;U2p#lHd83pE`Q(5xbJBorCIbw$ElL9xL9zGE1uo+i2aH$qSNimm=FQ(| zKoE#aKXquI;_=n8XdfsmZUMaop5<#efM(%@+F#Cu{&{aYO=Nh@ z4rGUyrq9CHIT2A<%#qZJXLDam(A86k`(=NBM_W#SrXNMl-#+7Fzg%Q`BByh~;s@K+ zC(AXNQb|1n=3Duj4%yDcext6jqDx$m!QsXKTKpU$~Kqil|?jk9M^I0Xhwa2b>E^s!YHylf=LYBooNwm zFqWltwXCGjY8wvO0sv$W+m@FU$}v>*#7M>|gG+>>RoO_ikK^FG4OlX`iUCdzm>0As ziiV-+b4I8~nSd=taf1PF-~hS5*)aHJS@R<2vdC0$mEm)1uP$_H4E5JI&9PWS6A`Pb z#LShOz@P&dD^YSOKis+zz%z_v9RsylUdz!+52W535n$WobRV7wDtE{~6qcpIA@z%a zJAdfG8}I7W<#$(b&RTUccGa>P7>ySq6HoS>kT)ZV-iu!zlB?YEhZL~=K5tu|bY>U{ z64;n~^DH|lJ`*UJ6eA?p@^xl-e8x$xmbzEpOeX18IEXL*m~t3i*Q&y51v*QOR1QH# zEi`&QcIcXM1LypoQo}^z_+i!Pz|Uuk!5E?G#iii}#C5nSD^rrxOY^Vw%Hu8C}nwPJN$S|2C29qVoDP{+0Gv%kuGyRsI@9 zLv5XzK~bcsM+CzHdHfcx(NcFMYtsFZqN6OWC_-|5XG+C2fH1C9;ZONRp4G zA4+B^gU#I#;!~z1(X1R8x*hh!?#x_9r>14O&APuPE__uxN~03#DBxT4am?I=FBm|Z2?id{+-J_+u;mrRz&+`U=-Q4@d6`$|0WqMAn-+Zv`v~5s)fveskpsXld z*l#f{>QdP3JwN@lM)q`}pTXY`B~CU`5P@2;`J5%pzZv1j<*`%b0G;;~Jz3hYPn(Md zlYgl20X8(QLb8bDh@Uw9c=L3i%79p23q|GTF-=$|O4P&4$il|T%V5b3qR4MVFU+3k zN8ow0r?YW@Afm*nNKqXZ+h?Iur)+2Lv=FXRx{Pknvu?5-WwH}(`a5kfzJ%n9^+YS_ z>^(0&NLVYt?qP~cDwiJR1xEw#cNfB>htoyp-$&OK4dMcebKGd85QR)RZq79Fhlorj zq_4<=l+uwah%!BJg}57%Z?aJqv1Y!FQAfSgyA`}-`L&Ck6?&YT!5UBgO`#?|O-K0M zYa8m!F!nm-)IW8^Q2`|aFCwQW5vnqIb~%5ylY*M4%6hcO7`CNW4I?#F^1WNp zJ(>6c{wDj*e&lMe{Q<_HqnN(YFz;Y+UyvvTF$6}UPw(OAsRMEj5vccjpA#0Wrf_3` zSN5bcuJcSfIOX{4I%~%Px^@wcUTV3mK;SxaTXBDha~AF4a0WZ;=Z1UAR&4j;3#;|5 zrI;4`#-MA1;{r>hn7TmsI^kw1!c7FTuC!qxR5Wa^oeVaDt;3YA@qEUwRd9y%M4yKX zfW}P81^~9pzduMiNg_RtLNw%a;a28}?-z$Wlq#|{FQgYA$?R2BGCRyJgzkPPzi$0v zt9(|(Q)>e530xy{;T7AyZ~aJbq^An$vBy0>@BocA_QC-WobJLEr0qN1!Wr>9?dOf) z=C!qse-X8bU4SJS-GgSM(lgmfL!H~~XTL~ufGo<`>yO$}vrl>$Yp;tb5E9N{NSHh2#< zX3SIgFUTub)S=M_n(;|c3FQ3)li6jn#iD|Scu(p6;K2RH47VzjnwS`QPlMHs(>_!+ zo*f)1_9DiG@sfes21SY;m-j_XpsZKZx|Bbvzo(dJrUd9vf{jh8T?En>tnmk;uXT!K43aVCGMpIXY?_&rdd8UMr}QXA+AHqQgNPfl>>N}J^pW0XHC)Kn@{J95daiLm)J{*xxQfnElbe zVq%tkmuoRFvD@oFf@binrNQ1Ccn?&3e{QUog0pCO%Q(Lb5EKqy#enn(kA1NO=@Gn# zDIg84N)7UF%rF{Wb>*Z4z^8nw7fE7q!y&|YdZ?6ma^t>y(0Fl;kUaXv9QfpkWTE=L zQmCyEg`{-{^lN|+?^ojX0u}@Z|3YLu1!tH=Rycg)dOil3LmrPQGPZ>)HGionG9iv+)vtXiOp@e_>x)P3nKK7f_zp6*9n3)2=Amy0(-qcYaR& zh4Xsz?C9Fj!u1I_!w;1Av&mp+*LxG0-4nqT6i(kg6p1uKz#DN5`QUi8?1j1DV5V?o zLKi<2@mVcm)lz- zv5Vk{7_O*(1|ffX0=N^Ki7p|zgQ$G2LS){IO^E5D)vJoMsDI|;Kjem?yxW3JAdJA( zZ85&hJ`(^fTG}xib;|F`!ct?J5UG;I`UsIAsw5*xmd}-sT&=+9SJy$yV`~*BEz`@4 z)Gy}=z9Zz7sno9Kyt7Da9XxWUAHqPows;CI8VkRB)?U~Hm@5JHx^kii3z9w0D<|s zo#k$c-+#8EyA7kW{6H4qt4clukZ*bIwYEE^A!oU;PkM1n@}3~`6|3sECR$VhHs;P2 zo|u{s9E`$Ey5!wNluISdyE{8uN|D}ALsfoe1X&r05wC`(zw?`%SP{u?9}BNGabsKs z&pB-<<(aT@8VTSN0&Y&HIa(Ti5_Y!&48FL(K5TF7@r@3jq&?O8wne+Uw|l%;^|G7! z^Fgo@?z`;uM}Uxryq7)#mYUpPanck=HSh)~N& zu%>8CR|1>wTC37Yn*O%#?3>JUO1Cbsrtj`8Olq^3^?j9up;F+ z?w150m9nM_VQ9kqC$64VYwrx3-Q>Px9WlHEkBWy4#qFTQfktBO#_E33;$QfvO;TG{#(#r4zADukwXjEYy4Y@I3Z1D{Yv+JU9%WO>{I z!o@`_m6wjP6NM(7BD~Vwn6v36A|ldntV62ZggvVHg#e)FT+(YGfo_7kj0?yLMaLbN!#H> zV>2hLn22j<-)YtVzFaPdq$`pa3Zf*kqHj0=0t>$6E;O>=Dk-4G`bwz(G673jrB%KF z5&lx7U(b7zO1DA%&WdJR;Y;P5f8U9r&;WNAdy2A(v9zbpJmsjTS6F*QbLHLs@h%)< zHaiis+kc@JL7?o`hyRKpYE@*aT%BrLq~MuB4Hv1al_ll-m0UAaFLztgm*!T#U(u1+ zcUsd}MM^_u{lACX2$~C#H*R<)8wji;CIoNg^LD6XQUe(ts`h~qpO^{c&1Sg5q`2K) zg=wsae~k&mzkMmah0cxMuCm;j3*~8?$S~GeCT4!ji|5MryGOqs+3Ah`w7P(W=`qvx;6-*u=9nH54+8(Zfj`=O!(OH6XOC=GkA4~ zT4{FcT`HOF4h#?Kqn^oT)x2Ek9+M}GWx?NJu*OUYZ66#e2lUNXwGg{d9$BuO_wtal%y zdC6Z#iTF@Tw=ACoZ#;&))9w|?RBwBo)+GZb)pRQIx&43cC1e9<$VUVRbo*lzFAuij z1L+61bXFX+l3bnJRvZc?sK|jmN^0Ps>B5&EK8(pY+c2DBLDdbk814+cE^gq*r<1WD zidXpxmlq!Olo~o8c`2Fx;!>WZ8mkAjitwAxM|oR2+RJEv!S-C`gA-Q!Ur~QcPGxHvI9O zXBS9&b4&aq^@DV<}&P6ts zZ-@v*ak4?t6vPfGDcq8w%l*;<#E3J=bs{`IH5Y83;b~^>q<~cl^2Ub)s(zYTuYsok zIYy`BxOlkv%}QlT%u&yJ3yJi&g|-WNTrU~f3(G+oY3Z4<3I$+xZ)$rLueG1yl}>YS zug96d;mDMVRBeQqI5_;$L0;aL5E&}r5*2-xW1P+Djk}QTL&cQ;_#15Zi91jqq&_n)f6! z96cJKw`fM0Cke&9T?!noZnUv-uo`irRo|Cz8%(FU~d&2c@-C#mlmiV{LzJZk8K-V60Wx zu;(~Do*}&tOl+0n6_|u@%-1E_CwZ8F1MBKp1P7pUkcCSDre{ z$2mcEhnY6VTig93944tB!WJF<0g6PKL{>Fh8rI(QWnZr;_wsubl}yw>-zR7-43LOE z?8~7s7;NtEV!Xmxlaa=rpKp%u7KL#6-At7VI{g5;ZYj&tQ*;mQ)%PpC4$4FL3RVXt z6c%)Dv9c`TQkV1w+|P_LC<>$)x@KJ~E$^RI#Gly+PDH^2E_fCb>brtO1vl^iZZz2~ z)=;X9P+L3fa%o}b%tYtczOp8jG}P0|gnr@^4M3h1{%rWlfLPV3e1A_(*o7c6Lx(i9 zxR)@b^vG`Rr+~|>pgfG5P=+0Ao1Tg!hQ=G&K0SqEI(F!z1b*kR-TrJ9@Qk!n?jT;w z+Cem?dHD*H4u$eBZYS%QNbh5V@cytH9zelJKE=HM>x@-w+R80payj9&8ftfKx8wiPu z@CH`EA^t2^-2^G&XK#LM3_0D~)9y%F#^9|*B+;x1=GAyq86@k2h*T7G|4JbdW4q!* z={h`ILBHAnbRN3@aSwHQNo3_>=CII4GuGaMDby&Li64JCDWB8L4Nzdi(UbW`E#gF^;vNl3 zkO4%F?;Y%rw3}5O6+~2506ul%fTh+jeDp-P*@Cz^Ofq5w5dCd*){b_B+|=?qU3H4S z>`dwEtZ>g-_jHudmDdLjyF~4JnMoCzNf4yD_`8Y%SZdo*&DjP)rdZA|FV`3F#5fMQ zab87#3;2Y;a(9v_=u}`NB{Dv@7$f}!7tx~uXg*CiefL`4WNvCIyzdstt5~M@RC*Ivx*?5u3K^ z8gJisHL2?uSibD$dVE~wsNv6w^RdFwoom!;I(6hROZWNSP-S@9k|jnaJH1)eYmt?o zlIYANO737DiC@yP6lmkApZ8nxHS=BC%e@1jhWk{jG&AMuy@+DSWf}dYW^Ifz^T$E7z*@q-7R7kK`d80ZCDu!WI?JWUloeDUZH)2eCT{%y^+9y*1kK*EYjhqPEr&GV0T8~`CkR{mDt_ZA7W}?1qKl$iTQy+J7)l6%-fEC(zN?6=hmTxI!R{bCLz&(zB ze;tpD0xm76W{t7yhoU1QTHx8fbXcaDe#Dl0Uetcd>UTRA%MqoxPFKTt9U zhqaaj4w=n{k`f#P&jS~Oddsv*OAn6=D2H4677y|*2|p6j!{M!V8DE-;S+7N(n3{XD zDWCJ<@a6c@1QZ@{%AWL570x97psmwz{+aG@$NFad#NbqSTlj&!9c?dpt5+60N!+`} zhdBLi1?;VVTG^ghSb}`Cb>Q|dF<1SpBd(-zR+pLCBql7JhM#-j4AME2nTzg-x@uA; ztQKLW$Q0v3^YwSzW zTFRA5chi?Q8r6a`!(H0rlzoda&xu~iFIUkZWVU%y(XNG4W?~JPV_Uv0R=K$f4GsiM zGDX5Bmo2xnKHeNJ_eu_Pp7f)3HHSOvnvA-gC(eHrrFZD-jgae9yO?^5m)mGdznPyI znH`8>v^)_g{VJG+CeoSXU_n&aO<3=@2FC6R-hC*k@kRUN|eJ7vp*pHtQiq6mLlzeAOjG_Xr7sdu3 zwmWWLEo@$UWZF4&m)08uKOJ#`Ez;w2d@kR%R9ch+0-xUh_Fi4ih>Kqn@T1<0ekRZL zwI#^F&Ex!Abv1hP6|lFZaq=WPqpYu!?qX+`nhPZ!-txG`0dSYRhtE8L7d^+Kulwq0 zxJR~i3E|ikh_-aR-D7|K2g6m?{`m;-#UvNjieSacS;!k7QbP6$U zo!e&=*fw$4JutT7wp1qtkSy%CskLQ8wYu0SH%3Ds%2K z?q{^Zgb{@2bIWFIyk#YTXC6wjk;H2Yf@Tc!aK3!~A)}Pre^W2?R@ERRLS#ouE*G6V zPt8{dBqeJ$d_>XHHTrxb)U*hZHEeJi$l9DPe>~hNPREADD=NoK;aXug=`8jJF$SOL z=Y7yoNA#W?GRuKZN{{a**tvbi+hZ}tVgXGZ46lle2w?Drl}@9blD!!gHQ{p5}X zsNc~RnI~K-CVjCwYRk9&yxW0#wHPwT`^)LN9}{`i&OkZ1f$^K%l?DF*PlSY?Ss^;s zgf}d$*1KsM(Z5ex(RLqo?NNHH7PWifzIY*Ytpd>N9DWA$+n1emtoU9^NGWh2C94Z? zajjCgh?7!k!i0 zyzE(@WZf<3Tl2iyN=;flMwShFu~$!ly_sH|f+)aPvlqcLnOL+TBcn<=^I7ikUj0Pv z(B#07!j>8RfNs0_PPpRs7sIqP>&&sTx&9sfoA*g))Jw`5aKLcycj7`Dq4o-*Nvl!m zi}N|=;`BiBYSL7|2*dY}srA+jmLvM6T|~(rpv7Eenq4H&)D4N0!Cz_2EX6%FB?J&?p^QMKclDnE;<4# z6C48-kU7|q#K1>YqH-5d{xBv?`?N!2I6tz~`ID%4qd!rkcRX2DZ=-B}#cZVOIo}XA zRD~p%phzH7;!&Mw(nl(FP9*9ymy2f(-C(c-8)*Z6(O%ryGGqr4;6*4TMJmPbB*TEV z;Fd1mZ-REB-RoSY$93M4=eY2SdLg?Iv-lt9=p@9aK)8UC@ ze|3|`*0ajlY}&bndoCg7eY&j=aPtjQwq5KA3L9BNQtF*lvR?Dm#D0q}TVFi%`mdQb zK|4ga?S$i6VuN4HF1>Z8>2BqGzyBw`PqMni9@y2^^d`)V)WnAhL%Kmk2%XnCgp^@O zbur-T)y^(u2OVhDtG(Q)5$wv_y{& zbwNf)j$!l#w$*Fe-8!ISSBm`|w;*5Cy8%|32hthGzQwu8&ENF0T)>X~w2yLB%iedj z{lmjtDQd7GNO76&XE8_LurY}`q*IN2sfi&WM{o7ckn!%$pzp=pwuhQYTB0r+U8T;z{vW-3h_dZte}}5CBFKou}~Tib0cqGWk`w21}mP{3o)H zRl}r`yLq^4VyFC238O4lHG1f*mt{b#%{z`HSOEZX&(zLL)1a(zeZ@B1lZ74hV=oFZ z@DRKMgm%-txcd9KJwghMTN`@Pm22XSL8T+m9qY|tRC5j=SI%V~3tEcJW|CN&r2|ua zsoEOMnsCXZ8MC*Ca}R{eqik7v1I`Dqb^Lv?c(AIc4=2k;*jVnRtdC;8waUvZ>l)|} zYM^7{N2R|5r1kzN&lqE%zm6w`jKW7=v`ez&OOiC<+z>Q-S?}( zZh$K3onBlQJPRNZwi5Ytf5@qBq!oeJcBO>_dFFS5v=Ddxc2XbdM)E+4N`EdzbR~l< zBe71^4K+UBvP}@oPA)i>#G&KSz~^mKK81yN`iRZa<((I7*v=t@bOas402y#fy{Jvs zPflooU-1IY+QmaZ&KR?EG6;m-g;#h!X?Sgh)_%S)-RlItpB<`0dZW;;suN$?JjrZ4 zD4nW4A!c|~D(_K?&L8By*=0XD%*M%m`yMG;rDyiW-W~YeyF?O~_QZbWz2^K<&bNdgyA1 z6|Z2sdL{GC?{Y%8aok~;byQm2NHa4o;pr5GW08+3qFI*y&;e_$XyI;Rmh!EDZp;c~ zrHzra8rte*E>Dv!CK$qYqW^v!&Qvyt#9(%FUg3NSu$>icuslyyL&BWn`G!8R38IQHNxL8GxG%(QX|xgWKc6(F z1i;H_eg6{TAv^#scQOqR+sOOJ^5b)Zc83=ZEgs(i?qj0u0PyV`Zz{O_WzKcKm}Lce z#+M>BR^EpP!NHrqAF)rCXzmI0>yCIItOB+P@xC?v`lGCUHn8GL6C{-8wX*;woX~|* zmgz&}X#$?Bf!ifx^i5^x?r>8g#*-*;R?k3uqCo*qS2U3g#XZ4SOCgc~Cmdt#m57!5 zYgFdapAtHU7dhY)0{_z;-a_)*l#DWdtv(;jYLiJ0-edDhjHQ(ac11AfkU=)J{uPCW zZ}Nr!udz&4#2|ZyXT}Y1fkBi#%5&L=ok#ZB*%k$0?fy1HWHT*p`y%D7i|%(z{&~&L zUrmXV=qibnV)CM+eot)+f9zv(n^^*hJ-@7FTGvxN9Pg=c_vM zMEFofJ?<;|^* zRk^`F^JBX{>xq@XN3!CTKEEX|Q)z4-U)B3*!^uiZlWgyq=m(QIJ9|UuSraWmzd>Vt zlQeQS5wiNEG^~QA>Jhc7-!9BytGHPA3oY_DD90YRn!oUOr_{ZWY{z| zzd-fNgZPHDf>-6FYd5ix$YlwO7a$8lFF#Hb!22cIaoNA)iOpYGeV>;^Mo!*^ttK8N zCbHa-XJPAfMtvmU$H%T=`&Oh~e|i)$EJFmD&e+og34~PMq{lq}U_oSpdDiL5UkY@1 z^?Gto#wMkOIZJW<$c0`bZY>E`f-1itZA}or4FF#XnLqU|ekELz=H9obkc{lbrtrusTyCk zo^ut(?B?4H`0ULOYjQZ^X^9er?ndMkdmieu0$xRTn=5bom5uECw=92qw_JRsxm z{QNZXG+LrS7I}iwiiue361_$duV!c9Yyn-LV!6gAXIR`W9DC`}dcPJ7{PjRR9X0%| z2NsS3nV~t((CCZ`2Nz3s-QI&lS!U@~)!vLsgkgMu97P?KrSGw>G2jx4hw4w?5EIei z>*n>W6BLn#GK4ZNebNdWAT15*ETMHdV|b)xN`h7Vq=~kf&>h3dQSVnZ}oy^J*hW zVwiQseQ;y-G(slEF3)LQg>8YzjmQJu47xs~ToPOa_Sv5W>j?0ERY*eB{o1(i$&7Mb zfFUz#OqwVN<)Ll}a3)F7zCPLWd+>5`qQvoLXY3^xa{8Ugrq}G$IECfjh_n!9 z`DqD(@&SgX*)}-0%9}~-WTxdk9)%WATzI93el)O#-=SEryZc;1*E6!OXq6T;a{$ae%RW9@4o=bu!~y@v?MR zeg4Tjav*tU;FA6^qC6mMX~76~F|xD!88-NX^r2Nuu)&el1M$h_LasTKj8QQ2N z%!CTlp+7wUaRSX0HU?Iy$ETqOIk451>nj99Zbvx8(g7B9%XeW4lXD zYN}%MLf8l0*0+RoQ?>tgkM^0g_}e|pLe&st!B<(&Audl2E3haMz~W?@Io})c7FWI* z1gedD>}H;-*)K$G3s4-=P;WQJcoz#!u2%|WtXKyuIA*RQt=}UmbWx-7-7j9};|{!d z^z8y-e$&3gh7Bk~TE?+AN?K|D5Z9jelB$<*TfE%bT|t9G2sf9skO1;C3@$*r4w4iK zz9~T(We6Os2#f|K{p2x_)#Q>roLsIQz6XF6u}?H{r4{q%l9)g4HOvlWG(ET750+?U zYWK4tR_RNsnCiL|#oq<#fd9sk$a-7p80Oi{5{GB(`%g|SM=1zKF%(a~7Ga|C4;W`t z8vLcM{^6zSSYnoTMDg^}XT;9IXHxJCIdb$%;?U%x-7Q^T)Kn~H)#h(O-ED166d-zx zd&tQdwb-nm3)!4FQXe%+4DhrP zNjUNJ{2J|IqONZ!wPA@Ep#^aR2s`1bbtkiARwKm3@=s1^S*Rvd4wPML{GO{`cNN#J zqd&;Px;4&m!?{69xDMPa6;ubNh`i}cO~tus$B};{o(4sNQQyD3`!X>#@mZXdEK*iI zzzzHo-6uLZ4iw+zXzqnCmSu14CZ^70%quOzjwY}?o3IpZI}pUJugN#OANF5wqR*@q%eMXd zw__m20|y6}tn-KT{(r+_4$MkI960~RQuAqbb_R;$yt-T;$axZ=_#2x#cBFBwD6v7B z^l7@~JBFo2{^wh}k_MzY@j*FwhNdG*DAK(BB9uurIA0l;~f(muWHCIGxXK3f0lUZ2s)IM4YMsB5R}Sb@a#-T6CHf}H`PJ5v#8l2%>vOnEWkeKx zr(C+sNrNHa<_X^Ce}iF=h0@fC7mhch&jFF#`%u5Ft_dU z*|OIr3*Xy6c`oyI=Gz-4=3f22DdVIV9ou6a{o`M4f<`ec$)D@;ehHi_&$I-o7Wxgy zf^w}RnFxISj8C=ECuuW@^c(q*d7(ZeoguBuU>6kc)hd2|b+y`P{75Fk-x{R$bOTQP z=}_XR4tL%^2JX*2z6_JaLDm}Vd(5WJg{(!LaY{#+hWP~TVzTSK-Xf#zpV~I>9RlQ> z^0)!A2n!8O-@snZMA$pF zMaAbzBE=TsUxg$5#=e7uk~+M$&rh+HWIo+$=!vU@APk~#@IGyCX=@mN!MK0q>!yhR zrmXNm<-ZwIrzsW9WZ-tVBX5ST;%Z0C$ll33w%WJDe(^JlX! zaA$`E=iitEf&4sU@5}Ak`@g)5z)=M$h!-X$Z?b2Hg^hz&A<6t=Nt9$pMlwqjzrYY7 zdtPjp<6a->$o9qZ-Go|xLkR`4GQP*m_NF^L;BE{bGkn<1jp|R=*6t_E(Vs`ZCkY1D zTgAb^$5HK(`b@{hJ86uU)f$>c79~kqjV7KH&8H2f85#erM`o3ilI4uDcJ~ z5O8H_XYj;_JkNqmU9hw7rn)hraUkQ$AOg(FrTKH)%(tGHE~nTN6Vv8$!p-zR3)9^8 zh=1P>W=BXGnh55`#+KRTy@gK2`I`;mln4bkI=WO7ws4hK@Zld;SZ&C~D{@wWHhOWz z%?eV~DY1?1) z<*mgHWoRcXeIQ`MeM2xN35u6MHr{XV?k*@3|NTsmdVghOV|_8|5^{HND1o;bW{fOC z`VN*q*IgYfny38h>rEmmz@P_f+2O%a>5=`=J}kwPhEKawsp76}JxY>k3f~J_77S{{ zA;H1HUYp6<<5N=>b@Fm@>hIqN@0GU$$;rw49ILpwxV}C;9zBJ@*E>=1M8BS%p8m;Z zr94Rd5heLg3MM;9^rBGp65|n#K!#mj(<{Xoc|gWl%hLlJBE%N36!uGnsN02BF@L#q918*Y0h8<)ZcG3BJQdX1JbYP#nrH9R{TSRHF4`fxsZod~`mygv>H^WBY2 z;S8_i0!1U%;juZtmejA^Jv`F=qNAfNX-czqoxqcu{#ZT3!^*B_|K7&IWa!Xl*YP-Q zldv9_*(j()&lS6>%5`G;_t)`beB_ptpXc*(@J-QB^JHXTpwT3op_Z=MePhmaS6v|? z#&vMRW#N~K{a=*hi(XWy53PNJKa|-FkT7LZaZaV5RISy20lhS#lo=M_e`tWi#l{~$ zesG~2uRI>CR5-4P-`~!(^PAd_W(o;9tv+kJD0N(egCDt(R0`9c+2J(aXu-QJfp!Rz zP+@wuHFCYH7Vx=ezQ$&9xGms1JfH(vScm4z$95fEnO*MBVZ03fX^ zsCSgNvH3YLb~Xl%4X5!Z3&*AIisWpIfCIk_|1p^5J47~qtu6e$*(S}}yxB5FBB4;t zO5-_&7x*LLh<$&471n?Zm*@h;-n=4_ASb?ZWFMcF9ShY~c*4TMQ5JD|huh#Mp?@faMZc>hopyh*$LrW9G7*pcj*r@g z7QiOXfSVz&zdwr#QUIMpob7fYR5RGv*dvuhi61|H)M@eJc5!hTc3E!mKK5yq+o()i z;D0~a)xTxZ=Zfs~lb>AOj0G%XNb50^)AX7mJETHed&W;qNHB4P@!kB)aThk{r?sw7 zM&pr@k-p#k6iGaa6;XYKKf6nco6lorXY@0!i7nnGC@^mL{S}y8qAC~ricB>9Dj1cckntFi za&Nwp%!ZGg)KjVkVh^sVQ^^ruI+raFY`K`$kDfZd@;|y#4rt@_+f`!b;NTC6mWoE$ zP5t^EcJnouNO3E`u_UE_(Is-X`?`=|>E`clTNNc!)xiY8$_a7EVCXkL0(NG?om-#gO850hK_4erCH@w&Yxl5r zyj!M!zWt5piw_lL`HW-wH=$RmAfT+S>?^IZ#uqm)dNrX=aYM=a4r;sN5K9O8z`SGR zU&|*IIW2hkwxBi^Gy$}h!ZuS>gaG2H{#5zixR^0)d?Jlu-m_!4*x=O3dn5aVsJPj#OeWL771s!f-h9_}MY|I64~WxtD{PzH zefpVK$ODNJ5(W6w4GoVHKCz)yyNn%=^$({YleB=)f7qhmA7@^38V}-5EZ=5lc^Q0& zSICteqD3zeyB1fA8<=D7rtY=VHLT^MZ%jJU&Xa{1)}2^-}Xe-GXu=cdR0*1(B0Sjs*V$56&HD zRazQKOp3%M=f*PgiNZbXC`SQJV_5t?EtP9OU_B6>BK@ zA9G0gsdtcjJJrIIQY%Ea5OBvJ+~iwrT(J%jxQ*2Y7TyIs-K_`&9ZJ*O#_gK&0*O4E zJ&*VeJ$1;7zSf#7lh5q9={&xt(xewZ8|E$ixb?TVoZo`!1&fgWqn*yBYuxK-?0qG1 zjX?wIhCBmYMnp$)q@|*Hpe(>rfPLZkbsl>i~3h-fI?!;UpeK{YlRmI9Vw z7$vopuxQxHdxQB-_6hKr(dLI&r`D8)T=NQRJwOA>Po~dcZ)dD3;ZDc0(nZCVUf;^P(M(r4081E4+ynWT zQf%#7O=Q#Zl1$?_-wd_5G3&9Il9Qg9*XMD)s%udn=8kp)z(uPT@L-wiDWyDn)1|y{ z9^j~_QuniUGqS>wvo#?nFzdEmwCN13o{qPQj=x$+l{T_VwfPBIUE)kvXrbEa7UD)? z-IP6+?c){Jq>C5&qA8;#Os^=h*sz!`YYWv)dy}Q&32)z9Yot2kYGX;Jgc6w|yKRg1 z3w3GYvr#ZZJ2Ix&xhD5wax#VMURJ22$5gJmIaL%=AXF;`Js>A%h3#sNa~qd1yME@@ z6W9G&w7MRS-VocB$YsA34tK>hfQpWKbXHtaHHd<<1t!-{lrw0AzHP>Lr8#pMV#xuA zBx5{YJT4(Yeg6oZAdQt{u3IrM2X9b znSMLE_qpDsLMXLb^mzRRQCJPp?;N{zq=8lWax+xc%L#<6{o z(X&lhlOT*;-&c^G97fb&P|yA&@dcLoLKgAd_Q~z)#m%Nq!-l}w&;wT+KL1H3YuS*h zj}fD0Tar@fKaOFN=X1Sr@yC#NQL_tI$QL%`G7%AAcT<5^ii7U+*Y+Cuy?2(0l zM3S@1zazH#e0ZDcYrj7aMs2lhr+XWkr0XfD z;`9>j5)zns#Pc4eP6q1#c$~Q5E1`J7miww7Z-xWoJ5Dfosf**00S26mDJPurdPS z)#r2Y-x@VMN6H!ApS=971HH|vdB;Nc79FoL3NGSzvz5N8@-e5FCR`ELqB|qAG5agE zrATxW40sdR!fEla@QK_7yUlS z4hLurfTZ3a3kDrJi)5s-q`lQ{xgMa_ZMyXL87F(h<2 zBR7z{3(mH18oxc7XtD!q+de|PC-lED`vTt4k!1Yavn)2z$B92&fC(hq>IKP3aG3&^ zF0YN)fo2||)DK8JJR}g;067N0in2jUqv6R5Y_u?m37dp;85}XBo%S4Ez3!%w5Tr_g zxeZ6OAP41uXc2$=w|__y=@W);DWz!m67rspK+>&PYWCI#O9f zbfVw*9Fl`lmRE&x^HT_`x|&Dhqanj%?~ivE@&&2GBLk8bHGg?uLew{iNlDYLQwb(> zgj`?p1e5zdQ9f_{JoPToQ7`?sbF-n``1BuT0hJ*J&X#yQvV#%7a%fw*4fR~amaH!@mc$6Y*$r*rOQ{c6HDO74r0AcqzZ?O zEf`B!Tqq;?s`5@#?)9%l>hY1$d+X0X2q&(!XDhwMc!JWHOH*S|ztp7`Zl%vfj(Z`< zpzk)Wm`k~4kh|rohMU8eb%sf{k+GGL%zBL4@Jo}i%U1-Rfi^})Pxoeh6mxEQBx>MG zR4Q=O%A(raYLfprI=y)9fdWR}Slu8gs}X%`bJ$PF7Yey>pd#D0{enS~r=+f$maBt_BWoRtg+zdG1z|Wrl6O_}k7}B(5!j_d5{R?rzl;K}gA;cMuD*X;i zT~Cu9WG{>oh!YZi=e)D(4{(!Qbl;{1mA#SSA|_L!%s9lt!q(8x`-%V!y^y^Yk}0sT zu!y8(lhp9fj;KPRtD@qFjTly@8-J&CY7x*F(f)1sNLyXqx`%pw)5pl@ktI-p8P`Ur zgqc-J!$kZ(P?-)*8zaL2@b^ zHG9VAqN}IJ*2P5IB|tc7OHYltcX4)W+@}5BZx26vaTcoW>7H9F5*0+niR`$8834FvH*(E zUW=XGzg${lY$LfcFiHo^!K}n;+Sd)^QIO zLXoO&v@Gj1nX?a)KD#j}_CK_}Wl)@5&@CFl-QAtw?(V??gy8PMA-E;DyKC^^KG@*y z?(XjH=ixo)yLErvx_?d;RZzvuuz7ZO@7~?3*IHU#q?y}BylOGs&G=N^WLl0o`dpgD zng}qEF)bP|4kq|16qLz1bEi1#S)#$)IvS|yFoB2@JAM4hB`JvqlYzeIaxJu`_ZG*9`5nPxOjd36A^ij-p&v^Q-gH&BUt0OYH7De^ z_PV^5lYha@yOe6Tb#jLO=Z1clsSyr7v6Pc6B5~>+-jr4$7=i4$=}1MuV6RrzED!Mu zR7JG6Nociuu4t`ZFw4)M2>_kgN-=#i}l%2C!!0IEON+QUcV;AGFsm@~t- zQ+df!B7sWuxfKUupb_Iw?fqztTDa42)uFil^makHExWqMq#IEXcY{N8DD^V&`5#{s zvCk!!p43WA3mNC#CfnN)o%uf@U!RxBtPQcOXCs2ewiR|gpU1H`0IB|_yg59)880t} zLv-9%$F*ie|8MPK?#&OM(iS6&KwoU4C>=IyB7`#ql==s_CK_g|hl->B`wsy6vGz_< z-6*i{1mle&P|O}7$Y}PGtQo*OKSFoyqxbE!f5UmzM@p>F^rIa5VPMA{dfI9Li!IMb z^|N!yhPiI6o3d}|^|Ki2kPrsjQKH_w6&q5ob>G0vwe`}%L+%!Dl>8XeizKm8Bsv!} z0n5R>clg?%R{!=?UP>Y|W6Id|uhE2F2^WK*3of(#b~@i^`QbF=+jae}-`lDXhzZ*? zG(BBG&O%a8so>CDkie0XL}U*vSN(x#Pd3Ge!?o1FCUM6+r9mP zE;TN^NlS3;OEXtm{=rw2e&!c+uj)LBR3(XAgQ>Q07&}<8KS>#BL8a;1aZI6oF)<=F z6}4bI5mftOjNJ7p+YdX=fi?tvUk|Rf}5QOKs8s)CEx6bUCNvrO8e+=33q<( z(iS*lx#o9d+d^E?&w`OuzjmjD-uhrH@TG8!(+fL_aM>EdTGxQ(FzUD~75|hKC|8UC z&?^1N?x(4s*9G-^Zr>F74dRCrA+E$Oz1QQm^Ch2a$P3$!0=2B3C+{s}iFDqJvWgcD z!ONmI+Pw*@M`K-&sTHzna_(A}W|*-Ab4#9(V3^e-o5neW^oIeL)<8 z?6f~bORJs>!4hz-@8B_Dg4*%;TH|VBY9Vm5`pcAB43;R|Xy&N%@>T0vw+^!RGW*=* zBNzKPlf5Y!AJxlM>g zS{ECI$k~?Uth=6>5w6&$EdOCWw1~>X3>q~>`8}_3f$2r zuOBkOK;V)d&Q*TB#{m0rfF!})8s7d$8(O@MKMh?`z1gQN~mA)$+$Io&+;nU(xY^$?)SlF0+ku#l) zZZ{DpLQ=WZT7AV@y@9rf$R6b#R`Cm zFT(%nUMt13{9k^dn+NxYBc#?^05jpfio0ro?a^UNR^maRnz};2U_#I=C{kXx;vS3&k*`{%Q zGzkty`j!zR5ay2dGzapA`4ovLDv)^RcLj?Vb&q2G;ciw2_(Wu~|Cr|qS!bW)&y3OKk|`M3Nx*cfqY`+G5EZPB8JdLl`dm9YQ-`o2#gy5-2>GC1{|`ks>T?LJ z@*f;Oa(D%L>QXzfwU{48>Mv~& zb#E?`Unoyb17kCfg52pyz=>cCEg)G*%*_)0|9*D^Ovj`r@hP~e`#MBRfJtpOOey}T zOPV_qE?*nYyeC5|Az(=jJGYoyKQ+ZxT{HOiT>d8=!2Hp2f%Av%@n8&a-jD9kbz5Bn<9$-F8p2JaJ6BL3lvC*Qz|wfXo5fyW0{#E)nnh znY#7Dl`wI1uw%s54RQM!Pydy$SnI}jO(k$6 zikKQr4jM1hRR{ur1%x{Eyz3YIe}@7zF^R<0s;P|f;VPQ!gU(TREeGwbsv_3QH9RcL`+PHqWuP-F&1$AQ0CCn5p@DMo0&ca%ST zXdJg|xPOq|WuAz~t3a~$#;9|xzy#pgVJzm(!< zK|;q!ML5KB&JI7f?wWsGpVq|E0({mIV*St`PE>P&3@>xuh0xnXMhLJCdLJ8;O~))@ zP>j3?O>M2yg^2&$j;NOoIqyzG${M`Z4ZVN zdg`H@Z@pr6KH-6?2o?!#)q8dS3_9cWEn7l1c(LS*p);^x@w~pcF-M!=Ch25C1B&^p zyc85~EiIq#lWh4z0|Pljj%Xeqq3oq0HphrOAnVBBut(DZ%_&0rGM9g=aUCbtMh>$0 z{VS*G4CK1oz?3n1iWGCT-Xgjls#;hO0pA}g5R_qV&>A{5`O_uv8-WYV{^R!g3I}}q z^P{TB4qjUsn5-h78`yFt72<+#zMI*&;kqSRzav|}(!aX(qoEz_@p35-nA&L%=|v(x z=WTw9A@4kYxzn>P^@@>3m@57%H=mwPvg)#S(?1Ba-oAm}KQ&{tFmN0S0$;Vt+XQ_;9ye1qF$w zrec|t7Z(Ixmh0YvUG^E{rPr(bP~MQf1yMzz(CuMc%#RGX@EL>~IgJ`G0|Nt~kRYL> zLkI93mox2RRgR^G0ojQcVuxtmgBlrSe?Y?U0-KrWa-?=s)}n%l++p-y4%O`M&o|=F zAcW%yD`XvwpplL%#CF2rzV$7F3y;O=j({~<4?A|U>?<7_X#{) zBq@|o@rVxvs@n8cDy%&=Nii%)R=~zu46>JzhG8YR!q};Ph+HrIslx^&+;R_KV$8d6)mQholmFKLJuLR6@YE1 z0UY8L-^~9kRGWm^FDxu1vlu~{j%7xyuIlmwUBg1%PJgD|(NP5>7C>+JXs&{*@Al>f z*FgE(x8DGUh+73HywdA69bgfKlC<{IQdqp;Dt?O$57*xt&Db?*aXR{8G?K#nt*ud8 zQrCL9npntvUxxp6`^c^-yq-;^(dr z(w*`3AU-#|lyENW!TD)97_P`IL7RWm)RI+p)?JQkS{6MP2(vFU7l z+C)D_FlBYtNKY+uBIcgK&7fOvmY)(5F;9p(yD5j38N@f5gY6w-CF)S=8+~`DqTp^r z1mpm@EQtbDdIJfL{LwEFL*vvJ{W0Y84ff<;Wo7NpTJBQ6!p+h4-Y;G0=KRYI-hL4? zkS^hnV1y)MSOF`(c(=i(B_x=f3IIX`SW$$0re8R~dbDu3fr+x3!)|LAt-G<9ctn<6 zPnE^RaFmpkyP3NzV4(wS7zpKT;D{*)u%Nox=a7R*CxZ!e5j*}^xS~f0Pn62jO8YGU zHpw)DNt2UW<--p(wKP-i*6-L1n#SGh01V7Ll8%n9Yb#19a(4E|aF!6UR1^_MB5|wv zLb|-cA?il#yCMx9un^)N>W!h~oaiT7H1n0b`~%O%X)>khuqwFO;NQ)M#R2M$BVG=^OTjRh)gy&BzD*m2v*y`dc@xM1~sD| zzn*SS3aUGwT);U|8x{!X2@0daVL{Bvu=c^^xmxhL@$WrwxQU9Kw@1iE^ zH{AWdeIv;tnJnOVHGN?iL~2nkAlr;XV6fdjIu5YUTS=j601!~sk{>DeSoG@prQ%kG zpenujTUj^JS)FaqWG2p?NnAwQP4<3rE2lPV(_pE-5N?V97mFx=%v&HVX*(Qsh@d^U{c~OpTg_TU*Yeg5+NEY zuz>qu<4CM`8~$X4m0ANk59_y$Odu|bt%#0SJDzcwuD|T(jER8>Z&4G zZ;}oPe0TgbdRzZA%u2s;u{>6b6Zrb;S~iM_P?O36(@d{F%5$5XhKWh>1jxY~FTcgO z2C#ZP{xZtWP6+U-GZ)y1jg)t)MZ2VY+)cAhHMP*RnAhe9l&*{< z3-`BKjum@%1zYbItopD8(;!chc=T46laQV=SP&o~7-Fr^@_PD+8kqA@syHicD>+m~YCR+0a|123yd8{_M- zr^cm3e}P`BzbycQtl17rNd!b`LxnXz0xCt4Z2iBPezyL$m@v?;UHqnfx${-1HGU!7 z3ZR@td+;p?h?RG(rMlP|<))Q>5T$Y!G#}X(yp_srFuwY`Vtb*jeJL1n(4aG0pDIwD zmvAbIA~Hg2jTZ@M$K|S(XCV_sa)ZjebF+nrm7-w2dKaer=&lLyqSg)+3;_$#Kv_&| z3A8`?Cm?E~RJ1<+bUd3VmPQ@00}DMoE%_xMZv|T)>zRCapA`~EtZ%-{9i z77R3CW1TYQwW zW2a~n^Xr=Tx%c1aBV9{>E=Cwt`5&k?*; z&Q~sCV?jz{pSp#E9cAz)Le$k7MT$B;k^u~~cU>178tcKD!o-Vvp*XK51&s zO)@-)-?Dnu~Sw|qOE{d1S=<{Ul?KU(jNC3afOM7kOaqiGQJORfRsNEFaCocEVIA}wQz`!h7TOi#u_CAvFcw#q&=G@POeq{;@< z!}MHN)UU^TO!%brZ|n9~jo`t{Pxqd>;3^9yU2i99=Tly|tfhd>9R6uSTNbj5@fgY3 zdFHYv3;}`9z*Vd=I3LIPR50+S;-vHS<>vf}e;3`W>_i}0$SgHv?_iH>V|@c^AXaR$ z!ru>TxSjzmz-41;BzL1l>WDopi^l|!VfwvtuoFauDuMf1Z`jJc0CM7#=^sp~YzWH9 zQC+J_i@t7M3PD<&Cb}lF%Sj2keOv@`+nZ@T*4{>s8h3DEmpbN}&ze&*XBk8qh*;Fx ztL6ZC@6H1i;nD6~`t#vYY6PW2eRoHLu9s0omH2B%AyIpsC6*C4hKQIEAD)C~YpYvU z3eFR<$(oa8QhSPACF0z+5o?!ar>XIeZ?!3EKx|(D$ar(KVH64vq}=2nbX6r#e=ues z`m~4j&N5CD*0p+eo^h$EVHxd$EdWI{;j?xW)=z?{K4Pw~2`BTlHzd4G?h``H2;L$A zBV%)6AHpTKTpw<9B>9n$g2V~1Hd7O>#D8-dyd3e3V5^u@B80di1XreWm7qCMYhHvN z!v}Ox*-Cc*H>4*4$d2cvGg+JFjW;dSbG=jt&TgM`~H=)45jEiQ@*Q;u2N9Uru>FArbWf1r261Q_4_aDr*(G zye~-To+zDrAu#LhQWXI)s3W9iVfbYg+NM2-2#ktYeYTO+=DQ(+8dK1g_g?q~J6=cR z(Coo8I8||{;w{;qo99;HD3^|;=>$AD7FW3~k+QQp{jKonM_?yK)j&ds{Oh7Wf2~M> zL%`X|!uW|5Ey6%teCJ#mMk&OlWBF%IMO_QNWrgPNk36PRyPx-u4>_iGuCEclHY8uz z{ygby{oFUPuwu5kkA<45@AzNoS(^eV(+xgs+L18kF!7^tjtpl#_n+MN+X@1t6_wWE@VY<0Fx)L^LXKZecnF3Q+>A_C#rMi-? zIejRP2JWuM*n~8~J3~ewqQ|%I(P2xrx&f(&%B7l|(2US_`{VXj3(n)7VGpNE<(xS; zE3c`5A&0U$jnmr2)AsBnf1)0c>e=xJAZ5K`>8_OBYaMQ=D&*ljnmhwM2nS4?5l?ZZ zsklL^d~AI~-mqPLXW}~Ov$o4d;+rpbI%d!2_Qo^{K}N)J#?PCI%^SE+$+vvIZXS04 zQT=Oy1No3{oiIivrGgx6!mOiB|MpU=!np+mZ2l6Lw zLL`&zO-K$h;hhgaw+DH}27B*cHAc36BqrCY;D0j1>hG0X_yGB)X9PX>DaN_nO{oc- z2#wB(`=M9^TAIdfL0(7jaZQ($p?d2t&eJ9@!~)f%^i?xrqadz>yiMX3#rK6Vk^x0C z6EYGq)_M}?fP6?h4}Mz3|0Ztl-a{xlq$cD;hFKllyEUVK0BR(q6DxqO)O*{zxS`zGth5Y3hG?6?_JbKFkdVGWcdv_>n9q@s2w2YzB z6uqg^E|7+yzVJ*)|BZXeG1N9{DE0b@4v;>cbHt}<^x&tS@7x~8U;|)Uyub1pb9z-RN`Ham^GV3gL~qN zzP+#C_?;d+|GdFs#341`!Oi@Tpo2-*x-<7E?b{k_IJK^AhtF#@5ntqW$A&Z{fZA4~ zB7CG$EqK>tdFf*LbtA=05Si;k6W9Ut-~)4lsi#w&Xa|PvhMXb|t~!WnZ~QA?q?wR1 z2>ivqVkfDnzL$rMdt#(1a;ksvKmfv$RK#T~r2NhKE9!Mf893r1k=T$6y1?xG`RdVN z45g@K<1a9%8U5{ii(K-b3eIXh2!pt#4$cJf^*hY_zge9PbgaLkoHwCHeYQP5k`#Ml zzMZ)-n)+jd0L;^zH!Eky%l72pE;MWvquui>Y(uQ5vrw!3SEZ(S*PNm3ANXZ-lEc4k z<|aW;#exxPpeX}Yo|#oBpPBSg_s$Y)HQrUnK6E0Ix(#Mu?K58aM|zeb*W1Bm;Ot?=C@N*ZGUz731erx8lPC`S&Ikt zVQZI^mXYd7dBd=ZJ67hz_C2*M8V6=C;&Ym*{okItfL;swy&v??N_|tCpHY`Ty2(hp zC7WWxkz@z-v7yO22^ibS4VU65j}qt1mu-S97ApX7#C0r!G2{x_=4#+?qoyhpl+(mt zG=kO?jA|dwcl#$TJ(Dq``mDb|yzhC>`+uIjk4$No!xj3C{$t^CESS#CNRw;tz`Co* zD-k&@p(}>l`}!Dm+tkhei6h^%eXwX^q#B1oD|R@L?_jx6A|lka21%)gNPE?Q1n?q?H;YLUA)?0LF* z+#Ex3D#icGPXEyu26RX)ohDxEs==x0b?I^%dhWrDq?tRp!K)k@E>nKOs zO!5heT}-!&w32H3vuBb|YV%2AXhE;|7Xoje#GyA};=dk{U^C$+!5;=WzMi%7JQ*df zQBqA@qoFOenO_2}lSzyX?r8+qt98xmAiL40ybG20@WcR=nPElL{g;K>Rzu$EtDtqr zuxKGx3V|NPdZJdm==f@%R^lh69}&6Y+k#oavY=n4PlUZ>^PD(R?7c5!D3lDL-Tqb1 z8om=)AuLz&>q&<4Jo2Nt&lxCg->3`g?^pC?54n{3evN3HQk}+A0Qtng z1~+lPMDgqNp`l(K^W3KUU1hU-}2_TRqj zBD`dV@gFXwLccy7^_fZZh@wD<2&ZDi=!&7zPLfMwOOt%05Eg}@9Ju9VTXS3Gqvkt? zlGk@(YiN^FJk8J=ZE-whej4`BWcqONRW0QP-@3>kj=pYjR|KcN8e^tefHL?S3$wPP zO47`9B?AM8tC%dJIuo~LRwbLK31k(vD9pr*G+NJ>ptgrGCM!??hsF`Qe;FA}rW#9- z_;n?{M1rI8$uf~nZD%Zv(L}4AjmBU&_f-GK9ZqlQ04bb@ztl6+dFLHWr5*_!A^5N; zZrk3bOh2nM&T3ubt6Wf5Z5i>ELtt6&;;4}bzGVcVh+e}K+hCOtupcMgwV7cHwss$C zIsE8(9uj#TjA_l;_Qe(~0IDP&^fBP*`wu}nnZz^wPqveNvjpEv+k9$!HM^iLg&)DG zJYYtfsQJX92TKr>#h}L8V0=d93Q1a1&xa#5RLsk^v}5=^Hz!B;VQhw)n=vtiMC{vA zk{kGuRiD|?vZ{Z)4fmD-)K^M+Y(on{7>TMW5y@7KYUMT0>2wTrqe%=c7ZVZ>@TC6x zj1(b;31XEtkA7!M5{mRyi>4GT=nyf|q&dmrAL4=y$ox+_)uX#BSBVhiG#o1hw$G2y zp8}08IbUA!Ea9<9A8{m@2T5fYnR+=x=#w~2%9_Yb*3$XMk+Rswp?=&T$qbLQ^g2}N znia)l)eTdv!AUftso+j_m~?cRj%66l)^hWMjg#?)n559QsB{_0!NGbW_8x+s90_-9 zOP9@wmaa>ew#z0(N&s>oIEO+BhO{Yb^eSpu8Q zfUl@Bt9W;Q$!$^-6V%i1a2I*oPu_>wrm&5+vd)F}#^NvUt#IE~aYv<04!We#;SHYu zDvP5eX2YPAqFthsYK1-V)$q3$-e z2yYW8pHRE=+oio zrEK7_JU5#N_ZnSY7nBGoQd&J$p=I&%WpO4_f!YW<2{HvC6P0nAF?4`~XR;*4>!6T6 zw@bE4l*Dy`#6^B%V>;mbldZCmgVmSxE#$x4fC6NUxBwzx{q__gITXTEvCe;FHvMEE ztJOq;^22l-{+ak;fK2=d+^?CfwW1j7U$}~@b>`kGNjm=?##=Lkhl6G|zJw4!=^#8% zzR|5$@Z<=5bSL`}FOJf(CFABZbVGjuW@k~6&FT*1#cI-rb9Nf(d1B)zBw8b=qy;L=_@z_)?~Bt6CDtW% zg94iKsHWd=h2nA5CSMI)Ft-igKGur=tgeRO8K(Brtw&tx)a>)>(d<=3JXl9MD4E3r z)t9w;-9vq;xAKZGNumcGZz9Lm8i(xQ;l)1RXUXPd1_I%VJyu)^4TU4G&*5#b7V#*l zstzgvx{e!8Y_dq1QTo2x0?C)n54;`3{u}AG4XM=Dq1=B%ur*hXx0p{Zk5aj zoN0L$=bI^@=T1-cQJA;*d*GZZsge_zY@M+NyKbQ;ifg{)tX@+iTEbM_VuXsVtkzTv z?sC7Pt3wvA=7*|{VDfEkNQ)Wsi6Vp^x$Ua;Lt#N02NTG1=G&0j7kEB5SARDnom&V9#{%`P@^EpyQ|s4r}sV7*?CFKlg>p>Sj*N#bZOIu(u z`I?qpHMU4&G%&e^v&Jd5u*iRz|AI38QAA78Fk~=F2r$-t`w|yt#%48iWLKg4B|qWr znhKlr_9D^N&>GR5)NIjNZ~xw5=hqfFOuKU4xnmQ7DxP zTuHP(4yyDiTleyq=C|(3d~DoFZ2ehg)Mjj*>x|szXeT9u0J%1c17Tl~@OG3M?Zqj* ziA>Mo-Bzg>OY=Vq)dOw%GKe3+c1q1k#Xfx#uua zZ@ypA-RJ6XbkFFz=S_A9B^(A`+N!;;^@j{g+XuuNKPRSUXMIX{p2EM#HO`TCk@yyA zGtU&bFvx#r)xn~;@L@QKy}!t6L=g>6kP;dJtn(WHHA4~qWjM=c?fg_~>d{;v2Jw>; z=UWQMi|f>g+D2&HS*K!gmiWi+31P`loaH71LTl{NI#bKXRW8-`osAJQn&01Wc$C%e zF!ZpMzgW@17xAa7o$dv6vlg855)!F$rq$jUM<0Jbu}svs|7)`9O`?INjC*j>rm>q5 zV;c=;(gU;)8?^4tn^))rVw7!reoK!E%DFFF5uu()(u{2*p#Yxa&Z^Y?P#Fr@-lIkUH zG&~%`=Wvo1lW~}hl zcVudeTiG&NTyTc`GCU|#66d`@xxkMZTg{~FTiX>&L0sqeA@q|oIlLtmeffm zLxBlu+-C?w=>3kfSuo}90auLU&2`=R9^WW`Qz*q_*u!zYah1ktqiI0N4hQw~utPkwIrN$R~z|2FV zrAU>2jHU^M-cXG=|3O)*WbV+;6vqO(a-4?0=VUuE@A&1X^Z z%6puWW<2lET+dQS%z{^!Nyc2DS-=7?8yTc8lzDueM_NA}oK>W`AY{@PP#~H)%;Yyn zr?HjAr0YmIpxwmLgfLr~u^=z41WToOM@RHNfmAM5q3K4Z;k!f|wWj377(JH8Jv^{X+d6cnN z-wtKi#r3MWR@#@24CNPT0H4WQzM+DD;w6FzeZ`gAmC)jL zKWBf3KUgd4o0Gwar2vw?LLljj{eTFg`AwY+9Waqff2XL^fDEFK2%EpRNr?pczM_aP zVsu&(*Dhpbme(Xr)ZJ|$lN3$* z)Hn9%mReWtB~CA~$sV1Q1)6Ftrq-p@X*Ap0r|A%*4MiRVMwi2mZ7w`o(e#z((@2li zk0^()=tl=xB|1V7~3fH_9%0E}EunQNvPo>_d& zWS0eP4BM;~eWuo_O)}{n3fVx%5Vu+>G%?*)-GA=7&T?dbm&F9ZwKS#XOhtn(IlP4P1XhL1Z?w9wnBEDWt|7$Hc+!8t%^@ z6Y*Gh7N`Il=mQK>73u5wlT=JMUI}unIxhWK53{Q{Rj?TEybLdR_jBmK%3hx9%oXDs zxTj5jK~OEN8Mcs;rTj2Y`l!}{SA<8D+GWjm^NV-MXc=bq*9Q=ViifHJVr;)%z=$rOw`6(G;h2Lg?{!N^2gi`le3r z#?udJ-&Fw~m|S>Iz6yTw&?Tmdk^kC1tcs2g&O|R9(6>$bX$A z!s<#o$5vK+5^u@sIrvNS&qGa4w_i-5`gI)YmRh+CKT9el8sV6Ra5l(&=W*cnHjwd7 z4j<}f(FQF?idmP8#PybJr0Z0n;|j0CU?q;mtc0UqV-ie;a$p%esT`bw z0iT0hh(4Q3LUn?|lXOBFfL(P3*9Y}f^E!@k9G=O_Jx3nTS^xW*jNRMPqqgaPg?F>Z%NB>K*nVw0j${W-_KD&A5i{d z1n4?hAnwq#m1wY-fx|O4KhR2H=yT4vUA(t{PhW}Qh~xZfu+529LXN?F#r(>x7{V^{;T zYd4QGR@aRxf1U*mOG;_|Jl5UdoZeFa3R?KU=aFTbSfF&*`=&D};Qe%$9Tjfkhgrmt zy}?ZZpW1ZZT*>ZfX1b-3ZybfBs0BM{_WbUYBn2KjaEF2czKi}6b>b9#|0xF@aH7d) zfE=s(>8x!{*XQM?6YKpI*AT!js%z`&7BJPc4WtO2;#XX^A_B_Z-?a}5=JEjG zjn>oult5>#Pnu3nbvlkC9JhmX74uwe5AxUpg_-VyyCZ3P4=2?_jGP9Y5X}b#X~&r! zrx#nu-0L6O6sV?}`!>Xekp=Jd^)ektd7SsN5qzdN&WEp^$o*0BqsV}~Qe^+M>4-6@ z;hlF6w3}+V2=EkbOtf8II^eUJhRdh2515=a?qxb&;Pbmy%96m^TUlGz`Mh3b-LCr~ zyT=3EEySH$q4Jhn%_#BPADWt)+Mo~; zvY{x!$A2TK97A`g2A(%FY9|5Mx|ItxW=pb6E!z{?WX%A()!jb^G-;yvJH9jU*4UrR zJ)Ffvw7Q*#c3_8XnOg62!uZ{Qx(N;8duA-c9Sxo1C4M)nWh>wTpgfQ3=5!rXmH<*V zfhG?9_7C_hhW>ts)0Id^fLK466A4-hp=j~kE>kMewL6*sA&}(-%r4_j-jm0QX$NORDwt4MkG?Hf*$Cm3W zt^z=Y){|MSRcjt|*0!G$rcDqsP2{p{@4R4=#DLz{_V#jidzJOJT;BHT&Ij-Yp43h$ zXi?9o+l} z2fvmS7hj5FvQ99Mel(7Q=!aA4k>_0KCXZ7okgKxwd0Nt4Sxys4l!>P^gdXF+3YX>F zX$7ppeLEqT3{`3n;LTtE1O3eJpAPyPY!d#eJPMXP1nA~;d=j~q?fitcHoAfU<*gJh z#0G%!>e>DOSt0ZB$VvU04M1*qM}slb;q`c3l`R(3Bu=kU-JQP`&NgbtBXU^1P$~r# zgHtRM5=a^&UJ#A7*ltgsgU(k-5?8CIZ4|))79dxu5kunXb_Ni;CY=jQf31RxFA-=w z#G?<5ixX+5{(OdBRNy-?*0QckC<872be=Z|a>L`aN){r|({aRcb!r>U%0V>LamIyKwV zwzpSC2EgU(rLYegL=|@c1Dj%OxmK>gS^?UBIQB`wO)Xg!FHnlxb7kN;Ah2bJ}_Xe1tJibTw>yj;3Lpx$`(}EVOQY*wvMk=3~8U z9P=9Gdm`E3JFfJzZrBLaEk7pzBm<4P7(7`O0LmzRcW}Mg4wK96fmMTGR;l$6ezM!| zcgVP`-I9?62V)X2q}|0*V(Y=NzA!5C@)1J>GefniVU>uD!e6-=e-KZ>`kIrn1}~Gw z5=dcEs}3GhhxSy#m|B(KY^alIZcBX+#~{~lfgQ}?46YS2-X|^grG?H3eY+BR+d6@d zv7-DZP;YcN)bP*6}Ril7IVuNJ7c1ye$6=WzFU`b5uqCR2I3w!&S@Uj3K-vME7XG~D{~bEP3eso+_x|6? zzN(g*BBBE6=9+(TTF=)%C@kFw0>Kt>l4)$c{kZ~~E@FJ#i(M0Oi64`VnAA`GtbeH+ zJPPtwVRX6GdKd|1s5Pi9PB^J9aV;*fEF&4gz8lhAW)twox(f*<(0bs+f*^V+jDN&( ziw|D(5!(V(l6szU;M2ebp5)$$KNTs147%tSMg$V4K?YW#A|x^OdH}PWU>=eY1Ci6T zlAxEinh3H!uP(EkB0*FYMhKyN^seDPZRE4dvM$2j1bIs4s1DCO$DMz>(9>~^V0aAh z)Fwr?C_*tmiRkMe0cS-Ym0a?AZ2!H>_Znb&#!GM-L9F1{USBY&Dr4g|8a7LJbhdIZ z3>lZ)QeT57!1LK4g-aej(zo1+P#YocuqM~l=b=x~Q~o>Kxj*XSwINHHKro2aFfh(> zOki#&wTkS|O&dU;B`8QX7AZfknMaUfn{ugVSytC19*jB)eCE86T6j@^B`@$; zzJ8X;Q727a@n<>txB1e6?7hxrWL4FJC1?zL`KKLqLyQcCr?=b46ryQ%f|L(T7W-6j?l6 zs-M9GY?L=;0r#xUbx$Tp1jD7&KU7g@5znTnY~ zi7b5vCEcsU72>uaLm0GJlVvO+*-EnXJ?;t${~6uo3u zfR-{6S@gbO=)LW{nNR5-C>uaGZ8AkGJV}uZ&NFU7-`ZyPR@RQgT%))jN4_VZa?LB{^e_{$(3ZEJ+(+HS0D<*-;= zzmy2U9a2MK<)_^Okv^|=S}S+;iO3gote=O?tq>AisNZvDY^7zr_d#y(^&3A72K*~* zks7;(G+DZ$tqV{Y&BA2jd+8A`ak(;wK2evpSr6mImXPbF;_9bc(=ZA`rMt+^!FY@+ zS)SqmWNeWtOgd0bn}_8{;nqpt)IEUP9GoXC(hQ)i^$DUHxk*=%CIE=kOiIl*EXlFR z;UG7Mmva1;?a2$k+T0&6E<&{v`R#eeJ&zZJ84k6M$eo^+nT$fq8V-KXJR?3ZZ3F5} zJj$>qCYLBIvf3Cf_C@R&$FO#0jHGzG3BHtRB~2>cJ(j8Ae8vFfdH;@i1WNn)$X_fe5#c3(Pz~Cz1Qs6TWALLIz!}*6_cSLvd_qxAb@8|CuZ`C@u-nqr2OrZ4v67g7M&?q9RoQVH}FNcn| z#VRwO=~jqiPcaz;U%KjRy30Xyydm~vP`FfwIqhMr)@|vs6ecpBh~FyXF;$HxB7Uk{ z3pe?&1)@_+T^mq>L<(6`SYFFTUNRlK{jGjY!D#8le%(0;ogSp{cdxW$hPJ6EF!3&x z8)Nyt@~qBaw{ja<)_rB)-&SJiFB4Ip&VI5j$}qUg+=~fmBW+T^_uVLP@;k#TT#R=m?qU2MriLa|IWo%IYC}^UDWZ8%arkV&5F38XSWIA}DNnBC`qS z#SwQU90hT-BuU0b8z!`r@C9{LpG3PF@wIlQEWF?EL5<}4^zj$L3$#om`@}Uc)M9~l z5Q3q{LD*x|Q9!E8Ea7U%jU95x6a9lhl3%yKn7^jo%fXqSk>a}YQlN2Uk5_UmqrgMD zaXQx;VDH=-OZd;yY6tqs_8df4q z$hC@kpMnc4sUmhaU_wem`%g(HI{t)dOWAKCFy-MfB>p776(R>;LnPjP+}%YzbR?mzTP=NU>`bX43YsE? zH-o80+&iA-8ZmN>@)5_?0n{veuNP$Sj0=f~h`;6`5T_h?q} zS+#U}A2$B;uaD2_BDbwQu)8}EAgPgIn6}sP*cKwnGQTO)ZJe*J&5>sXz)v9Z(bLbdg3MrdrMWGKOx=ip+#6`|Lg{bC~U_3dCtPZ9#)~ z&dMV~g;<%+DsC`san--kSG{0oD>@!<*JyB8gk-p`FyA+_<=se9VJ*6RgdHuT%rgQ0 znDU>``xf2&&-%Fb^P8c6+K)%~|Fw{!8gH)9XStk8S9ZF(4e2tf?^2`uysqksko}Sa zOTQj{z@fTC&f-$OD*@nIGfO%)CcuM!%|1hkFI9XJ!g1Z)V4Glm}29sf!BNbX-k`+(-+q zg@Hls5d}>>`?MElPuORw3}hWQIJ<#eZRE-ab-uW%$O)4-Ct?8DC=e?57<3 zXVYN?GRA4qryw!>U`l+ZU!j$;UQz0KHwBv=zUG#K^GDKkw2sccqzXJgDw?u=iC;T> z$LO#f{ZOawv}ym2LTUKd)oQ7+iG{A>0F`E)M>_+?rmWWsDBVJbedsy8s>q^{T`OeCPK#b8dqX>ttNo3;^zXxHPFLe=Wr*yd_E1-BPVD|O8?&>V$fy@E#7 zM!4E!^G2xuPY0cs2K6Cd&QYy)mA#$UT&}acpcsDbw^p@*NuZ+g-85`v4KaZwsVgQa zYON=STRBPz9U6(j{_{&|`|m5V7J=FAWhg$*X1OL8q1u_$VpolJU+ zt-$Ji24CIdH!izayyBM5O-WkY(*OJ^yXOaMd}7rdKW;@s4D{x8k=kOrYk1RH)Evb+ zMJk}oH5QYI_cV3-d;Iq4C?Xp?t$aV3`tV_gm+51!4l#|6YtBFfS3y0OSMRlfdwDHS z4$=`Hn%YS1C$7*@10}cBNPCsjlaTm4rmQprpp?AvwnP(A8p51eE2oybYn~Y14O@K0 zAc6O`Dgq&?ECsr2P+d%%2XI1oPr$QJ6JC38Ha5)6V2?{)W*iL|{>9DW6IynMXyE&3YPO9 z^u=$11*;G3ytl(_qhOL(s|;HPe;!oss((IL`}R>d%J9uU=C3)sNRzP$S)Fu>8h1f;b8A=B)Ddvf5S)sTL1ouSTQF-+($ab$kgxvdWxP93n#w z*=Mdu@<~2P-l%hn}SV>Su@0hR>^U#{sgjNC8 z%3Giz`p(lNB}kOu*{_dSA(4k}14@2J;j=gDZxcZHWYe@y|MoW> zI;ak+Yl+mvo!$n;AgaNg4~db9*|3}qx;U-b2)>K!C?@;r{6Tkb*ocCsbh#1K@BZuZ zJBO9<^g2vgVs+pPQmr`X{&1%x`luqlWzckGU_li%43(kv*&B|CX&dt*=(u2dW~Sfr z6u$u>(wecdT8P=;Pj<%s=<_)%qtT#XEsVsbkq-~UL@WM$!Y;j(-!Pu#HWxxFh@8ld z)iPD~EwmYM!gpI?Vk&Q_1zXF=Q;RfcHw`5qJVb%C#41 z3l)SSRH~$d$#|(hu>$bm~z#zUFAWyxfttG%$U;WClJRRz@;dUCMb;TS;#>)WvsaE zcg)X3d`l@&UExQStK-iB(vfO(tRQu)#Oc8SHI};0C_jh{<5e6I!A2d0k{gx-sttfd z=X`MFq)*;B>eQmy8Lq&vLE@`fc0b!&gzP4UCi&!D!A0@eh|(vK;ak74{MK(=;%Xu3 zF7$n|@gpSOY&!&xy#FOSfK`n$N=A>X$L2XCo#2%ibm{R`O_ zoOtm;)yntUvB4>wxW}PPT&u;EW`!W(&s_(rUlyJlBuqXfLa!`R)u`~4(s`jP_l`Ri zNRIf-{L6BL7j-L)7hX)0F%;ejn`7&OUQOYiH@^bkkij2~V5G_HZGxnL^0U})nDYdQ z7D_8?!+-J!U&G1jiscWIGy+8{Yd2beiO%OVR!oHGhD|dI##Ymh9$fI-!Hrzpf+cz- zcF^;2#CVpKZstf1b+eZs1Ijv8&y+p?rI0|E?J_1xX{aZYK^@fJG#&5)?gEf|bR zqq}ZxM;V$1T)A-2Fpxvd}+*k_X3|j#Ex|<7l-c8dKQbX_fbZi8XSIVK(Sc~m` znEZnWLP{Dq2l-Hdw#d$+9)rp3gSmCpPTo`mq`j=-4m9J?g{|>dTPj{CNmeI)Ep)CpDOqUGS{|r z!8{J+)NR3tEqQQj7qE}W`uE6|Ikj~J-nH|;tNt^vwroV`tRs-A{{QGU=YGTud)NrV T-;=&=gEngmd!USVF5&+GyP)S9 literal 0 HcmV?d00001 diff --git a/website/static/img/60-days-of-ia/blogs/2024-04-17/7-4-2.png b/website/static/img/60-days-of-ia/blogs/2024-04-17/7-4-2.png new file mode 100644 index 0000000000000000000000000000000000000000..7076ded30f234476092d84f3df763773d57ab6fe GIT binary patch literal 38141 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 T8UyFn0Uz&E7!Tgb&#(Rmva9i} literal 0 HcmV?d00001 From a69721eb4bfd322903ad7b7dfd8e55906d03e0dc Mon Sep 17 00:00:00 2001 From: josephatcatalysis <106772721+josephatcatalysis@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:32:53 -0700 Subject: [PATCH 2/8] Updated Blog 7.4 and 7.5 --- ...-time-voice-sentiment-analysis-system-1.md | 6 +- ...-time-voice-sentiment-analysis-system-2.md | 782 ++++++++++++++++++ 2 files changed, 784 insertions(+), 4 deletions(-) create mode 100644 website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md index bf1320edd0..7443c36407 100644 --- a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md @@ -19,19 +19,17 @@ tags: [Build-Intelligent-Apps, 60-days-of-IA, learn-live, hack-together, communi - + - + -![Graphic with a chat bubble-meets-robot head in the top right corner. At the bottom of the graphic is text that reads, "Personalizing Education with Generative AI and Retrieval Augmented Generation: Creating the Chatbot."](../../static/img/60-days-of-ia/blogs/2024-04-10/7-3-1.jpeg) - ## Real-time Voice Sentiment Analysis System Using Azure Communication Services, Azure AI and Azure OpenAI (Part 1) ### Introduction diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md new file mode 100644 index 0000000000..2e197eee06 --- /dev/null +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md @@ -0,0 +1,782 @@ +--- +date: 2024-04-17T09:00 +slug: real-time-voice-sentiment-analysis-system-2 +title: "7.5 Real-time Voice Sentiment Analysis System 2" +authors: [cnteam] +draft: true +hide_table_of_contents: false +toc_min_heading_level: 2 +toc_max_heading_level: 3 +keywords: [Cloud, Data, AI, AI/ML, intelligent apps, cloud-native, 60-days, enterprise apps, digital experiences, app modernization, serverless, ai apps] +image: https://github.com/Azure/Cloud-Native/blob/main/website/static/img/ogImage.png +description: "In today's fast-paced digital world, understanding customer sentiment in real-time during voice calls can provide businesses with a competitive edge. This guide will show developers how to build a robust real-time voice sentiment analysis application using several key Azure services. In this second part, we will develop the backend to handle interactions with Azure Communication Services, Azure AI Language and Azure OpenAI, connect it to the frontend, and deploy to Azure Container Apps ." +tags: [Build-Intelligent-Apps, 60-days-of-IA, learn-live, hack-together, community-buzz, ask-the-expert, azure-kubernetes-service, azure-functions, azure-openai, azure-container-apps, azure-cosmos-db, github-copilot, github-codespaces, github-actions] +--- + + + + + + + + + + + + + + + + + + +## Real-time Voice Sentiment Analysis System Using Azure Communication Services, Azure AI and Azure OpenAI (Part 2) + +In the [first part](https://azure.github.io/Cloud-Native/60daysofIA/real-time-voice-sentiment-analysis-system-1) of this topic, we setup all the Azure resources like the [Azure Communication Services](https://learn.microsoft.com/azure/communication-services/overview?ocid=buildia24_60days_blogs) for VoIP and PSTN, [Azure AI Language](https://learn.microsoft.com/azure/ai-services/language-service/overview?ocid=buildia24_60days_blogs), [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview?ocid=buildia24_60days_blogs), and developed the frontend for a simple yet functional UI to make voice calls and display closed captions generated from the conversation. In this second part, we will develop the backend to handle interactions with Azure Communication Services, Azure AI Language and Azure OpenAI, connect it to the frontend, and deploy to [Azure Container Apps](https://learn.microsoft.com/azure/container-apps/overview?ocid=buildia24_60days_blogs). Let’s get started! + +### Prerequisite + +To follow this tutorial, ensure you have completed the [first part](https://azure.github.io/Cloud-Native/60daysofIA/real-time-voice-sentiment-analysis-system-1) of this topic. + +### Setting Up the Backend with ASP.NET Core + +The backend of our real-time voice sentiment analysis application, built with ASP.NET Core, plays a crucial role. It will handle interactions with Azure Communication Services for voice calling, manage sentiment analysis with Azure AI Language and Azure OpenAI, and serve the necessary tokens and endpoints to our frontend. Let's set this up step by step. + +#### Step 1: Create a New ASP.NET Core Project + +1. **Open Your Command Line**: Navigate to the directory where you want to create your project. + +1. **Create the Project**: Execute the following command to create a new ASP.NET Core Web API project. This command will generate a basic template for a RESTful API application. + + `bash` + ``` + dotnet new webapi --no-https + ``` +* **Note**: We are turning off HTTPS for simplicity in this guide. For production environments, HTTPS should be enabled and properly configured. + +#### Step 2: Add Azure SDK Packages + +Our backend needs to interact with Azure Communication Services, Azure AI Language, and Azure OpenAI. We'll add the necessary NuGet packages for these services. + +1. **Azure Communication Services SDK**: This package allows us to manage voice calling and access tokens. + + `bash` + ``` + dotnet add package Azure.Communication.CallingServer + ``` + +1. **Azure AI Text Analytics SDK**: We'll use this for sentiment analysis through Azure AI Language. + + `bash` + ``` + dotnet add package Azure.AI.TextAnalytics + ``` + +1. **Azure OpenAI SDK**: This package is used to interact with Azure OpenAI for more complex sentiment analysis. + + `bash` + ``` + dotnet add package Azure.AI.OpenAI --prerelease + ``` + +#### Step 3: Set Up Configuration + +1. **appsettings.json**: Open or create the `appsettings.json` file in your project root. Add placeholders for the keys and endpoints for the Azure services. Here’s an example configuration: + + `json` + ``` + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "AzureSettings": { + "AZURE_LANGUAGE_SERVICE_KEY": "YOUR_AZURE_LANGUAGE_KEY", + "AZURE_LANGUAGE_SERVICE_ENDPOINT": "YOUR_AZURE_OPENAI_ENDPOINT", + "OPENAI_ENDPOINT": "YOUR_AZURE_OPENAI_ENDPOINT", + "OPENAI_KEY": "YOUR_AZURE_OPENAI_KEY", + "OPENAI_DEPLOYMENT_NAME": "YOUR_AZURE_OPENAI_ENDPOINT_DEPLOYMENT", + "COMMUNICATION_CONNECTION_STRING": "YOUR_AZURE_COMMUNICATION_SERVICES_CONNECTION_STRING" + } + } + ``` + * Replace the placeholders with your actual Azure resource keys and endpoints. + +1. **Configure Services in Program.cs**: Read the endpoint from appsettings.json and configure the services in the `Program.cs` file. + + `csharp` + ``` + // Set keys and configuration + var azureSettings = builder.Configuration.GetSection("AzureSettings"); + + string GetSetting(string key) => azureSettings[key] ?? throw new Exception($"{key} is not set"); + + var languageKey = GetSetting("AZURE_LANGUAGE_SERVICE_KEY"); + var languageEndpoint = GetSetting("AZURE_LANGUAGE_SERVICE_ENDPOINT"); + AzureKeyCredential credentials = new AzureKeyCredential(languageKey); + Uri endpoint = new Uri(languageEndpoint); + var textAnalyticsClient = new TextAnalyticsClient(endpoint, credentials); + + var OPENAI_ENDPOINT = GetSetting("OPENAI_ENDPOINT"); + var OPENAI_KEY = GetSetting("OPENAI_KEY"); + var OPENAI_DEPLOYMENT_NAME = GetSetting("OPENAI_DEPLOYMENT_NAME"); + OpenAIClient openAIClient = new OpenAIClient( + new Uri(OPENAI_ENDPOINT), + new AzureKeyCredential(OPENAI_KEY)); + + var communicationConnectionString = GetSetting("COMMUNICATION_CONNECTION_STRING"); + var communicationClient = new CommunicationIdentityClient(communicationConnectionString); + ``` + +1. **Implement the Token Endpoint**: + * In your ASP.NET Core project Program.cs, create a new endpoint to generate and return an access token. We'll call this endpoint from our client later. + + `csharp` + ``` + app.MapGet("api/token", () => + { + var identityAndTokenResponse = communicationClient.CreateUserAndToken(scopes: [CommunicationTokenScope.VoIP]); + + + var result = new TokenResponse() + { + Token = identityAndTokenResponse.Value.AccessToken.Token, + PhoneNumber = "+12533192954", + }; + + return Results.Json(result); + }) + .WithName("Token") + .WithOpenApi(); + ``` + + * We'll also need a TokenResponse object to return the token and phone number: + + `c sharp` + ``` + public class TokenResponse + { + public string Token { get; set; } + public string PhoneNumber { get; set; } + } + ``` + +#### Step 4: Implement Sentiment Analysis Endpoints + +With the services configured, you can now implement the API endpoints needed for your application. Here are the essentials: + +1. **Token Provisioning**: An endpoint to provide access tokens for Azure Communication Services. + +1. **Sentiment Analysis**: Endpoints to analyze sentiment using either Azure AI Language or Azure OpenAI. + +`csharp` +``` +app.MapPost("api/sentiment", (SentimentRequest request) => +{ + var result = SentimentAnalysisWithAzureLanguage(request.Content); + + + return Results.Json(result); +}) +.WithName("Sentiment") +.WithOpenApi(); +``` + +This endpoint takes a SentimentRequest object and returns a SentimentResponse object and calls one of our sentiment analysis methods, which we'll implement shortly + +`csharp` +``` +public class SentimentRequest +{ + public string Analyzer { get; set; } = string.Empty; + public string ParticipantToAnalyze { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; +} + +public class SentimentResult +{ + public string Sentiment { get; set; } = string.Empty; + public double PositiveContentScore { get; set; } +} +``` + +We'll create a method that uses Azure AI Language and another that uses Azure OpenAI. Here's an example of how to implement sentiment analysis using Azure AI Language: + +`csharp` +``` +SentimentResult SentimentAnalysisWithAzureLanguage(string document) +{ + var review = textAnalyticsClient.AnalyzeSentiment(document); + return new SentimentResult() + { + Sentiment = review.Value.Sentiment.ToString(), + PositiveContentScore = review.Value.ConfidenceScores.Positive + }; +} +``` + +And we'll create method endpoint that uses Azure OpenAI: + +`csharp` +``` +SentimentResult SentimentAnalysisWithGPT(string document) +{ + var chatCompletionsOptions = new ChatCompletionsOptions() + { + DeploymentName = "shawn-deployment", + Messages = + { + new ChatRequestSystemMessage(SentimentAnalysisPrompt), + new ChatRequestUserMessage(document), + }, + Temperature = (float)1, + MaxTokens = 800 + }; + + Response response = openAIClient.GetChatCompletions(chatCompletionsOptions); + + return new SentimentResult() { + Sentiment = response.Value.Choices[0].Message.Content + }; +} +``` + +#### Step 5: Test Your Backend + +Before moving forward, make sure to test your backend. Use tools like Postman or Swagger UI to ensure your endpoints are responsive and returning the expected results. + +Your final backend should look like this: + +`csharp` +``` +using Azure; +using Azure.AI.TextAnalytics; +using Azure.AI.OpenAI; +using Azure.Communication.Identity; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// Set keys and configuration +var azureSettings = builder.Configuration.GetSection("AzureSettings"); + +string GetSetting(string key) => azureSettings[key] ?? throw new Exception($"{key} is not set"); + +var languageKey = GetSetting("AZURE_LANGUAGE_SERVICE_KEY"); +var languageEndpoint = GetSetting("AZURE_LANGUAGE_SERVICE_ENDPOINT"); +AzureKeyCredential credentials = new AzureKeyCredential(languageKey); +Uri endpoint = new Uri(languageEndpoint); +var textAnalyticsClient = new TextAnalyticsClient(endpoint, credentials); + +var OPENAI_ENDPOINT = GetSetting("OPENAI_ENDPOINT"); +var OPENAI_KEY = GetSetting("OPENAI_KEY"); +var OPENAI_DEPLOYMENT_NAME = GetSetting("OPENAI_DEPLOYMENT_NAME"); +OpenAIClient openAIClient = new OpenAIClient( + new Uri(OPENAI_ENDPOINT), + new AzureKeyCredential(OPENAI_KEY)); + +var communicationConnectionString = GetSetting("COMMUNICATION_CONNECTION_STRING"); +var communicationClient = new CommunicationIdentityClient(communicationConnectionString); + +const string SentimentAnalysisPrompt = "Please analyze the sentiment of the following text. The sentiment can be positive, negative, or neutral. Response on with one of those three values"; + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.MapGet("api/token", () => +{ + var identityAndTokenResponse = communicationClient.CreateUserAndToken(scopes: [CommunicationTokenScope.VoIP]); +  + + var result = new TokenResponse() + { + Token = identityAndTokenResponse.Value.AccessToken.Token, + PhoneNumber = "+12533192954", + }; + + return Results.Json(result); +}) +.WithName("Token") +.WithOpenApi(); + +app.MapPost("api/sentiment", (SentimentRequest request) => +{ + var result = SentimentAnalysisWithAzureLanguage(request.Content); +  + + return Results.Json(result); +}) +.WithName("Sentiment") +.WithOpenApi(); + +  +app.Run(); + + +SentimentResult SentimentAnalysisWithAzureLanguage(string document) +{ + var review = textAnalyticsClient.AnalyzeSentiment(document); + return new SentimentResult() + { + Sentiment = review.Value.Sentiment.ToString(), + PositiveContentScore = review.Value.ConfidenceScores.Positive + }; +} + +// // Function to get a response from OpenAI's ChatGPT +SentimentResult SentimentAnalysisWithGPT(string document) +{ + var chatCompletionsOptions = new ChatCompletionsOptions() + { + DeploymentName = "shawn-deployment", + Messages = + { + new ChatRequestSystemMessage(SentimentAnalysisPrompt), + new ChatRequestUserMessage(document), + }, + Temperature = (float)1, + MaxTokens = 800 + }; + + Response response = openAIClient.GetChatCompletions(chatCompletionsOptions); + + return new SentimentResult() { + Sentiment = response.Value.Choices[0].Message.Content + }; +} +public class TokenResponse +{ + public string Token { get; set; } = string.Empty; + public string PhoneNumber { get; set; } = string.Empty; +} + +public class SentimentRequest +{ + public string Analyzer { get; set; } = string.Empty; + public string ParticipantToAnalyze { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; +} + +public class SentimentResult +{ + public string Sentiment { get; set; } = string.Empty; + public double PositiveContentScore { get; set; } +} +``` + +:::info +Earn Microsoft-verified credentials for cloud-native app development skills by passing the [Azure Container Apps](https://learn.microsoft.com/credentials/applied-skills/deploy-cloud-native-apps-using-azure-container-apps/?ocid=buildia24_60days_blogs) assessment to elevate your professional profile. +::: + +### Connecting the Frontend with the ASP.NET Core Backend + +After setting up the backend and the frontend of our real-time voice sentiment analysis application separately, it’s time to connect them. This connection ensures that our Node.js frontend can communicate with the ASP.NET Core backend to do things like obtaining access tokens for Azure Communication Services and conducting sentiment analysis through Azure AI Language and Azure OpenAI. + +#### Step 1: Get the Access Token + +For our application to establish a call, the frontend needs to obtain an access token from Azure Communication Services. In the `app.js` file within your Node.js project, modify the `initCallAgent` function to fetch the token from the backend before initializing the call agent. Use the `fetch` API for this purpose: + +`javascript` +``` +async function initCallAgent() { + try { + const response = await fetch('/api/communications/token'); + const { token, userId } = await response.json(); + + const tokenCredential = new AzureCommunicationTokenCredential(token); + const callClient = new CallClient(); + callAgent = await callClient.createCallAgent(tokenCredential, { displayName: 'Caller' }); + + attachCallListeners(); + } catch (error) { + console.error('Failed to obtain token:', error); + } +} +``` + +#### Step 2: Implement Sentiment Analysis on Captions + +Our application should analyze the sentiment of the transcribed call captions. We’ll capture these captions on the frontend, then send them to our backend for sentiment analysis. + +1. **Make Sentiment Analysis Request:** +* In the `captionsReceivedHandler`, make a request to your backend sentiment analysis endpoint whenever captions are received. Update your captions event handler, under `if (captionData.resultType === 'Final')` to include this: + +`javascript` +``` +if (captionData.resultType === 'Final') { + captionContainer.setAttribute('isNotFinal', 'false'); + transcript.push(captionText); + + // Call the sentiment service + fetch('/api/sentiment', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ content: transcript.join('\n') }) + }) + .then(response => response.json()) + .then(data => { + // Handle the sentiment response + const sentimentText = `Sentiment:${data.sentiment} (${data.positiveContentScore})`; + document.getElementById('sentimentScore').textContent = captionText; + + //update sentiment UI + updateSentimentMeter(data.positiveContentScore); + + }); +} +``` + +#### Step 3: Display Analysis Results + +Finally, utilize the sentiment analysis data received from the backend to inform the user about the call’s sentiment in real-time. + +1. **Update the UI Dynamically**: Enhance your frontend to dynamically display sentiment analysis results as they are received. We'll add an indicator for positive, neutral, or negative sentiment. + +`javascript` +``` +function updateSentimentMeter(score) { + // Ensure score is between 0 and 1 + score = Math.max(0, Math.min(score, 1)); + + // Convert score to angle: -90° for 0, 90° for 1 + var angle = score * 180 - 90; + + // Rotate the arrow to the corresponding angle + document.getElementById('meterArrow').style.transform = 'rotate(' + angle + 'deg)'; +} +``` + +Create new elements in the HTML to display the sentiment score and meter: + +`html` +``` +
+

Sentiment Score

+
+
+
+``` + +Your complete code for app.js should look like this: + +`javascript` +``` +import { CallClient, CallAgent, Features } from "@azure/communication-calling"; +import { AzureCommunicationTokenCredential } from '@azure/communication-common'; + +let call; +let callAgent; + +const calleePhoneInput = document.getElementById("callee-phone-input"); +const callPhoneButton = document.getElementById("call-phone-button"); +const hangUpPhoneButton = document.getElementById("hang-up-phone-button"); + +let acsPhoneNumber; +let tokenCredential; + +let captions; + +async function init() { + + const response = await fetch('/api/token'); + const { token, phoneNumber } = await response.json(); + + tokenCredential = new AzureCommunicationTokenCredential(token); + acsPhoneNumber = phoneNumber; + + const callClient = new CallClient(); + callAgent = await callClient.createCallAgent(tokenCredential); +} + +init(); + +callPhoneButton.addEventListener("click", () => { + // start a call to phone + const endpointToCall = calleePhoneInput.value; + + const guidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + const phoneNumberPattern = /^\+\d+$/; + const userIdPattern = /^8:acs:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}_[0-9a-f-]+$/i; + + if (guidPattern.test(endpointToCall)) { + call = callAgent.join({ groupId: endpointToCall}); + } else if (phoneNumberPattern.test(endpointToCall)) { + call = callAgent.startCall( + [{phoneNumber: endpointToCall}], { alternateCallerId: {phoneNumber: acsPhoneNumber} + }); + } else if (userIdPattern.test(endpointToCall)) { + call = callAgent.startCall({ communicationUserId: endpointToCall }); + } else { + console.error('Invalid input. Must be a phone number, user ID, or GUID'); + return; + } +  + + call.on('stateChanged', async () => { + console.log(`Call state: ${call.state}`); + if(call.state === 'Connected') { + console.log('Call connected'); + + + captions = call.feature(Features.Captions).captions; + transcript = []; + try { + if (!captions.isCaptionsFeatureActive) { + await captions.startCaptions({ spokenLanguage: 'en-us' }); + } + captions.on('CaptionsReceived', captionsReceivedHandler); + } catch (e) { + console.error('startCaptions failed', e); + } + } + }); + + hangUpPhoneButton.disabled = false; + callPhoneButton.disabled = true; + }); + + hangUpPhoneButton.addEventListener("click", () => { + // end the current call + call.hangUp({ + forEveryone: true + }); + + // toggle button states + hangUpPhoneButton.disabled = true; + callPhoneButton.disabled = false; + }); + + const captionsReceivedHandler = (captionData) => { + let mri = ''; + + if (captionData.speaker.identifier.kind === 'communicationUser') { + mri = captionData.speaker.identifier.communicationUserId; + mri = mri.slice(-8); + } else if (captionData.speaker.identifier.kind === 'microsoftTeamsUser') { + mri = captionData.speaker.identifier.microsoftTeamsUserId; + mri = mri.slice(-8); + } else if (captionData.speaker.identifier.kind === 'phoneNumber') { + mri = captionData.speaker.identifier.phoneNumber; + } + + let displayName = captionData.speaker.displayName || mri; + + // Get the captions area container + let captionAreasContainer = document.getElementById('captionsArea'); + + // Generate a class name based on the MRI + const newClassName = `prefix${mri.replace(/[:\-+]/g, '')}`; + + // Generate the caption text + const captionText = `${captionData.timestamp.toUTCString()} ${displayName}: ${captionData.captionText ?? captionData.spokenText}`; + + // Try to find an existing caption container + let captionContainer = captionAreasContainer.querySelector(`.${newClassName}[isNotFinal='true']`); + + // If no existing caption container was found, create a new one + if (!captionContainer) { + captionContainer = document.createElement('div'); + captionContainer.setAttribute('isNotFinal', 'true'); + captionContainer.classList.add(newClassName, 'caption-item'); + captionAreasContainer.appendChild(captionContainer); + } + + // Set the caption text + captionContainer.textContent = captionText; + + // If the caption is final, update the 'isNotFinal' attribute + if (captionData.resultType === 'Final') { + captionContainer.setAttribute('isNotFinal', 'false'); + transcript.push(captionText); + + + // Call the sentiment service + fetch('/api/sentiment', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ content: transcript.join('\n') }) + }) + .then(response => response.json()) + .then(data => { + // Handle the sentiment response + const sentimentText = `Sentiment:${data.sentiment} (${data.positiveContentScore})`; + document.getElementById('sentimentScore').textContent = captionText; + updateSentimentMeter(data.positiveContentScore); + }); + } + + function updateSentimentMeter(score) { + // Ensure score is between 0 and 1 + score = Math.max(0, Math.min(score, 1)); + + // Convert score to angle: -90° for 0, 90° for 1 + var angle = score * 180 - 90; + + // Rotate the arrow to the corresponding angle + document.getElementById('meterArrow').style.transform = 'rotate(' + angle + 'deg)'; + } +}; +``` + +... and the HTML should look like this + +`HTML` +``` + + + + + + Azure Communication Service - Realtime Sentiment Analysis + + + +

Azure Communication Services

+ +
+ +   + +
+

Sentiment Score

+
+
+
+
+ +
+ + + +``` + +#### Testing the Complete System + +With the frontend and backend now connected, thoroughly test your application: + +* Ensure the voice call setup and teardown works flawlessly. +* Verify that captions are accurately captured and displayed. +* Confirm that sentiment analysis requests are successfully handled and that the results make sense for the given captions. + +:::info +Explore a variety of Azure Container Apps [code samples](https://learn.microsoft.com/samples/browse/?terms=azure%20container%20apps&ocid=buildia24_60days_blogs) for a quick start to your intelligent app development with cloud-native technologies. +::: + +### Deployment + +With the real-time voice sentiment analysis system fully developed, including both the frontend and backend components, the next critical step is deployment. We'll deploy the ASP.NET Core backend using Azure Container Apps and the Node.js frontend through Azure Static Web Apps. + +#### Step 1: Prepare the Backend for Deployment + +##### Create a Dockerfile + +1. **Navigate to Your Backend Project Directory**: Open the terminal or command prompt and ensure you're in the root directory of your ASP.NET Core project. + +1. **Create a Dockerfile**: In the root of your project, create a file named `Dockerfile` with no file extension. This file will contain instructions for building a Docker image for your application. + +1. **Define the Dockerfile Contents**: Open the `Dockerfile` in your editor and add the following content: + +`Dockerfile` +``` +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["VoiceSentimentBackend.csproj", "."] +RUN dotnet restore "./VoiceSentimentBackend.csproj" +COPY . . +WORKDIR "/src/." +RUN dotnet build "VoiceSentimentBackend.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "VoiceSentimentBackend.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "VoiceSentimentBackend.dll"] +``` + +* **Note**: Adjust `"VoiceSentimentBackend.csproj"` to match your project's name. + +##### Create and Test the Docker Image Locally + +1. **Build the Docker Image**: Run the following command in your terminal: + + `bash` + ``` + docker build -t voicesentimentbackend . + ``` + +1. **Run Your Docker Container**: To test the Docker container locally, execute: + + `bash` + ``` + docker run -d -p 8080:80 --name myapp voicesentimentbackend + ``` + +1. **Verify**: Open your browser and navigate to `http://localhost:8080/api/token` to ensure your application is running correctly in the container. + +#### Step 2: Deploy the Backend to Azure Container Apps + +Azure Container Apps offers a fully managed serverless container service. It's an excellent choice for deploying containers without managing complex infrastructure. + +1. **Create a Container App Environment**: Use VS Code to create an Azure Container App and deploy your backend. Follow the [official documentation](https://docs.microsoft.com/azure/container-apps/quickstart-dotnet?ocid=buildia24_60days_blogs) for detailed instructions. Type Crtl+Shift+P to open the command palette and type "Azure Container Apps: Create New Container App" and follow the prompts. + +1. **Verify Deployment**: Use the FQDN obtained from the previous command to verify your application is accessible via the internet. + +#### Step 3: Deploy the Frontend to Azure Static Web Apps + +Azure Static Web Apps is a service tailored for static web applications, providing global distribution, serverless APIs, and seamless integration with GitHub for continuous deployment. + +##### Setting Up Continuous Deployment with GitHub + +1. **Push Your Frontend Code to GitHub**: Ensure your Node.js frontend code is in a GitHub repository. + +1. **Create a Static Web App Resource**: In the Azure Portal, create a new Static Web App resource and connect it to your GitHub repository. Specify your build details during the setup. + +1. **Verify Deployment**: Once the GitHub Actions workflow is completed, your Static Web App will be accessible via the provided URL. Check it to ensure your frontend is live and functional. + +#### Conclusion + +Over the course of this guide, you've: + +* Set up and configured critical Azure resources, including Azure Communication Services for managing voice calls, Azure AI Language for basic sentiment analysis, and Azure OpenAI for more nuanced sentiment insights. +* Developed a Node.js frontend to interface with users, initiating and managing voice calls, and dynamically displaying sentiment analysis results. +* Implemented an ASP.NET Core backend to handle business logic, interact with Azure services, and provide APIs for the frontend. +* Connected your frontend and backend, ensuring seamless communication and data flow within your application. +* Deployed your application to Azure, leveraging Azure Container Apps for the backend and Azure Static Web Apps for the frontend, making your project accessible from anywhere. + +#### Future Enhancements + +While your current application is fully functional, there's always room for improvement and expansion. Consider the following possibilities for future development: + +* Language Support: Expand the application to support additional languages, enhancing its accessibility and utility across different geographical locations. +* Advanced AI Insights: Explore deeper insights beyond sentiment, such as emotional tone, intent recognition, or specific topic detection, to provide more detailed analysis of voice calls. \ No newline at end of file From 40e1cee217d1a16e89c9534487cd6bf8fe7b5e09 Mon Sep 17 00:00:00 2001 From: josephatcatalysis <106772721+josephatcatalysis@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:33:44 -0700 Subject: [PATCH 3/8] Updated Blogs 7.4 and 7.5 --- .../2024-04-17/real-time-voice-sentiment-analysis-system-2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md index 2e197eee06..96f22ac919 100644 --- a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md @@ -1,5 +1,5 @@ --- -date: 2024-04-17T09:00 +date: 2024-04-17T09:05 slug: real-time-voice-sentiment-analysis-system-2 title: "7.5 Real-time Voice Sentiment Analysis System 2" authors: [cnteam] From b5c94b9b12c6fcbe0f8bba0f9a6674ecef38c9cc Mon Sep 17 00:00:00 2001 From: josephatcatalysis <106772721+josephatcatalysis@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:59:48 -0700 Subject: [PATCH 4/8] 7.4 Updates --- .../real-time-voice-sentiment-analysis-system-1.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md index 7443c36407..03c1646d1f 100644 --- a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md @@ -133,7 +133,7 @@ With the prerequisites set, the next step is to create necessary Azure resources To enable PSTN calling capabilities (optional), you'll need a phone number. Azure Communication Services offers a free trial phone number for testing purposes. Here's how to obtain one: -1. **Navigate to Your ACS Resource**: Sign in to the [Azure Portal]() and go to your newly created Azure Communication Services resource. +1. **Navigate to Your ACS Resource**: Sign in to the [Azure Portal](https://portal.azure.com) and go to your newly created Azure Communication Services resource. 1. **Phone Numbers Section**: On the left-hand menu of your ACS resource overview page, find and select the **Phone Numbers** option. This section allows you to manage phone numbers associated with your ACS resource. @@ -222,7 +222,7 @@ Now, let's create the basic structure of our frontend application. This includes 1. **HTML**: Create an index.html file in your project root. This file will serve as the entry point for your application. - `bash` + `html` ``` @@ -275,7 +275,8 @@ Now, let's create the basic structure of our frontend application. This includes ``` 1. **JavaScript**: Create an `app.js` file. This is where we'll write the logic for initializing voice calling and handling sentiment analysis. -* Let's start by setting up a basic structure for making and ending calls using the Azure Communication Calling SDK you previously installed. + +Let's start by setting up a basic structure for making and ending calls using the Azure Communication Calling SDK you previously installed. `javascript` ``` @@ -326,7 +327,7 @@ Now, let's create the basic structure of our frontend application. This includes initCallAgent(); ``` -* **Note**: Make sure to replace placeholders like `tokenCredential` with actual values obtained from your backend or Azure Communication Services resource. +**Note**: Make sure to replace placeholders like `tokenCredential` with actual values obtained from your backend or Azure Communication Services resource. #### Testing and Running Your Application From 8be6910b2192e37d02af3507abc0e597a56073a3 Mon Sep 17 00:00:00 2001 From: josephatcatalysis <106772721+josephatcatalysis@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:20:18 -0700 Subject: [PATCH 5/8] 7.5 Updates --- ...-time-voice-sentiment-analysis-system-2.md | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md index 96f22ac919..5c8f308f0e 100644 --- a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md @@ -52,7 +52,7 @@ The backend of our real-time voice sentiment analysis application, built with AS ``` dotnet new webapi --no-https ``` -* **Note**: We are turning off HTTPS for simplicity in this guide. For production environments, HTTPS should be enabled and properly configured. +**Note**: We are turning off HTTPS for simplicity in this guide. For production environments, HTTPS should be enabled and properly configured. #### Step 2: Add Azure SDK Packages @@ -132,37 +132,38 @@ Our backend needs to interact with Azure Communication Services, Azure AI Langua ``` 1. **Implement the Token Endpoint**: - * In your ASP.NET Core project Program.cs, create a new endpoint to generate and return an access token. We'll call this endpoint from our client later. - `csharp` - ``` - app.MapGet("api/token", () => - { - var identityAndTokenResponse = communicationClient.CreateUserAndToken(scopes: [CommunicationTokenScope.VoIP]); +In your ASP.NET Core project Program.cs, create a new endpoint to generate and return an access token. We'll call this endpoint from our client later. + +`csharp` +``` +app.MapGet("api/token", () => +{ + var identityAndTokenResponse = communicationClient.CreateUserAndToken(scopes: [CommunicationTokenScope.VoIP]); - var result = new TokenResponse() - { - Token = identityAndTokenResponse.Value.AccessToken.Token, - PhoneNumber = "+12533192954", - }; + var result = new TokenResponse() + { + Token = identityAndTokenResponse.Value.AccessToken.Token, + PhoneNumber = "+12533192954", + }; - return Results.Json(result); - }) - .WithName("Token") - .WithOpenApi(); - ``` + return Results.Json(result); +}) +.WithName("Token") +.WithOpenApi(); +``` - * We'll also need a TokenResponse object to return the token and phone number: +We'll also need a TokenResponse object to return the token and phone number: - `c sharp` - ``` - public class TokenResponse - { - public string Token { get; set; } - public string PhoneNumber { get; set; } - } - ``` +`c sharp` +``` +public class TokenResponse +{ + public string Token { get; set; } + public string PhoneNumber { get; set; } +} +``` #### Step 4: Implement Sentiment Analysis Endpoints @@ -412,7 +413,8 @@ async function initCallAgent() { Our application should analyze the sentiment of the transcribed call captions. We’ll capture these captions on the frontend, then send them to our backend for sentiment analysis. 1. **Make Sentiment Analysis Request:** -* In the `captionsReceivedHandler`, make a request to your backend sentiment analysis endpoint whenever captions are received. Update your captions event handler, under `if (captionData.resultType === 'Final')` to include this: + +In the `captionsReceivedHandler`, make a request to your backend sentiment analysis endpoint whenever captions are received. Update your captions event handler, under `if (captionData.resultType === 'Final')` to include this: `javascript` ``` @@ -445,7 +447,9 @@ if (captionData.resultType === 'Final') { Finally, utilize the sentiment analysis data received from the backend to inform the user about the call’s sentiment in real-time. -1. **Update the UI Dynamically**: Enhance your frontend to dynamically display sentiment analysis results as they are received. We'll add an indicator for positive, neutral, or negative sentiment. +1. **Update the UI Dynamically**: + +Enhance your frontend to dynamically display sentiment analysis results as they are received. We'll add an indicator for positive, neutral, or negative sentiment. `javascript` ``` @@ -724,7 +728,7 @@ COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "VoiceSentimentBackend.dll"] ``` -* **Note**: Adjust `"VoiceSentimentBackend.csproj"` to match your project's name. +**Note**: Adjust `"VoiceSentimentBackend.csproj"` to match your project's name. ##### Create and Test the Docker Image Locally @@ -778,5 +782,5 @@ Over the course of this guide, you've: While your current application is fully functional, there's always room for improvement and expansion. Consider the following possibilities for future development: -* Language Support: Expand the application to support additional languages, enhancing its accessibility and utility across different geographical locations. -* Advanced AI Insights: Explore deeper insights beyond sentiment, such as emotional tone, intent recognition, or specific topic detection, to provide more detailed analysis of voice calls. \ No newline at end of file +* **Language Support**: Expand the application to support additional languages, enhancing its accessibility and utility across different geographical locations. +* **Advanced AI Insights**: Explore deeper insights beyond sentiment, such as emotional tone, intent recognition, or specific topic detection, to provide more detailed analysis of voice calls. \ No newline at end of file From b91ad56e4488219c5898df45856c661ac92db86d Mon Sep 17 00:00:00 2001 From: josephatcatalysis <106772721+josephatcatalysis@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:22:36 -0700 Subject: [PATCH 6/8] 7.5 copy updates --- .../2024-04-17/real-time-voice-sentiment-analysis-system-2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md index 5c8f308f0e..c3dec939e4 100644 --- a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md @@ -156,7 +156,7 @@ app.MapGet("api/token", () => We'll also need a TokenResponse object to return the token and phone number: -`c sharp` +`csharp` ``` public class TokenResponse { From 17a26411d7c16bfdeba2ab9bac0ae240c3834951 Mon Sep 17 00:00:00 2001 From: josephatcatalysis <106772721+josephatcatalysis@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:54:34 -0700 Subject: [PATCH 7/8] Updated Blog 8 --- .../2024-04-15/managing-the-cost-of-intelligent-apps.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/blog-60daysofIA/2024-04-15/managing-the-cost-of-intelligent-apps.md b/website/blog-60daysofIA/2024-04-15/managing-the-cost-of-intelligent-apps.md index d4fb10a4b6..3b2628b288 100644 --- a/website/blog-60daysofIA/2024-04-15/managing-the-cost-of-intelligent-apps.md +++ b/website/blog-60daysofIA/2024-04-15/managing-the-cost-of-intelligent-apps.md @@ -1,5 +1,5 @@ --- -date: 2024-04-15T09:00 +date: 2024-04-17T09:10 slug: managing-the-cost-of-intelligent-apps title: "Managing the Cost of Intelligent Apps" authors: [cnteam] @@ -85,7 +85,7 @@ Like Azure Functions, Azure OpenAI Service offers [two plans](https://azure.micr * **Azure savings plan for compute** — A fixed hourly amount, this model accommodates fluctuations and dynamic workloads, one- or three-year commitment. :::info -Checkout the quick demo bytes for [Intelligent Apps with Azure Container Apps](https://developer.microsoft.com/en-us/reactor/series/S-1308/?wt.mc_id=blog_S-1308_webpage_reactor&ocid=buildia24_60days_blogs) for a detailed walkthrough from the product team on using open-source vector database, Qrdrant, and building a multi-LLM chat application. +Checkout the quick demo bytes for **[Intelligent Apps with Azure Container Apps](https://developer.microsoft.com/en-us/reactor/series/S-1308/?wt.mc_id=blog_S-1308_webpage_reactor&ocid=buildia24_60days_blogs)** for a detailed walkthrough from the product team on using open-source vector database, Qrdrant, and building a multi-LLM chat application. ::: #### **Azure Machine Learning** From fc2fff7f954a8661f5e329b78adc07b59f6f9d0f Mon Sep 17 00:00:00 2001 From: josephatcatalysis <106772721+josephatcatalysis@users.noreply.github.com> Date: Thu, 18 Apr 2024 09:30:58 -0700 Subject: [PATCH 8/8] Publishing Blogs 7.4, 7.5, and 8 --- .../2024-04-15/managing-the-cost-of-intelligent-apps.md | 4 ++-- .../2024-04-17/real-time-voice-sentiment-analysis-system-1.md | 2 +- .../2024-04-17/real-time-voice-sentiment-analysis-system-2.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/blog-60daysofIA/2024-04-15/managing-the-cost-of-intelligent-apps.md b/website/blog-60daysofIA/2024-04-15/managing-the-cost-of-intelligent-apps.md index 3b2628b288..ebc4419572 100644 --- a/website/blog-60daysofIA/2024-04-15/managing-the-cost-of-intelligent-apps.md +++ b/website/blog-60daysofIA/2024-04-15/managing-the-cost-of-intelligent-apps.md @@ -1,9 +1,9 @@ --- date: 2024-04-17T09:10 slug: managing-the-cost-of-intelligent-apps -title: "Managing the Cost of Intelligent Apps" +title: "8 Managing the Cost of Intelligent Apps" authors: [cnteam] -draft: true +draft: false hide_table_of_contents: false toc_min_heading_level: 2 toc_max_heading_level: 3 diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md index 03c1646d1f..44a4696033 100644 --- a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-1.md @@ -3,7 +3,7 @@ date: 2024-04-17T09:00 slug: real-time-voice-sentiment-analysis-system-1 title: "7.4 Real-time Voice Sentiment Analysis System 1" authors: [cnteam] -draft: true +draft: false hide_table_of_contents: false toc_min_heading_level: 2 toc_max_heading_level: 3 diff --git a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md index c3dec939e4..f6eadb465c 100644 --- a/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md +++ b/website/blog-60daysofIA/2024-04-17/real-time-voice-sentiment-analysis-system-2.md @@ -3,7 +3,7 @@ date: 2024-04-17T09:05 slug: real-time-voice-sentiment-analysis-system-2 title: "7.5 Real-time Voice Sentiment Analysis System 2" authors: [cnteam] -draft: true +draft: false hide_table_of_contents: false toc_min_heading_level: 2 toc_max_heading_level: 3 @@ -752,7 +752,7 @@ ENTRYPOINT ["dotnet", "VoiceSentimentBackend.dll"] Azure Container Apps offers a fully managed serverless container service. It's an excellent choice for deploying containers without managing complex infrastructure. -1. **Create a Container App Environment**: Use VS Code to create an Azure Container App and deploy your backend. Follow the [official documentation](https://docs.microsoft.com/azure/container-apps/quickstart-dotnet?ocid=buildia24_60days_blogs) for detailed instructions. Type Crtl+Shift+P to open the command palette and type "Azure Container Apps: Create New Container App" and follow the prompts. +1. **Create a Container App Environment**: Use VS Code to create an Azure Container App and deploy your backend. Follow the [official documentation](https://learn.microsoft.com/azure/container-apps/deploy-visual-studio-code?ocid=buildia24_60days_blogs) for detailed instructions. Type Crtl+Shift+P to open the command palette and type "Azure Container Apps: Create New Container App" and follow the prompts. 1. **Verify Deployment**: Use the FQDN obtained from the previous command to verify your application is accessible via the internet.