Merge remote-tracking branch 'origin/main'
This commit is contained in:
15
app.py
15
app.py
@ -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'])
|
||||||
|
@ -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."""
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
|
@ -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!!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
7
tests/test_parser.py
Normal 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"))
|
@ -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
|
@ -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])
|
|
||||||
|
@ -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 ?
|
Reference in New Issue
Block a user