Merge remote-tracking branch 'origin/main'

This commit is contained in:
NoeBerdoz
2025-04-12 21:49:32 +02:00
10 changed files with 87 additions and 61 deletions

15
app.py
View File

@ -5,7 +5,9 @@ from flask_cors import cross_origin
import config import config
from dto.requests import GameStartRequestDTO, GameDecisionRequestDTO from dto.requests import GameStartRequestDTO, GameDecisionRequestDTO
from dto.responses import GameStartResponseWithBotDecisionDTO
from services.julius_baer_api_client import JuliusBaerApiClient from services.julius_baer_api_client import JuliusBaerApiClient
from services.player import Player
app = Flask(__name__) app = Flask(__name__)
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(module)s] - %(message)s') logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(module)s] - %(message)s')
@ -17,8 +19,19 @@ jb_client = JuliusBaerApiClient()
def new_game(): def new_game():
game_start_request = GameStartRequestDTO(player_name=config.API_TEAM) game_start_request = GameStartRequestDTO(player_name=config.API_TEAM)
res = jb_client.start_game(game_start_request) res = jb_client.start_game(game_start_request)
bot_decision = Player().make_decision(res.client_data)
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=bot_decision
)
return res_with_bot_decision.model_dump_json()
@app.route('/next', methods=['POST']) @app.route('/next', methods=['POST'])

View File

@ -1,5 +1,5 @@
from pydantic import BaseModel from pydantic import BaseModel
from typing import Dict, Optional, Any from typing import Dict, Optional, Any, Literal
from uuid import UUID from uuid import UUID
@ -12,6 +12,16 @@ class GameStartResponseDTO(BaseModel):
client_data: Dict[str, Any] client_data: Dict[str, Any]
score: int 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): class GameDecisionResponseDTO(BaseModel):
"""Response model for a game decision result.""" """Response model for a game decision result."""

View File

@ -12,6 +12,7 @@ pycparser==2.22
pymupdf==1.25.5 pymupdf==1.25.5
pypdfium2==4.30.1 pypdfium2==4.30.1
pytesseract==0.3.13 pytesseract==0.3.13
PassportEye==2.2.2
requests==2.32.3 requests==2.32.3
types-requests==2.32.0.20250328 types-requests==2.32.0.20250328
urllib3==2.4.0 urllib3==2.4.0
@ -39,5 +40,6 @@ Flask==3.1.0
annotated-types==0.7.0 annotated-types==0.7.0
blinker==1.9.0 blinker==1.9.0
langchain-google-genai==2.1.2 langchain-google-genai==2.1.2
numpy==2.2.4
pymupdf == 1.25.5 pymupdf == 1.25.5
flask-cors==5.0.1 flask-cors==5.0.1

View File

@ -1,28 +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
1 client_id decision
2 cdd60b2d-34eb-4db4-9d5e-8e903bbb4057 Accept
3 7b48afa1-6db3-4762-8faf-e73b5158f6b0 Accept
4 32eeffdc-91ab-4518-8dc6-4d805c5e4aed Accept
5 1b97cefc-48ab-437f-8cd0-ffd4eaa707df Accept
6 5471d561-b89b-4317-b3ed-499b5334e424 Accept
7 a482b28b-6f4c-4f2d-a167-42c071a92470 Accept
8 2d7d0293-291c-46ec-91fb-75351be347f8 Accept
9 3006006d-1060-4b53-afd3-b702a8fc2358 Accept
10 f4a42e3e-75a8-43dc-92fe-e38fe23b1d82 Accept
11 a5e06a84-2b05-47d1-8149-649d7a9e8bb6 Accept
12 36adf081-fef6-4696-81da-e6ba73a6c8a0 Accept
13 154b3c9d-a2e0-4d40-bb39-f3a249b26bc2 Accept
14 71fdff4e-466a-40c8-a944-4958be13f974 Accept
15 7b20c9c6-1bd6-4675-9e46-9b9829e50252 Accept
16 1591ebcd-d0c2-44c7-b130-72f9a15c8a35 Accept
17 10fd6524-a2a0-4ecf-953e-6c758f7147dc Accept
18 44418b3d-e2cd-4105-a599-5165c00c4971 Accept
19 06dcf9d6-3ec4-451c-9bf4-75cb9c8061ee Accept
20 d1d0eb32-9f99-422c-a404-606b4d5c3a10 Accept
21 3efa6b4e-8a4b-43d8-b570-0d02dd28b5ee Accept
22 42d0b5e4-03ab-4199-a74b-2f3fbe68680a Accept
23 1ad14242-15a9-4142-bc1f-c3fdd1165021 Accept
24 e11b296a-5278-49de-a6b1-01aa31a508c4 Accept
25 ad6b6980-7b2e-4af9-bda0-2ec004574211 Accept
26 ac62e33d-6645-4360-8e14-21bfb0b6902a Accept
27 d5789f9c-a0f4-4663-9c6e-0d416bbbffb8 Accept
28 25a429d6-bd8a-45d2-af1d-a0b8e5ec2e72 Accept

View File

@ -4,8 +4,10 @@ import time
from typing import Literal, Dict, Any from typing import Literal, Dict, Any
import config import config
from dto.requests import GameStartRequestDTO, GameDecisionRequestDTO 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 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__) log = logging.getLogger(__name__)
@ -34,10 +36,10 @@ class Player:
start_response = self.client.start_game(payload) start_response = self.client.start_game(payload)
log.info('game started, session id: %s', start_response.session_id) 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 = self.make_decision(start_response.client_data)
decision_counter = 0 decision_counter = 0
client_id = start_response.client_id
is_game_running = True is_game_running = True
while is_game_running: while is_game_running:
@ -53,8 +55,6 @@ class Player:
status = decision_response.status status = decision_response.status
is_game_running = status not in ['gameover', 'complete'] is_game_running = status not in ['gameover', 'complete']
if is_game_running:
store_decision(str(client_id), decision)
client_id = decision_response.client_id client_id = decision_response.client_id
decision = self.make_decision(decision_response.client_data) decision = self.make_decision(decision_response.client_data)
@ -71,11 +71,31 @@ class Player:
time.sleep(1) time.sleep(1)
def make_decision(self, client_data: Dict[str, Any]) -> Literal["Accept", "Reject"]: 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!! return 'Accept' # Replace me!!

View File

@ -113,8 +113,8 @@ def dummy_data() -> ExtractedData:
def dummy_client_data() -> dict[str, Any]: def dummy_client_data() -> dict[str, Any]:
# TODO make generic # TODO make generic
resp_path = f"{config.GAME_FILES_DIR}/65e6ec83-88b1-4d1f-8560-a1418803348b/000000_decision_accept_active/000000_response.json" resp_path = f"{config.GAME_FILES_DIR}/fc3b1f5a-296d-4cd0-a560-cfa5a6f8d302/000000_decision_accept_active/000000_response.json"
out = {} out = {}
with open(resp_path, "r") as file: with open(resp_path, "r") as file:
out = json.loads(str(json.loads(file.read()))) out = json.loads(file.read())["client_data"]
return out return out

7
tests/test_parser.py Normal file
View File

@ -0,0 +1,7 @@
from tests.dummy import dummy_client_data
from utils.parsers.passport_parser import process_passport
def test_passport_parser() -> None:
client_data = dummy_client_data()
process_passport(client_data.get("passport"))

View File

@ -1,8 +1,10 @@
import base64 import base64
import io import io
from tempfile import NamedTemporaryFile
from PIL import Image from PIL import Image
import pytesseract import pytesseract
from passporteye import read_mrz
import json
def process_passport(passport_b64: str) -> str: def process_passport(passport_b64: str) -> str:
""" """
@ -14,6 +16,20 @@ def process_passport(passport_b64: str) -> str:
:return: Texte extrait de l'image. :return: Texte extrait de l'image.
""" """
image_bytes = base64.b64decode(passport_b64) image_bytes = base64.b64decode(passport_b64)
with NamedTemporaryFile(mode="wb") as tmp_img:
tmp_img.write(image_bytes)
with open(tmp_img.name, "rb") as read_img:
mrz_obj = read_mrz(read_img)
image = Image.open(io.BytesIO(image_bytes)) image = Image.open(io.BytesIO(image_bytes))
text = pytesseract.image_to_string(image, lang='eng') tesseract_text = pytesseract.image_to_string(image, lang='eng')
return text 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

View File

@ -4,7 +4,6 @@ import logging
import config import config
import json import json
from typing import Dict, Any from typing import Dict, Any
import csv
from pathlib import Path from pathlib import Path
from dto.responses import GameStartResponseDTO, GameDecisionResponseDTO from dto.responses import GameStartResponseDTO, GameDecisionResponseDTO
@ -129,16 +128,3 @@ def store_game_round_data(decision: str, response: GameStartResponseDTO | GameDe
except Exception as e: except Exception as e:
logging.error(f"[!] Failed to save API response JSON: {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])

View File

@ -39,5 +39,5 @@ class FromAccount(BaseModel):
if self.ebanking_name != self.account_name: if self.ebanking_name != self.account_name:
raise ValueError(f'Ebanking name is different from account name') raise ValueError(f'Ebanking name is different from account name')
return self return self
phone_number: PhoneNumber phone_number: str = Field(..., min_length=8)
email: EmailStr email: EmailStr #TODO change ?