Enneo

Business Logic Samples

Advance-Agent

Efficient management and adjustment of advance payments

Tip

The Advance-Agent specializes in the automated processing of customer requests for adjustment of advance payments. It analyzes, validates, and processes modification requests for monthly payments and ensures a seamless implementation in the billing system. Intelligent verification mechanisms enable changes in advance payments within sensible parameters while at the same time ensuring payment security.

Core functionalities

The Advance-Agent handles the entire process of adjusting advance payments and offers the following key services:

  • Needs analysis: Evaluation of the desired change in the context of actual consumption
  • Plausibility check: Automatic validation of whether the requested change is within sensible limits
  • Implementation: Direct adjustment of the advance payment amount in all relevant systems
  • Communication: Automated confirmation of the change with indication of the effective date

Process flow

The agent follows a structured procedure that ensures both customer satisfaction and financial stability:

  1. Recognition of the issue and extraction of relevant information (desired advance payment amount, time)
  2. Comparison with consumption data to verify the appropriateness of the requested change
  3. Evaluation of the change request based on predefined rules and limits
  4. Execution of the adjustment in case of positive evaluation or suggestion of alternative amounts
  5. Transparent communication with the customer about the result and next steps

By combining data analysis and clear decision rules, the Advance-Agent facilitates rapid, consistent, and customer-centric processing of change requests while simultaneously relieving the customer service team.

<?php

use EnneoSDK\Api;
use EnneoSDK\IntentInfo;
use EnneoSDK\IntentOption;
use EnneoSDK\Interaction;

require(getenv()['SDK'] ?? 'sdk.php');
/** @var stdClass $in */

######### Expected Input:
// contractId -> ID of the contract (required)
// requestedDeposit -> Amount of the deposit (required)
// channel -> ticket channel. Voice and chat can be handled synchronously (required)

######### Possible Actions:
// For better readability, actions can be prefixed with "base_", if they are handled in main agent business logic
// Increase requested deposit to minimum allowed
const BASE_INCREASE_DEPOSIT = 'base_increase_deposit';
// Decrease requested deposit to maximum allowed
const BASE_DECREASE_DEPOSIT = 'base_decrease_deposit';
// Deposit is not valid. Inform customer.
const DEPOSIT_INVALID = 'deposit_invalid';
// Save valid deposit
const SAVE_DEPOSIT = 'save_deposit';


$interaction = new Interaction($in);
$deposit = getCurrentDeposit($in->contractId);


######### ACTION: Deposit already validated, "save deposit" action selected
if ($in->_action === SAVE_DEPOSIT) {
    $params = [
        'contractId' => $in->contractId,
        'depositValue' => $in->requestedDeposit,
    ];
    try {
        saveDeposit($in->contractId, $in->requestedDeposit);
        stopProcessing($interaction);
    } catch (Throwable $t) {
        $interaction->infos[] = new IntentInfo(
            type: 'danger',
            message: sprintf('API-Aufruf fehlgeschlagen: %s', $t->getMessage())
        );
        // add the "try again" option using an empty action type.
        $interaction->options[] = new IntentOption(
            type: '',
            name: 'Erneut versuchen',
            recommended: true
        );
    }
}


######### STEP: Fetch current deposit value
$currentDepositValue = getCurrentDeposit($in->contractId);
$minAllowed = round($currentDepositValue * 0.95, 2);
$maxAllowed = round($currentDepositValue * 2.5, 2);
$interaction->infos[] = new IntentInfo(
    type: 'neutral',
    message: sprintf('Aktueller Abschlag: %s. Min.: %s  Max.: %s', $currentDepositValue, $minAllowed, $maxAllowed)
);

######### ACTION: Set Deposit to the minimum
if ($in->_action === BASE_INCREASE_DEPOSIT) {
    $interaction->infos[] = new IntentInfo(
        type: 'neutral',
        message: sprintf('Abschlag auf %s hochgesetzt', $currentDepositValue)
    );
    $in->requestedDeposit = $minAllowed;
}

######### ACTION: Set Deposit to the maximum
if ($in->_action === BASE_DECREASE_DEPOSIT) {
    $interaction->infos[] = new IntentInfo(
        type: 'neutral',
        message: sprintf('Abschlag auf %s reduziert', $currentDepositValue)
    );
    $in->requestedDeposit = $maxAllowed;
}

######### STEP: Validate deposit
// this is a sample validation with a tolerance while decreasing and increasing the allowed deposit values
// you can adjust this to your needs
$isDepositValid = true;
if ($in->requestedDeposit < $minAllowed) {
    $interaction->infos[] = new IntentInfo(
        type: 'warning',
        message: sprintf('Gewünschter Abschlag zu niedrig. Mindestabschlag: %s', $minAllowed)
    );
    $interaction->options[] = new IntentOption(
        type: BASE_INCREASE_DEPOSIT,
        name: 'Auf Mindestabschlag erhöhen',
        recommended: isSyncChannel($in)
    );
    $isDepositValid = false;
}
if ($in->requestedDeposit > $maxAllowed) {
    $interaction->infos[] = new IntentInfo(
        type: 'warning',
        message: sprintf('Abschlag zu hoch. Max: %s €', $maxAllowed)
    );
    $interaction->options[] = new IntentOption(
        type: BASE_DECREASE_DEPOSIT,
        name: 'Auf Maximalabschlag reduzieren',
        recommended: isSyncChannel($in)
    );
    $isDepositValid = false;
}
// If asking the customer for an adjusted deposit amount is not possible (email / letter etc),
// recommended action would be to stop processing and let the customer know that the deposit can't be adjusted
if (!$isDepositValid && !isSyncChannel($in)) {
    $interaction->options[] = new IntentOption(
        type: DEPOSIT_INVALID,
        name: 'Kunden informieren',
        recommended: true
    );
}
if (!$isDepositValid) {
    stopProcessing($interaction);
}

######### STEP: Add Action to save deposit in the system
$interaction->infos[] = new IntentInfo(
    type: 'success',
    message: 'Angefragter Abschlag nicht valid'
);
$interaction->options[] = new IntentOption(
    type: SAVE_DEPOSIT,
    name: 'Abschlag speichern',
    recommended: true
);
stopProcessing($interaction);


######### HELPER FUNCTIONS #########

/**
 * Function simulating a request to an external API and returning the current deposit value
 */
function getCurrentDeposit(int $contractId): float
{
    $response = Api::call(
        method: 'POST',
        url: 'https://echo.enneo.ai',
        params: ['value' => 123.45]
    );

    return $response->vlaue ?? 123.45;
}

function saveDeposit(int $contractId, float $requestedDeposit): void
{
    $response = Api::call(
        method: 'POST',
        url: 'https://echo.enneo.ai',
        params: [
            'contractId' => $contractId,
            'depositValue' => $requestedDeposit,
        ]
    );
}

function isSyncChannel(stdClass $in): bool
{
    return in_array($in->channel, ['chat', 'phone']);
}

/**
 * Business logic execution should always return an Interaction object in JSON format
 */
function stopProcessing(Interaction $interaction): void
{
    echo json_encode($interaction);
    exit();
}
import importlib.util
import os
import json
import requests

file_path = os.getenv('SDK', 'sdk.py')
spec = importlib.util.spec_from_file_location('sdk', file_path)
sdk = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sdk)

######### Expected Input:
# contractId -> ID of the contract (required)
# requestedDeposit -> Amount of the deposit (required)
# channel -> ticket channel. Voice and chat can be handled synchronously (required)

######### Possible Actions:
# For better readability, actions can be prefixed with "base_", if they are handled in main agent business logic
# Increase requested deposit to minimum allowed
BASE_INCREASE_DEPOSIT = 'base_increase_deposit'
# Decrease requested deposit to maximum allowed
BASE_DECREASE_DEPOSIT = 'base_decrease_deposit'
# Deposit is not valid. Inform customer.
DEPOSIT_INVALID = 'deposit_invalid'
# Save valid deposit
SAVE_DEPOSIT = 'save_deposit'

######### HELPER FUNCTIONS #########

def get_current_deposit(contract_id):
    """
    Function simulating a request to an external API and returning the current deposit value
    """
    try:
        # Make the API call directly using requests to handle JSON decode errors properly
        headers = {'Content-Type': 'application/json', 'User-Agent': 'enneo/1.0.0'}
        response = requests.post(
            'https://echo.enneo.ai',
            headers=headers,
            json={'value': 123.45},
            timeout=60
        )

        # Handle the response similar to PHP version
        if response.status_code < 200 or response.status_code >= 400:
            raise Exception(f"Api call to https://echo.enneo.ai failed with code {response.status_code} and response: {response.text}")

        # Check if the response is valid JSON
        if response.text and (response.text.strip().startswith('{') or response.text.strip().startswith('[')):
            try:
                result = response.json()
                return result.get('vlaue', 123.45)
            except json.JSONDecodeError:
                # If JSON decode fails, return default value
                return 123.45
        else:
            # Special case when a REST API does not respond with a json-encoded response
            return 123.45

    except Exception as e:
        # Handle any other exceptions by returning default value
        return 123.45

def save_deposit(contract_id, requested_deposit):
    """
    Function to save the deposit via API call
    """
    try:
        # Make the API call directly using requests to handle JSON decode errors properly
        headers = {'Content-Type': 'application/json', 'User-Agent': 'enneo/1.0.0'}
        response = requests.post(
            'https://echo.enneo.ai',
            headers=headers,
            json={
                'contractId': contract_id,
                'depositValue': requested_deposit,
            },
            timeout=60
        )

        # Handle the response similar to PHP version
        if response.status_code < 200 or response.status_code >= 400:
            raise Exception(f"Api call to https://echo.enneo.ai failed with code {response.status_code} and response: {response.text}")

        # Check if the response is valid JSON
        if response.text and (response.text.strip().startswith('{') or response.text.strip().startswith('[')):
            try:
                return response.json()
            except json.JSONDecodeError:
                # If JSON decode fails, return raw response
                return {"response": response.text, "code": response.status_code}
        else:
            # Special case when a REST API does not respond with a json-encoded response
            return {"response": response.text, "code": response.status_code}

    except Exception as e:
        # Handle any other exceptions
        return {"response": str(e), "code": 500, "error": "API call failed"}

def is_sync_channel(input_data):
    """
    Check if the channel is synchronous (chat or phone)
    """
    return input_data['channel'] in ['chat', 'phone']

def stop_processing(interaction):
    """
    Business logic execution should always return an Interaction object in JSON format
    """
    print(json.dumps(interaction.model_dump()))
    exit()

input_data = sdk.load_input_data()
interaction = sdk.Interaction(data=input_data)
deposit = get_current_deposit(input_data['contractId'])

######### ACTION: Deposit already validated, "save deposit" action selected
if input_data.get('_action') == SAVE_DEPOSIT:
    params = {
        'contractId': input_data['contractId'],
        'depositValue': input_data['requestedDeposit'],
    }
    try:
        save_deposit(input_data['contractId'], input_data['requestedDeposit'])
        stop_processing(interaction)
    except Exception as e:
        interaction.infos.append(
            sdk.IntentInfo(
                type='danger',
                message=f'API-Aufruf fehlgeschlagen: {str(e)}'
            )
        )
        # add the "try again" option using an empty action type.
        interaction.options.append(
            sdk.IntentOption(
                type='',
                name='Erneut versuchen',
                recommended=True
            )
        )

######### STEP: Fetch current deposit value
current_deposit_value = get_current_deposit(input_data['contractId'])
min_allowed = round(current_deposit_value * 0.95, 2)
max_allowed = round(current_deposit_value * 2.5, 2)
interaction.infos.append(
    sdk.IntentInfo(
        type='neutral',
        message=f'Aktueller Abschlag: {current_deposit_value}. Min.: {min_allowed}  Max.: {max_allowed}'
    )
)

######### ACTION: Set Deposit to the minimum
if input_data.get('_action') == BASE_INCREASE_DEPOSIT:
    interaction.infos.append(
        sdk.IntentInfo(
            type='neutral',
            message=f'Abschlag auf {current_deposit_value} hochgesetzt'
        )
    )
    input_data['requestedDeposit'] = min_allowed

######### ACTION: Set Deposit to the maximum
if input_data.get('_action') == BASE_DECREASE_DEPOSIT:
    interaction.infos.append(
        sdk.IntentInfo(
            type='neutral',
            message=f'Abschlag auf {current_deposit_value} reduziert'
        )
    )
    input_data['requestedDeposit'] = max_allowed

######### STEP: Validate deposit
# this is a sample validation with a tolerance while decreasing and increasing the allowed deposit values
# you can adjust this to your needs
is_deposit_valid = True
if input_data['requestedDeposit'] < min_allowed:
    interaction.infos.append(
        sdk.IntentInfo(
            type='warning',
            message=f'Gewünschter Abschlag zu niedrig. Mindestabschlag: {min_allowed}'
        )
    )
    interaction.options.append(
        sdk.IntentOption(
            type=BASE_INCREASE_DEPOSIT,
            name='Auf Mindestabschlag erhöhen',
            recommended=is_sync_channel(input_data)
        )
    )
    is_deposit_valid = False
if input_data['requestedDeposit'] > max_allowed:
    interaction.infos.append(
        sdk.IntentInfo(
            type='warning',
            message=f'Abschlag zu hoch. Max: {max_allowed} €'
        )
    )
    interaction.options.append(
        sdk.IntentOption(
            type=BASE_DECREASE_DEPOSIT,
            name='Auf Maximalabschlag reduzieren',
            recommended=is_sync_channel(input_data)
        )
    )
    is_deposit_valid = False
# If asking the customer for an adjusted deposit amount is not possible (email / letter etc),
# recommended action would be to stop processing and let the customer know that the deposit can't be adjusted
if not is_deposit_valid and not is_sync_channel(input_data):
    interaction.options.append(
        sdk.IntentOption(
            type=DEPOSIT_INVALID,
            name='Kunden informieren',
            recommended=True
        )
    )
if not is_deposit_valid:
    stop_processing(interaction)

######### STEP: Add Action to save deposit in the system
interaction.infos.append(
    sdk.IntentInfo(
        type='success',
        message='Angefragter Abschlag nicht valid'
    )
)
interaction.options.append(
    sdk.IntentOption(
        type=SAVE_DEPOSIT,
        name='Abschlag speichern',
        recommended=True
    )
)
stop_processing(interaction)