From 1ea6f2655c8db3deb2b379fb1c57053c8a9e92ef Mon Sep 17 00:00:00 2001 From: Nitwix Date: Sat, 12 Apr 2025 19:18:51 +0200 Subject: [PATCH 1/5] add libs --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 5fba858..1d44aa1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,7 @@ pycparser==2.22 pymupdf==1.25.5 pypdfium2==4.30.1 pytesseract==0.3.13 +PassportEye==2.2.2 requests==2.32.3 types-requests==2.32.0.20250328 urllib3==2.4.0 @@ -39,3 +40,4 @@ Flask==3.1.0 annotated-types==0.7.0 blinker==1.9.0 langchain-google-genai==2.1.2 +numpy==2.2.4 \ No newline at end of file From 10b69c945bc385fc6ae233a6c5b589b0e9c6a3c0 Mon Sep 17 00:00:00 2001 From: robinrolle Date: Sat, 12 Apr 2025 20:13:52 +0200 Subject: [PATCH 2/5] add slack to phone_validation --- validation/from_account.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validation/from_account.py b/validation/from_account.py index 099d222..a89546c 100644 --- a/validation/from_account.py +++ b/validation/from_account.py @@ -39,5 +39,5 @@ class FromAccount(BaseModel): if self.ebanking_name != self.account_name: raise ValueError(f'Ebanking name is different from account name') return self - phone_number: PhoneNumber - email: EmailStr \ No newline at end of file + phone_number: str = Field(..., min_length=8) + email: EmailStr #TODO change ? \ No newline at end of file From 470aba59905f571d2757740e884fa6d30d77c4db Mon Sep 17 00:00:00 2001 From: Nitwix Date: Sat, 12 Apr 2025 20:31:20 +0200 Subject: [PATCH 3/5] Fix passport parser --- utils/parsers/passport_parser.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/utils/parsers/passport_parser.py b/utils/parsers/passport_parser.py index 7975370..5aa9fea 100644 --- a/utils/parsers/passport_parser.py +++ b/utils/parsers/passport_parser.py @@ -16,12 +16,20 @@ def process_passport(passport_b64: str) -> str: :return: Texte extrait de l'image. """ image_bytes = base64.b64decode(passport_b64) - # image = Image.open(io.BytesIO(image_bytes)) - # text = pytesseract.image_to_string(image, lang='eng') with NamedTemporaryFile(mode="wb") as tmp_img: tmp_img.write(image_bytes) with open(tmp_img.name, "rb") as read_img: - text = read_mrz(read_img) - # text = json.dumps(text) - # TODO CONTINUE - return text \ No newline at end of file + mrz_obj = read_mrz(read_img) + + image = Image.open(io.BytesIO(image_bytes)) + tesseract_text = pytesseract.image_to_string(image, lang='eng') + out_dict = { + "country": mrz_obj.country, + "names": mrz_obj.names, + "number": mrz_obj.number, + "surname": mrz_obj.surname, + "mrz": mrz_obj.aux["text"], + "raw": tesseract_text + } + out = json.dumps(out_dict) + return out \ No newline at end of file From 3f9de128bee900eb5b93b578dd76ef7ab55a8a31 Mon Sep 17 00:00:00 2001 From: dylan <12473240+dlmw@users.noreply.github.com> Date: Sat, 12 Apr 2025 21:36:09 +0200 Subject: [PATCH 4/5] Add DTO that contains decision from bot as well as the previous DTO (hard-coded bot decision for now) --- app.py | 13 ++++++++++++- dto/responses.py | 12 +++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 5b4f17d..949b24b 100644 --- a/app.py +++ b/app.py @@ -5,6 +5,7 @@ from flask_cors import cross_origin import config from dto.requests import GameStartRequestDTO, GameDecisionRequestDTO +from dto.responses import GameStartResponseWithBotDecisionDTO from services.julius_baer_api_client import JuliusBaerApiClient app = Flask(__name__) @@ -18,7 +19,17 @@ def new_game(): game_start_request = GameStartRequestDTO(player_name=config.API_TEAM) res = jb_client.start_game(game_start_request) - return res.model_dump_json() + res_with_bot_decision = GameStartResponseWithBotDecisionDTO( + message=res.message, + session_id=res.session_id, + player_id=res.player_id, + client_id=res.client_id, + client_data=res.client_data, + score=res.score, + bot_decision="Accept" # TODO: Get decision from bot + ) + + return res_with_bot_decision.model_dump_json() @app.route('/next', methods=['POST']) diff --git a/dto/responses.py b/dto/responses.py index fd5509b..4ad3223 100644 --- a/dto/responses.py +++ b/dto/responses.py @@ -1,5 +1,5 @@ from pydantic import BaseModel -from typing import Dict, Optional, Any +from typing import Dict, Optional, Any, Literal from uuid import UUID @@ -12,6 +12,16 @@ class GameStartResponseDTO(BaseModel): client_data: Dict[str, Any] score: int +class GameStartResponseWithBotDecisionDTO(BaseModel): + """Response model for to send to frontend after a new game started.""" + message: str + session_id: UUID + player_id: str + client_id: UUID + client_data: Dict[str, Any] + score: int + bot_decision: Literal["Accept", "Reject"] + class GameDecisionResponseDTO(BaseModel): """Response model for a game decision result.""" From bc2ea68332fb278bbe1a2d3db976a7af0177c0ba Mon Sep 17 00:00:00 2001 From: Luca De Laurentiis <39311040+lucadela96@users.noreply.github.com> Date: Sat, 12 Apr 2025 21:47:57 +0200 Subject: [PATCH 5/5] Player 2 (#3) Decision Making (not finish) --- app.py | 4 ++- resources/decision_log2.csv | 35 ------------------------- services/player.py | 40 +++++++++++++++++++++-------- utils/storage/game_files_manager.py | 14 ---------- 4 files changed, 33 insertions(+), 60 deletions(-) delete mode 100644 resources/decision_log2.csv diff --git a/app.py b/app.py index 949b24b..32b214b 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,7 @@ import config from dto.requests import GameStartRequestDTO, GameDecisionRequestDTO from dto.responses import GameStartResponseWithBotDecisionDTO from services.julius_baer_api_client import JuliusBaerApiClient +from services.player import Player app = Flask(__name__) logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(module)s] - %(message)s') @@ -18,6 +19,7 @@ jb_client = JuliusBaerApiClient() def new_game(): game_start_request = GameStartRequestDTO(player_name=config.API_TEAM) res = jb_client.start_game(game_start_request) + bot_decision = Player().make_decision(res.client_data) res_with_bot_decision = GameStartResponseWithBotDecisionDTO( message=res.message, @@ -26,7 +28,7 @@ def new_game(): client_id=res.client_id, client_data=res.client_data, score=res.score, - bot_decision="Accept" # TODO: Get decision from bot + bot_decision=bot_decision ) return res_with_bot_decision.model_dump_json() diff --git a/resources/decision_log2.csv b/resources/decision_log2.csv deleted file mode 100644 index f817b7d..0000000 --- a/resources/decision_log2.csv +++ /dev/null @@ -1,35 +0,0 @@ -client_id,decision -cdd60b2d-34eb-4db4-9d5e-8e903bbb4057,Accept -7b48afa1-6db3-4762-8faf-e73b5158f6b0,Accept -32eeffdc-91ab-4518-8dc6-4d805c5e4aed,Accept -1b97cefc-48ab-437f-8cd0-ffd4eaa707df,Accept -5471d561-b89b-4317-b3ed-499b5334e424,Accept -a482b28b-6f4c-4f2d-a167-42c071a92470,Accept -2d7d0293-291c-46ec-91fb-75351be347f8,Accept -3006006d-1060-4b53-afd3-b702a8fc2358,Accept -f4a42e3e-75a8-43dc-92fe-e38fe23b1d82,Accept -a5e06a84-2b05-47d1-8149-649d7a9e8bb6,Accept -36adf081-fef6-4696-81da-e6ba73a6c8a0,Accept -154b3c9d-a2e0-4d40-bb39-f3a249b26bc2,Accept -71fdff4e-466a-40c8-a944-4958be13f974,Accept -7b20c9c6-1bd6-4675-9e46-9b9829e50252,Accept -1591ebcd-d0c2-44c7-b130-72f9a15c8a35,Accept -10fd6524-a2a0-4ecf-953e-6c758f7147dc,Accept -44418b3d-e2cd-4105-a599-5165c00c4971,Accept -06dcf9d6-3ec4-451c-9bf4-75cb9c8061ee,Accept -d1d0eb32-9f99-422c-a404-606b4d5c3a10,Accept -3efa6b4e-8a4b-43d8-b570-0d02dd28b5ee,Accept -42d0b5e4-03ab-4199-a74b-2f3fbe68680a,Accept -1ad14242-15a9-4142-bc1f-c3fdd1165021,Accept -e11b296a-5278-49de-a6b1-01aa31a508c4,Accept -ad6b6980-7b2e-4af9-bda0-2ec004574211,Accept -ac62e33d-6645-4360-8e14-21bfb0b6902a,Accept -d5789f9c-a0f4-4663-9c6e-0d416bbbffb8,Accept -25a429d6-bd8a-45d2-af1d-a0b8e5ec2e72,Accept -e592968f-4970-4aad-82d5-9f9383e3ae57,Accept -ef70356d-5014-498e-9897-643b6bc88dab,Accept -87db27e8-e1ff-4915-8dd2-2dc4afb570a4,Accept -161cffea-a125-4496-a0fc-17d21e2ae512,Accept -aaa05711-5e4b-455e-9971-fdba386441e9,Accept -3709c954-6c2e-42a5-90f7-8b73c374943f,Accept -f3054c1b-ad4a-4cad-be7b-03cb192f5c45,Accept diff --git a/services/player.py b/services/player.py index 05e0c32..beb283a 100644 --- a/services/player.py +++ b/services/player.py @@ -4,8 +4,10 @@ import time from typing import Literal, Dict, Any import config from dto.requests import GameStartRequestDTO, GameDecisionRequestDTO +from services.extractor import extract_profile, extract_passport, extract_description, extract_account from services.julius_baer_api_client import JuliusBaerApiClient -from utils.storage.game_files_manager import store_game_round_data, store_decision +from utils.storage.game_files_manager import store_game_round_data + log = logging.getLogger(__name__) @@ -34,10 +36,10 @@ class Player: start_response = self.client.start_game(payload) log.info('game started, session id: %s', start_response.session_id) + client_id = start_response.client_id decision = self.make_decision(start_response.client_data) decision_counter = 0 - client_id = start_response.client_id is_game_running = True while is_game_running: @@ -53,8 +55,6 @@ class Player: status = decision_response.status is_game_running = status not in ['gameover', 'complete'] - if is_game_running: - store_decision(str(client_id), decision) client_id = decision_response.client_id decision = self.make_decision(decision_response.client_data) @@ -71,11 +71,31 @@ class Player: time.sleep(1) def make_decision(self, client_data: Dict[str, Any]) -> Literal["Accept", "Reject"]: - # Do your magic! + # Data extraction + profile = extract_profile(client_data) + passport = extract_passport(client_data) + description = extract_description(client_data) + account = extract_account(client_data) + + prompt_template = ( + "You are a helpful assistant at a private bank.\n" + "Your task is to accept or reject a new client's application for private banking.\n\n" + "Only reject the application if there is an inconsistency in the data provided.\n" + "Inconsistencies include:\n" + "- Incorrect data (e.g., mismatched or invalid information)\n" + "- Incomplete data (e.g., missing required fields)\n\n" + "Return only JSON matching this format:\n" + "{format_instructions}\n\n" + "Here is the extracted passport text:\n" + "{processed_text}\n\n" + "Use the extracted profile, description, and account details to check consistency." + ) + + # You'd insert the format instructions and passport text here before calling the LLM + # For example: + # final_prompt = prompt_template.format( + # format_instructions=your_format_instructions, + # processed_text=passport['text'] + # ) return 'Accept' # Replace me!! - - - - - diff --git a/utils/storage/game_files_manager.py b/utils/storage/game_files_manager.py index d9797d6..7d38a6b 100644 --- a/utils/storage/game_files_manager.py +++ b/utils/storage/game_files_manager.py @@ -4,7 +4,6 @@ import logging import config import json from typing import Dict, Any -import csv from pathlib import Path from dto.responses import GameStartResponseDTO, GameDecisionResponseDTO @@ -129,16 +128,3 @@ def store_game_round_data(decision: str, response: GameStartResponseDTO | GameDe except Exception as e: logging.error(f"[!] Failed to save API response JSON: {e}") - - -def store_decision(client_id: str, decision: str): - path = Path('./resources/decision_log2.csv') # TODO clean me!! - - path.parent.mkdir(parents=True, exist_ok=True) # create dirs if needed - - exists = path.exists() - with open(path, 'a', newline='') as f: - writer = csv.writer(f) - if not exists: - writer.writerow(['client_id', 'decision']) # header - writer.writerow([client_id, decision])