From e53ab4004c07dd00bd70cf8d13d7eaa5f3771343 Mon Sep 17 00:00:00 2001 From: Emmett-H Date: Thu, 27 Feb 2025 09:40:14 +0000 Subject: [PATCH] Fix transfers 500 error and add specific transfer types --- app/schemas/players/transfers.py | 3 +- app/services/players/transfers.py | 65 ++++++++++++++++++++++++- tests/players/test_players_transfers.py | 6 ++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/app/schemas/players/transfers.py b/app/schemas/players/transfers.py index d09bd35..a1db6d9 100644 --- a/app/schemas/players/transfers.py +++ b/app/schemas/players/transfers.py @@ -1,5 +1,5 @@ from datetime import date -from typing import Optional +from typing import Optional, Literal from app.schemas.base import AuditMixin, TransfermarktBaseModel @@ -18,6 +18,7 @@ class PlayerTransfer(TransfermarktBaseModel): season: str market_value: Optional[int] fee: Optional[int] + transfer_type: Literal["permanent", "loan", "end_of_loan", "free_transfer"] class PlayerTransfers(TransfermarktBaseModel, AuditMixin): diff --git a/app/services/players/transfers.py b/app/services/players/transfers.py index 95a7360..73f7b33 100644 --- a/app/services/players/transfers.py +++ b/app/services/players/transfers.py @@ -1,4 +1,7 @@ from dataclasses import dataclass +from bs4 import BeautifulSoup +import re +from typing import Tuple from app.services.base import TransfermarktBase from app.utils.utils import extract_from_url, safe_split @@ -26,6 +29,50 @@ def __post_init__(self) -> None: self.raise_exception_if_not_found(xpath=Players.Profile.NAME) self.transfer_history = self.make_request(url=self.URL_TRANSFERS.format(player_id=self.player_id)) + def __clean_html_value(self, value: str) -> Tuple[str, str]: + """ + Clean HTML tags from a string value and extract just the currency value or preserve special text values. + Also determine the transfer type based on the fee text. + + Args: + value (str): The string value that may contain HTML tags. + + Returns: + Tuple[str, str]: A tuple containing (cleaned_fee_value, transfer_type) + """ + if not value or not isinstance(value, str): + return value, "permanent" + + soup = BeautifulSoup(value, 'html.parser') + text = soup.get_text().strip() + + if "end of loan" in text.lower(): + return "End of loan", "end_of_loan" + + if "loan transfer" in text.lower() and not any(char.isdigit() for char in text): + return "loan transfer", "loan" + + if "fee" in text.lower(): + currency_match = re.search(r'(€\d+(?:\.\d+)?[km]?)', text) + if currency_match: + if "loan" in text.lower(): + return currency_match.group(1), "loan" + return currency_match.group(1), "permanent" + + number_match = re.search(r'(\d+(?:\.\d+)?[km]?)', text) + if number_match: + if "loan" in text.lower(): + return number_match.group(1), "loan" + return number_match.group(1), "permanent" + + if "loan" in text.lower() and not any(char.isdigit() for char in text): + return "€0", "loan" + + if "free transfer" in text.lower() or text == "-": + return "€0", "free_transfer" + + return text, "permanent" + def __parse_player_transfer_history(self) -> list: """ Parse and retrieve the transfer history of the specified player from Transfermarkt, @@ -53,10 +100,26 @@ def __parse_player_transfer_history(self) -> list: "upcoming": transfer["upcoming"], "season": transfer["season"], "marketValue": transfer["marketValue"], - "fee": transfer["fee"], + **self.__process_fee_and_type(transfer["fee"]), } for transfer in transfers ] + + def __process_fee_and_type(self, fee_value: str) -> dict: + """ + Process the fee value and determine the transfer type. + + Args: + fee_value (str): The raw fee value from the transfer data. + + Returns: + dict: A dictionary containing the cleaned fee value and transfer type. + """ + fee, transfer_type = self.__clean_html_value(fee_value) + return { + "fee": fee, + "transferType": transfer_type + } def get_player_transfers(self) -> dict: """ diff --git a/tests/players/test_players_transfers.py b/tests/players/test_players_transfers.py index 866cc76..f3a0aac 100644 --- a/tests/players/test_players_transfers.py +++ b/tests/players/test_players_transfers.py @@ -2,7 +2,7 @@ import pytest from fastapi import HTTPException -from schema import And, Optional, Schema +from schema import And, Optional, Schema, Or from app.services.players.transfers import TransfermarktPlayerTransfers @@ -36,6 +36,7 @@ def test_get_player_transfers(player_id, len_greater_than_0, regex_integer, rege "upcoming": bool, Optional("marketValue"): And(str, len_greater_than_0, regex_market_value), Optional("fee"): And(str, len_greater_than_0), + "transferType": Or("permanent", "loan", "end_of_loan", "free_transfer"), }, ], "youthClubs": list, @@ -46,3 +47,6 @@ def test_get_player_transfers(player_id, len_greater_than_0, regex_integer, rege assert expected_schema.validate(result) assert any("marketValue" in stat for stat in result.get("transfers")) assert any("fee" in stat for stat in result.get("transfers")) + + transfer_types = [transfer.get("transferType") for transfer in result.get("transfers")] + assert all(transfer_types), "All transfers should have a transferType"