Enneo

Business Logik Beispiele

Abschlags-Agent

Effiziente Verwaltung und Anpassung von Abschlagszahlungen

Tip

Der Abschlags-Agent ist spezialisiert auf die automatisierte Bearbeitung von Kundenwünschen zur Anpassung von Abschlagszahlungen. Er analysiert, validiert und verarbeitet Änderungsanfragen für monatliche Zahlungen und sorgt für eine nahtlose Umsetzung im Abrechnungssystem. Durch intelligente Prüfmechanismen werden Abschlagsänderungen im Rahmen sinnvoller Parameter ermöglicht, während gleichzeitig die Zahlungssicherheit gewährleistet bleibt.

Kernfunktionalitäten

Der Abschlags-Agent übernimmt den gesamten Prozess der Abschlagsanpassung und bietet dabei folgende zentrale Leistungen:

  • Bedarfsanalyse: Bewertung der gewünschten Änderung im Kontext des tatsächlichen Verbrauchs
  • Plausibilitätsprüfung: Automatische Validierung, ob die angeforderte Änderung innerhalb sinnvoller Grenzen liegt
  • Umsetzung: Direkte Anpassung der Abschlagshöhe in allen relevanten Systemen
  • Kommunikation: Automatisierte Bestätigung der Änderung mit Angabe des Gültigkeitszeitpunkts

Prozessablauf

Der Agent folgt einem strukturierten Ablauf, der sowohl Kundenzufriedenheit als auch finanzielle Stabilität sicherstellt:

  1. Erkennung des Anliegens und Extraktion relevanter Informationen (gewünschte Abschlagshöhe, Zeitpunkt)
  2. Abgleich mit Verbrauchsdaten zur Prüfung der Angemessenheit der angeforderten Änderung
  3. Bewertung des Änderungswunsches anhand vordefinierter Regeln und Grenzwerte
  4. Durchführung der Anpassung bei positiver Bewertung oder Vorschlag alternativer Beträge
  5. Transparente Kommunikation mit dem Kunden über das Ergebnis und weitere Schritte

Durch die Kombination aus Datenanalyse und klaren Entscheidungsregeln ermöglicht der Abschlags-Agent eine schnelle, konsistente und kundenorientierte Bearbeitung von Änderungswünschen bei gleichzeitiger Entlastung des Kundenservice-Teams.

<?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)