2025-04-12 11:58:01 +02:00
import logging
2025-04-12 12:59:36 +02:00
import threading
import time
2025-04-12 11:58:01 +02:00
from typing import Literal , Dict , Any
import config
from dto . requests import GameStartRequestDTO , GameDecisionRequestDTO
2025-04-12 21:47:57 +02:00
from services . extractor import extract_profile , extract_passport , extract_description , extract_account
2025-04-12 11:58:01 +02:00
from services . julius_baer_api_client import JuliusBaerApiClient
2025-04-12 21:47:57 +02:00
from utils . storage . game_files_manager import store_game_round_data
2025-04-13 01:13:20 +02:00
from langchain_openai . chat_models import ChatOpenAI
2025-04-12 22:18:40 +02:00
from validation . llm_validate import AdvisorDecision
2025-04-12 21:51:40 +02:00
from langchain_core . prompts import ChatPromptTemplate
from langchain_core . output_parsers import PydanticOutputParser
2025-04-12 21:47:57 +02:00
2025-04-12 11:58:01 +02:00
2025-04-12 13:59:59 +02:00
log = logging . getLogger ( __name__ )
2025-04-12 11:58:01 +02:00
2025-04-12 22:10:37 +02:00
class Advisor :
2025-04-12 11:58:01 +02:00
def __init__ ( self ) :
self . client = JuliusBaerApiClient ( )
2025-04-12 12:59:36 +02:00
self . _thread = None
2025-04-12 11:58:01 +02:00
def start ( self ) :
self . play ( )
2025-04-12 12:59:36 +02:00
def play_on_separate_thread ( self ) :
if self . _thread and self . _thread . is_alive ( ) :
2025-04-12 13:59:59 +02:00
log . warning ( ' Game loop already running. ' )
2025-04-12 12:59:36 +02:00
return self . _thread
self . _thread = threading . Thread ( target = self . play , daemon = True )
self . _thread . start ( )
return self . _thread
2025-04-12 11:58:01 +02:00
def play ( self ) :
2025-04-12 13:59:59 +02:00
log . info ( ' playing ' )
2025-04-12 11:58:01 +02:00
payload = GameStartRequestDTO ( player_name = config . API_TEAM )
start_response = self . client . start_game ( payload )
2025-04-12 13:59:59 +02:00
log . info ( ' game started, session id: %s ' , start_response . session_id )
2025-04-12 11:58:01 +02:00
2025-04-12 21:47:57 +02:00
client_id = start_response . client_id
2025-04-12 11:58:01 +02:00
decision = self . make_decision ( start_response . client_data )
2025-04-12 13:40:51 +02:00
decision_counter = 0
2025-04-12 15:41:45 +02:00
is_game_running = True
while is_game_running :
2025-04-12 11:58:01 +02:00
payload = GameDecisionRequestDTO (
2025-04-12 22:18:40 +02:00
decision = decision . answer ,
2025-04-12 11:58:01 +02:00
session_id = start_response . session_id ,
2025-04-12 15:41:45 +02:00
client_id = client_id ,
2025-04-12 11:58:01 +02:00
)
2025-04-12 15:41:45 +02:00
log . info ( ' client id: %s ' , client_id )
2025-04-12 12:51:13 +02:00
decision_response = self . client . send_decision ( payload )
2025-04-12 13:59:59 +02:00
log . info ( f ' decision: { decision } , response status: { decision_response . status } , score: { decision_response . score } ' )
2025-04-12 15:41:45 +02:00
2025-04-12 11:58:01 +02:00
status = decision_response . status
2025-04-12 15:41:45 +02:00
is_game_running = status not in [ ' gameover ' , ' complete ' ]
client_id = decision_response . client_id
2025-04-12 11:58:01 +02:00
decision = self . make_decision ( decision_response . client_data )
2025-04-12 13:40:51 +02:00
2025-04-12 14:00:11 +02:00
# Handle first response from game initialization logic
if decision_counter == 0 :
# Store start response
2025-04-12 22:18:40 +02:00
store_game_round_data ( decision . answer , start_response , decision_counter , str ( start_response . session_id ) , status )
2025-04-12 14:00:11 +02:00
else :
# store ongoing decision response
2025-04-12 22:18:40 +02:00
store_game_round_data ( decision . answer , decision_response , decision_counter , str ( start_response . session_id ) , status )
2025-04-12 13:40:51 +02:00
decision_counter + = 1
2025-04-12 15:41:45 +02:00
time . sleep ( 1 )
2025-04-12 14:00:11 +02:00
2025-04-12 22:18:40 +02:00
def make_decision ( self , client_data : Dict [ str , Any ] ) - > AdvisorDecision :
2025-04-12 21:51:40 +02:00
# 1. Extraction des données
2025-04-12 21:47:57 +02:00
profile = extract_profile ( client_data )
passport = extract_passport ( client_data )
description = extract_description ( client_data )
account = extract_account ( client_data )
2025-04-12 21:51:40 +02:00
# 2. Création du parser
2025-04-12 22:18:40 +02:00
parser = PydanticOutputParser ( pydantic_object = AdvisorDecision )
2025-04-12 21:51:40 +02:00
format_instructions = parser . get_format_instructions ( )
# 3. Prompt enrichi
prompt = ChatPromptTemplate . from_template (
2025-04-13 08:01:31 +02:00
""" You are an experienced compliance analyst at a prestigious private bank. Your role is to carefully examine client applications by cross-checking data from four provided documents: Passport, Profile, Description, and Account.
2025-04-12 21:51:40 +02:00
2025-04-13 09:33:02 +02:00
Your task :
Determine whether to ACCEPT or REJECT the client ’ s private banking application based on data consistency .
CRITICAL RULES for rejection ( any single issue means rejection ) :
- Mismatch in personal details : names , surnames must match exactly across all documents .
- Typos or spelling errors in critical information .
- Expired or incorrect validity dates on passports or other documents .
- Non - existent or incorrect addresses , including city , street , zip code , and country .
- Conflicting information regarding country of domicile .
- Suspicious or implausible personal details .
- Financial discrepancies between Profile and Description documents .
- Mismatching nationality between Passport and Account documents .
ADDITIONAL INSTRUCTIONS :
- Cross - check the Profile , Description , and Account information meticulously against the Passport .
- Historical occupation details may legitimately differ from current data — this alone does not imply inconsistency .
- Always verify currency consistency when evaluating monetary amounts .
- Be extremely cautious — reject immediately if there ' s any uncertainty or if any detail appears suspicious.
- NEVER fabricate or assume information ; rely strictly on provided data .
RESPOND STRICTLY IN THE FOLLOWING JSON FORMAT :
{ format_instructions }
- - -
* * Passport Document : * *
{ passport }
* * Profile Document : * *
{ profile }
* * Description Document : * *
The following is a single physical document , structured into multiple thematic sections . Each section starts with a heading followed by relevant content .
{ description }
* * Account Document : * *
{ account }
"""
2025-04-12 21:51:40 +02:00
)
2025-04-12 11:58:01 +02:00
2025-04-12 21:51:40 +02:00
# 4. Chaîne LLM
2025-04-13 01:13:20 +02:00
chain = prompt | ChatOpenAI ( model = " gpt-4o-mini " ) | parser
2025-04-12 21:51:40 +02:00
# 5. Invocation
2025-04-12 22:18:40 +02:00
result : AdvisorDecision = chain . invoke ( {
" passport " : passport ,
" profile " : profile ,
" description " : description ,
" account " : account ,
2025-04-12 21:51:40 +02:00
" format_instructions " : format_instructions ,
} )
# 6. Logs et retour
2025-04-12 22:36:21 +02:00
if result . answer == " Reject " :
2025-04-12 21:51:40 +02:00
log . warning ( f " Client rejected. Reason: { result . reason } " )
else :
log . info ( " Client accepted. " )
2025-04-12 22:18:40 +02:00
return result