From 617fd38a6a3224ee75cc4f247a64e8d22099c133 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Wed, 29 Oct 2025 21:43:45 +0100 Subject: [PATCH 01/18] #802 added foulborn unique detector --- .../data_retrieval/poe_api_handler.py | 2 ++ .../detectors/unique_detector.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py index 02b6ff7f..d480b122 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py @@ -12,6 +12,7 @@ from data_retrieval_app.external_data_retrieval.detectors.unique_detector import ( UniqueArmourDetector, UniqueDetector, + UniqueFoulbornDetector, UniqueJewelDetector, UniqueJewelleryDetector, UniqueUnidentifiedDetector, @@ -58,6 +59,7 @@ def __init__( UniqueJewelleryDetector(), UniqueWeaponDetector(), UniqueUnidentifiedDetector(), + UniqueFoulbornDetector(), ] logger.debug("Item detectors set to: " + str(item_detectors)) self.url = url diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py index 3d7c8cdb..b838b574 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py @@ -25,6 +25,21 @@ def _specialized_filter(self, df: pd.DataFrame) -> pd.DataFrame: return df +class UniqueFoulbornDetector(UniqueDetector): + def _check_if_wanted(self, df: pd.DataFrame) -> pd.DataFrame: + """ + Uses the icon to identify which unique it is, then saving that name. + If the name attribute still has a length of 0 it means no matching unique + was found. + """ + if "mutated" not in df.columns: + return pd.DataFrame(columns=df.columns) + + df = df.loc[df["mutated"]] + + return df + + class UniqueUnidentifiedDetector(UniqueDetector): """ Notes: From 40ddea0bba740b4ba6a3e59148e03ef1d55889f2 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sun, 2 Nov 2025 11:06:29 +0100 Subject: [PATCH 02/18] #802 added empty name filter --- .../external_data_retrieval/detectors/unique_detector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py index b838b574..7cc990b2 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py @@ -37,6 +37,7 @@ def _check_if_wanted(self, df: pd.DataFrame) -> pd.DataFrame: df = df.loc[df["mutated"]] + df = df.loc[df["name"].str.len() != 0] return df From 0cfd2e32d40f0992d01d16430bfbf7838b63d352 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Tue, 18 Nov 2025 21:03:58 +0100 Subject: [PATCH 03/18] #802 Created carantene modifier models and schemas in api --- ...84cb4d_created_carantene_modifier_table.py | 57 ++++++ src/backend_api/app/api/api.py | 10 ++ src/backend_api/app/api/api_message_util.py | 4 +- src/backend_api/app/api/routes/__init__.py | 1 + .../app/api/routes/carantene_modifier.py | 165 ++++++++++++++++++ src/backend_api/app/core/models/models.py | 34 ++++ src/backend_api/app/core/schemas/__init__.py | 6 + .../app/core/schemas/carantene_modifier.py | 48 +++++ src/backend_api/app/crud/__init__.py | 19 +- 9 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 src/backend_api/app/alembic/versions/d789cc84cb4d_created_carantene_modifier_table.py create mode 100644 src/backend_api/app/api/routes/carantene_modifier.py create mode 100644 src/backend_api/app/core/schemas/carantene_modifier.py diff --git a/src/backend_api/app/alembic/versions/d789cc84cb4d_created_carantene_modifier_table.py b/src/backend_api/app/alembic/versions/d789cc84cb4d_created_carantene_modifier_table.py new file mode 100644 index 00000000..87d543a6 --- /dev/null +++ b/src/backend_api/app/alembic/versions/d789cc84cb4d_created_carantene_modifier_table.py @@ -0,0 +1,57 @@ +"""Created carantene modifier table + +Revision ID: d789cc84cb4d +Revises: e38727349f3f +Create Date: 2025-11-18 19:44:38.570115 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'd789cc84cb4d' +down_revision: Union[str, None] = 'e38727349f3f' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('carantene_modifier', + sa.Column('caranteneModifierId', sa.BigInteger(), sa.Identity(always=False, start=1, increment=1, cycle=True), nullable=False), + sa.Column('effect', sa.Text(), nullable=False), + sa.Column('relatedUnique', sa.Text(), nullable=False), + sa.Column('implicit', sa.Boolean(), nullable=True), + sa.Column('explicit', sa.Boolean(), nullable=True), + sa.Column('delve', sa.Boolean(), nullable=True), + sa.Column('fractured', sa.Boolean(), nullable=True), + sa.Column('synthesised', sa.Boolean(), nullable=True), + sa.Column('unique', sa.Boolean(), nullable=True), + sa.Column('corrupted', sa.Boolean(), nullable=True), + sa.Column('enchanted', sa.Boolean(), nullable=True), + sa.Column('veiled', sa.Boolean(), nullable=True), + sa.Column('mutated', sa.Boolean(), nullable=True), + sa.Column('createdAt', sa.DateTime(timezone=True), nullable=False), + sa.Column('updatedAt', sa.DateTime(timezone=True), nullable=True), + sa.PrimaryKeyConstraint('caranteneModifierId') + ) + op.alter_column('item', 'itemId', + existing_type=sa.BIGINT(), + server_default=sa.Identity(always=True, start=1, increment=1), + existing_nullable=False, + autoincrement=True) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('item', 'itemId', + existing_type=sa.BIGINT(), + server_default=sa.Identity(always=False, start=1, increment=1, minvalue=1, maxvalue=9223372036854775807, cycle=False, cache=1), + existing_nullable=False, + autoincrement=True) + op.drop_table('carantene_modifier') + # ### end Alembic commands ### diff --git a/src/backend_api/app/api/api.py b/src/backend_api/app/api/api.py index 77862728..9e6f3c4c 100644 --- a/src/backend_api/app/api/api.py +++ b/src/backend_api/app/api/api.py @@ -1,6 +1,8 @@ from fastapi import APIRouter from app.api.routes import ( + carantene_modifier, + carantene_modifier_prefix, currency, currency_prefix, item, @@ -26,6 +28,11 @@ api_router = APIRouter() +api_router.include_router( + carantene_modifier.router, + prefix=f"/{carantene_modifier_prefix}", + tags=[f"{carantene_modifier_prefix}s"], +) api_router.include_router( currency.router, prefix=f"/{currency_prefix}", tags=[f"{currency_prefix}s"] ) @@ -50,6 +57,9 @@ api_router.include_router( modifier.router, prefix=f"/{modifier_prefix}", tags=[f"{modifier_prefix}s"] ) +api_router.include_router( + modifier.router, prefix=f"/{modifier_prefix}", tags=[f"{modifier_prefix}s"] +) api_router.include_router( plot.router, prefix=f"/{plot_prefix}", tags=[f"{plot_prefix}s"] ) diff --git a/src/backend_api/app/api/api_message_util.py b/src/backend_api/app/api/api_message_util.py index d4fedd53..3fb45c9a 100644 --- a/src/backend_api/app/api/api_message_util.py +++ b/src/backend_api/app/api/api_message_util.py @@ -1,3 +1,5 @@ +from typing import Any + from pydantic import EmailStr from app.core.schemas.message import Message @@ -8,7 +10,7 @@ def get_delete_return_msg( model_table_name: str, - filter: dict[str, str], + filter: dict[str, Any], ) -> Message: """Returns a message indicating the object was deleted successfully. diff --git a/src/backend_api/app/api/routes/__init__.py b/src/backend_api/app/api/routes/__init__.py index 1ddd5963..a709f6e2 100644 --- a/src/backend_api/app/api/routes/__init__.py +++ b/src/backend_api/app/api/routes/__init__.py @@ -5,6 +5,7 @@ from app.api.routes.unidentified_item import unidentified_item_prefix from app.api.routes.login import login_prefix from app.api.routes.modifier import modifier_prefix +from app.api.routes.carantene_modifier import carantene_modifier_prefix from app.api.routes.plot import plot_prefix from app.api.routes.turnstile import turnstile_prefix from app.api.routes.user import user_prefix diff --git a/src/backend_api/app/api/routes/carantene_modifier.py b/src/backend_api/app/api/routes/carantene_modifier.py new file mode 100644 index 00000000..2a91975d --- /dev/null +++ b/src/backend_api/app/api/routes/carantene_modifier.py @@ -0,0 +1,165 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends, Query, Request, Response +from sqlalchemy.orm import Session + +import app.core.schemas as schemas +from app.api.api_message_util import ( + get_delete_return_msg, +) +from app.api.deps import ( + get_current_active_superuser, + get_current_active_user, + get_db, +) +from app.api.params import FilterParams +from app.core.models.models import Modifier +from app.core.rate_limit.rate_limit_config import rate_limit_settings +from app.core.rate_limit.rate_limiters import ( + apply_user_rate_limits, +) +from app.crud import CRUD_carantene_modifier + +router = APIRouter() + + +carantene_modifier_prefix = "carantene_modifier" + + +@router.get( + "/{caranteneModifierId}", + response_model=schemas.Modifier | list[schemas.Modifier], + dependencies=[Depends(get_current_active_user)], +) +@apply_user_rate_limits( + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_SECOND, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_MINUTE, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, +) +async def get_carantene_modifier( + request: Request, # noqa: ARG001 + response: Response, # noqa: ARG001 + caranteneModifierId: int, + db: Session = Depends(get_db), +): + """ + Get carantene modifier or list of carantene modifiers by key and + value for "caranteneModifierId" + + Dominant key is "caranteneModifierId". + + Returns one or a list of carantene_modifiers. + """ + + carantene_modifier_map = {"caranteneModifierId": caranteneModifierId} + carantene_modifier = await CRUD_carantene_modifier.get( + db=db, filter=carantene_modifier_map + ) + + return carantene_modifier + + +@router.get( + "/", + response_model=schemas.Modifier | list[schemas.Modifier], + dependencies=[Depends(get_current_active_user)], +) +@apply_user_rate_limits( + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_SECOND, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_MINUTE, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, +) +async def get_all_carantene_modifiers( + request: Request, # noqa: ARG001 + response: Response, # noqa: ARG001 + filter_params: Annotated[FilterParams, Query()], + db: Session = Depends(get_db), +): + """ + Get all carantene_modifiers. + + Returns a list of all carantene_modifiers. + """ + + all_carantene_modifiers = await CRUD_carantene_modifier.get( + db=db, filter_params=filter_params + ) + + return all_carantene_modifiers + + +@router.post( + "/", + response_model=schemas.ModifierCreate | list[schemas.ModifierCreate] | None, + dependencies=[Depends(get_current_active_superuser)], +) +async def create_carantene_modifier( + carantene_modifier: schemas.ModifierCreate | list[schemas.ModifierCreate], + return_nothing: bool | None = None, + db: Session = Depends(get_db), +): + """ + Create one or a list of new carantene_modifiers. + + Returns the created carantene_modifier or list of carantene_modifiers. + """ + + return await CRUD_carantene_modifier.create( + db=db, obj_in=carantene_modifier, return_nothing=return_nothing + ) + + +@router.put( + "/", + response_model=schemas.Modifier, + dependencies=[Depends(get_current_active_superuser)], +) +async def update_carantene_modifier( + caranteneModifierId: int, + carantene_modifier_update: schemas.ModifierUpdate, + db: Session = Depends(get_db), +): + """ + Update a carantene_modifier by key and value for "caranteneModifierId" + + Dominant key is "caranteneModifierId". + + Returns the updated carantene modifier. + """ + + carantene_modifier_map = {"caranteneModifierId": caranteneModifierId} + + carantene_modifier = await CRUD_carantene_modifier.get( + db=db, + filter=carantene_modifier_map, + ) + + return await CRUD_carantene_modifier.update( + db_obj=carantene_modifier, obj_in=carantene_modifier_update, db=db + ) + + +@router.delete( + "/{caranteneModifierId}", + response_model=str, + dependencies=[Depends(get_current_active_superuser)], +) +async def delete_carantene_modifier( + caranteneModifierId: int, + db: Session = Depends(get_db), +): + """ + Delete a carantene modifier by key and value for "caranteneModifierId" + + Returns a message that the carantene modifier was deleted. + Always deletes one carantene_modifier. + """ + + carantene_modifier_map = {"caranteneModifierId": caranteneModifierId} + await CRUD_carantene_modifier.remove(db=db, filter=carantene_modifier_map) + + return get_delete_return_msg( + model_table_name=Modifier.__tablename__, filter=carantene_modifier_map + ).message diff --git a/src/backend_api/app/core/models/models.py b/src/backend_api/app/core/models/models.py index 65435167..ab759cbc 100644 --- a/src/backend_api/app/core/models/models.py +++ b/src/backend_api/app/core/models/models.py @@ -160,7 +160,12 @@ class Modifier(Base): corrupted: Mapped[bool | None] = mapped_column(Boolean) enchanted: Mapped[bool | None] = mapped_column(Boolean) veiled: Mapped[bool | None] = mapped_column(Boolean) + # mutated: Mapped[bool | None] = mapped_column(Boolean) static: Mapped[bool | None] = mapped_column(Boolean) + # dynamicallyCreated: Mapped[bool | None] = mapped_column( + # Boolean + # ) # Modifier got created during extract of items + explicit: Mapped[bool | None] = mapped_column(Boolean) effect: Mapped[str] = mapped_column(Text, nullable=False) relatedUniques: Mapped[str | None] = mapped_column(Text) textRolls: Mapped[str | None] = mapped_column(Text) @@ -223,6 +228,35 @@ class Modifier(Base): ) +class CaranteneModifier(Base): + __tablename__ = "carantene_modifier" + + caranteneModifierId: Mapped[int] = mapped_column( + BigInteger, + Identity(start=1, increment=1, cycle=True), + primary_key=True, + ) + effect: Mapped[str] = mapped_column(Text) + relatedUnique: Mapped[str] = mapped_column(Text) + implicit: Mapped[bool | None] = mapped_column(Boolean) + explicit: Mapped[bool | None] = mapped_column(Boolean) + delve: Mapped[bool | None] = mapped_column(Boolean) + fractured: Mapped[bool | None] = mapped_column(Boolean) + synthesised: Mapped[bool | None] = mapped_column(Boolean) + unique: Mapped[bool | None] = mapped_column(Boolean) + corrupted: Mapped[bool | None] = mapped_column(Boolean) + enchanted: Mapped[bool | None] = mapped_column(Boolean) + veiled: Mapped[bool | None] = mapped_column(Boolean) + mutated: Mapped[bool | None] = mapped_column(Boolean) + createdAt: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=func.now(), nullable=False + ) + updatedAt: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), + onupdate=func.now(), + ) + + class ItemModifier(Base): # Hypertable # For hypertable specs, see alembic revision `cc29b89156db' diff --git a/src/backend_api/app/core/schemas/__init__.py b/src/backend_api/app/core/schemas/__init__.py index bc580562..3a927596 100644 --- a/src/backend_api/app/core/schemas/__init__.py +++ b/src/backend_api/app/core/schemas/__init__.py @@ -8,6 +8,12 @@ ModifierCreate, ModifierUpdate, ) +from .carantene_modifier import ( + CaranteneModifier, + CaranteneModifierInDB, + CaranteneModifierCreate, + CaranteneModifierUpdate, +) from .item_base_type import ( ItemBaseType, ItemBaseTypeInDB, diff --git a/src/backend_api/app/core/schemas/carantene_modifier.py b/src/backend_api/app/core/schemas/carantene_modifier.py new file mode 100644 index 00000000..cf5d6831 --- /dev/null +++ b/src/backend_api/app/core/schemas/carantene_modifier.py @@ -0,0 +1,48 @@ +import datetime as _dt + +import pydantic as _pydantic + + +# Shared modifier props +class _BaseCaranteneModifier(_pydantic.BaseModel): + model_config = _pydantic.ConfigDict(from_attributes=True) + + effect: str + relatedUnique: str + implicit: bool | None = None + explicit: bool | None = None + delve: bool | None = None + fractured: bool | None = None + synthesised: bool | None = None + unique: bool | None = None + corrupted: bool | None = None + enchanted: bool | None = None + veiled: bool | None = None + mutated: bool | None = None + + +# Properties to receive on modifier creation +class CaranteneModifierCreate(_BaseCaranteneModifier): + pass + + +# Properties to receive on update +class CaranteneModifierUpdate(_BaseCaranteneModifier): + pass + + +# Properties shared by models stored in DB +class CaranteneModifierInDBBase(_BaseCaranteneModifier): + caranteneModifierId: int + createdAt: _dt.datetime + updatedAt: _dt.datetime | None = None + + +# Properties to return to client +class CaranteneModifier(CaranteneModifierInDBBase): + pass + + +# Properties stored in DB +class CaranteneModifierInDB(CaranteneModifierInDBBase): + pass diff --git a/src/backend_api/app/crud/__init__.py b/src/backend_api/app/crud/__init__.py index 62977e8e..5415dd9e 100644 --- a/src/backend_api/app/crud/__init__.py +++ b/src/backend_api/app/crud/__init__.py @@ -1,8 +1,9 @@ -from app.core.models.models import Currency as model_Currency +from app.core.models.models import CaranteneModifier, Currency as model_Currency from app.core.models.models import Item as model_Item from app.core.models.models import ItemBaseType as model_ItemBaseType from app.core.models.models import ItemModifier as model_ItemModifier from app.core.models.models import Modifier as model_Modifier +from app.core.models.models import CaranteneModifier as model_CaranteneModifier from app.core.models.models import UnidentifiedItem as model_UnidentifiedItem from app.core.schemas.currency import Currency, CurrencyCreate, CurrencyUpdate from app.core.schemas.item import Item, ItemCreate, ItemUpdate @@ -17,6 +18,11 @@ ItemModifierUpdate, ) from app.core.schemas.modifier import Modifier, ModifierCreate +from app.core.schemas.carantene_modifier import ( + CaranteneModifier, + CaranteneModifierCreate, + CaranteneModifierUpdate, +) from app.core.schemas.unidentified_item import ( UnidentifiedItem, UnidentifiedItemCreate, @@ -79,4 +85,15 @@ create_schema=ModifierCreate, ) +CRUD_carantene_modifier = CRUDBase[ + model_CaranteneModifier, + CaranteneModifier, + CaranteneModifierCreate, + CaranteneModifierUpdate, +]( + model=model_CaranteneModifier, + schema=CaranteneModifier, + create_schema=CaranteneModifierCreate, +) + CRUD_user = CRUDUser() From d9d7d72cda191e952a879a9295482981a30dd859 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sat, 22 Nov 2025 16:54:34 +0100 Subject: [PATCH 04/18] #802 Fix new deposit data modifiers and base types --- .../item_base_type_data/Amulets.csv | 1 + .../item_base_type_data/Armour.csv | 68 ++++++++++++++++++- .../item_base_type_data/Weapons.csv | 30 +++++++- .../modifier/modifier_data/BoundByDestiny.csv | 4 +- .../modifier/modifier_data/ForbiddenFlame.csv | 2 +- .../modifier/modifier_data/ForbiddenFlesh.csv | 2 +- .../modifier/modifier_data/ForbiddenShako.csv | 2 +- .../modifier_data/ImpossibleEscape.csv | 4 +- .../ReplicaDragonfangsFlight.csv | 2 +- .../modifier/modifier_data/ThreadOfHope.csv | 6 +- 10 files changed, 108 insertions(+), 13 deletions(-) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Amulets.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Amulets.csv index ae1f573a..3ef88965 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Amulets.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Amulets.csv @@ -10,3 +10,4 @@ baseType,category,relatedUniques "Turquoise Amulet",amulet, "Agate Amulet",amulet, "Citrine Amulet",amulet, +"Agate Amulet",amulet, diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv index 9689658d..bfd23661 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv @@ -2,5 +2,71 @@ # Note: Only contains bases relevant to the uniques we are tracking baseType,category,subCategory,relatedUniques "Great Crown","any armour",helmet,"Forbidden Shako" +"Leather Cap","any armour","helmet", +"Silken Hood","any armour","helmet", +"Sinner Tricorne","any armour","helmet", +"Iron Mask","any armour","helmet", +"Golden Mask","any armour","helmet", +"Regicide Mask","any armour","helmet", +"Lunaris Circlet","any armour","helmet", +"Royal Burgonet","any armour","helmet", +"Nightmare Bascinet","any armour","helmet", +"Gilded Sallet","any armour","helmet", +"Great Crown","any armour","helmet", +"Great Helmet","any armour","helmet", +"Zealot Helmet","any armour","helmet", +"Praetor Crown","any armour","helmet", "Carnal Armour","any armour","body armour","Shroud of the Lightless" -"Simple Robe","any armour","body armour","Skin of the Lords" \ No newline at end of file +"Simple Robe","any armour","body armour","Skin of the Lords" +"Zodiac Leather","any armour","body armour", +"Buckskin Tunic","any armour","body armour", +"Cutthroat's Garb","any armour","body armour", +"Destiny Leather","any armour","body armour", +"Exquisite Leather","any armour","body armour", +"Varnished Coat","any armour","body armour", +"Lacquered Garb","any armour","body armour", +"Destroyer Regalia","any armour","body armour", +"Sage's Robe","any armour","body armour", +"Silken Vest","any armour","body armour", +"Scholar's Robe","any armour","body armour", +"Spidersilk Robe","any armour","body armour", +"Necromancer Silks","any armour","body armour", +"Plate Vest","any armour","body armour", +"Copper Plate","any armour","body armour", +"Golden Plate","any armour","body armour", +"Crusader Plate","any armour","body armour", +"Gladiator Plate","any armour","body armour", +"Triumphant Lamellar","any armour","body armour", +"Wyrmscale Doublet","any armour","body armour", +"Crusader Chainmail","any armour","body armour", +"Nubuck Boots","any armour","boots", +"Goathide Boots","any armour","boots", +"Sorcerer Boots","any armour","boots", +"Titan Greaves","any armour","boots", +"Bronzescale Boots","any armour","boots", +"Dragonscale Boots","any armour","boots", +"Soldier Boots","any armour","boots", +"Deerskin Gloves","any armour","gloves", +"Strapped Mitts","any armour","gloves", +"Assassin's Mitts","any armour","gloves", +"Clasped Mitts","any armour","gloves", +"Carnal Mitts","any armour","gloves", +"Wool Gloves","any armour","gloves", +"Samite Gloves","any armour","gloves", +"Conjurer Gloves","any armour","gloves", +"Titan Gauntlets","any armour","gloves", +"Ironscale Gauntlets","any armour","gloves", +"Wyrmscale Gauntlets","any armour","gloves", +"Chain Gloves","any armour","gloves", +"Legion Gloves","any armour","gloves", +"War Buckler","any armour","shield", +"Corrugated Buckler","any armour","shield", +"Ironwood Buckler","any armour","shield", +"Titanium Spirit Shield","any armour","shield", +"Tarnished Spirit Shield","any armour","shield", +"Ancient Spirit Shield","any armour","shield", +"Harmonic Spirit Shield","any armour","shield", +"Vaal Spirit Shield","any armour","shield", +"Ezomyte Tower Shield","any armour","shield", +"Elegant Round Shield","any armour","shield", +"Mosaic Kite Shield","any armour","shield", diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv index f27411cf..5da8915c 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv @@ -2,4 +2,32 @@ # Note: Only contains bases relevant to the uniques we are tracking baseType,category,subCategory,relatedUniques "Vaal Rapier","weapon","one-handed sword","Paradoxica" -"Serpentine Staff","weapon","warstaff","Cane of Kulemak" \ No newline at end of file +"Midnight Blade","weapon","one-handed sword" +"Serpentine Staff","weapon","warstaff","Cane of Kulemak" +"Ranger Bow","weapon","bow", +"Citadel Bow","weapon","bow", +"Short Bow","weapon","bow", +"Assassin Bow","weapon","bow", +"Terror Claw","weapon","claw", +"Imperial Claw","weapon","claw", +"Timeworn Claw","weapon","claw", +"Fiend Dagger","weapon","dagger", +"Fishing Rod","weapon","fishing rod" +"Tomahawk","weapon","one-handed axe" +"Royal Axe","weapon","one-handed axe" +"Ornate Mace","weapon","one-handed mace" +"Gavel","weapon","one-handed mace" +"Cutlass","weapon","one-handed sword" +"Platinum Sceptre","weapon","sceptre" +"Serpentine Staff","weapon","staff", +"Vaal Axe","weapon","two-handed axe", +"Jasper Chopper","weapon","two-handed axe", +"Terror Maul","weapon","two-handed mace", +"Steelhead","weapon","two-handed mace", +"Etched Greatsword","weapon","two-handed sword", +"Lion Sword","weapon","two-handed sword", +"Reaver Sword","weapon","two-handed sword", +"Goat's Horn","weapon","wand", +"Opal Wand","weapon","wand", +"Calling Wand","weapon","wand", +"Kinetic Wand","weapon","wand", diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/BoundByDestiny.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/BoundByDestiny.csv index 5e6b5c89..b3f7efb4 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/BoundByDestiny.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/BoundByDestiny.csv @@ -29,7 +29,7 @@ minRoll,maxRoll,position,effect,static,unique ,,0,"+3% to maximum Chance to Block Spell Damage if 4 Shaper Items are Equipped",True,True ,,0,"Cannot take Reflected Elemental Damage if 4 Shaper Items are Equipped",True,True 1,1.5,0,"+#% to Spell Critical Strike Chance if 4 Shaper Items are Equipped",,True -5,10,0,"#% of Elemental taken as Chaos Damage if 4 Hunter Items are Equipped",,True +5,10,0,"#% of Elemental Damage taken as Chaos Damage if 4 Hunter Items are Equipped",,True 10,25,0,"#% increased Movement Speed if 4 Hunter Items are Equipped",,True 2,3,0,"+#% to maximum Chaos Resistance if 4 Hunter Items are Equipped",,True 5,10,0,"#% of Physical Damage taken as Fire Damage if 4 Warlord Items are Equipped",,True @@ -58,4 +58,4 @@ minRoll,maxRoll,position,effect,static,unique ,,0,"+1 to Maximum Frenzy Charges if 6 Redeemer Items are Equipped",True,True 1,3,0,"#% of Physical Damage Prevented Recently is Regenerated as Energy Shield Per Second if 6 Crusader Items are Equipped",,True ,,0,"+1 to Level of all Lightning Skill Gems if 6 Crusader Items are Equipped",True,True -,,0,"+1 to Maximum Power Charges if 6 Crusader Items are Equipped",True,True \ No newline at end of file +,,0,"+1 to Maximum Power Charges if 6 Crusader Items are Equipped",True,True diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenFlame.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenFlame.csv index 29e80008..b5442304 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenFlame.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenFlame.csv @@ -5,4 +5,4 @@ # Modifier distrubution: "rest":1 # Source: https://poedb.tw/Forbidden_Flame textRolls,position,effect,unique -"Assassin|Berserker|Champion|Chieftain|Deadeye|Elementalist|Gladiator|Guardian|Hierophant|Inquisitor|Juggernaut|Necromancer|Occultist|Pathfinder|Saboteur|Slayer|Trickster|Unleashed Potential|Warden|Fatal Flourish|Indomitable Resolve|Fury of Nature|Searing Purity|Nine Lives|Ambush and Assassinate|Deadly Infusion|Mistwalker|Noxious Strike|Opportunistic|Toxic Delivery|Unstable Infusion|Aspect of Carnage|Blitz|Gore Dancer|Ancestral Fury|Crave the Slaughter|Defy Pain|Flawless Savagery|Rite of Ruin|War Bringer|Conqueror|Worthy Causes|First to Strike, Last to Fall|Fortitude|Inspirational|Master of Metal|Unstoppable Hero|Worthy Foe|Hinekora, Death's Fury|Ngamahu, Flame's Advance|Ramako, Sun's Light|Sione, Sun's Roar|Tasalio, Cleansing Water|Tawhoa, Forest's Strength|Tukohama, War's Herald|Valako, Storm's Embrace|Avidity|Endless Munitions|Far Shot|Focal Point|Gathering Winds|Occupying Force|Ricochet|Wind Ward|Bastion of Elements|Elemancer|Bringer of Ruin|Heart of Destruction|Liege of the Primordial|Mastermind of Discord|Shaper of Flames|Shaper of Storms|Shaper of Winter|Determined Survivor|Gratuitous Violence|Jagged Technique|Measured Retaliation|More Than Skill|War of Attrition|Weapon Master|Bastion of Hope|Harmony of Purpose|Radiant Crusade|Radiant Faith|Time of Need|Unwavering Crusade|Unwavering Faith|Arcane Blessing|Conviction of Power|Divine Guidance|Illuminated Devotion|Pursuit of Faith|Ritual of Awakening|Sanctuary of Thought|Sign of Purpose|Augury of Penitence|Inevitable Judgement|Instruments of Virtue|Instruments of Zeal|Pious Path|Righteous Providence|Sanctuary|Unbreakable|Undeniable|Unflinching|Unrelenting|Unstoppable|Untiring|Unyielding|Bone Barrier|Commander of Darkness|Corpse Pact|Essence Glutton|Mindless Aggression|Mistress of Sacrifice|Plaguebringer|Unnatural Strength|Forbidden Power|Frigid Wake|Profane Bloom|Unholy Authority|Vile Bastion|Void Beacon|Withering Presence|Master Alchemist|Master Distiller|Master Surgeon|Master Toxicist|Nature's Adrenaline|Nature's Boon|Nature's Reprisal|Bomb Specialist|Born in the Shadows|Chain Reaction|Demolitions Specialist|Explosives Expert|Shrapnel Specialist|Calculated Risk|Like Clockwork|Perfect Crime|Pyromaniac|Harness the Void|Bane of Legends|Brutal Fervour|Endless Hunger|Headsman|Impact|Masterful Form|Overwhelm|Escape Artist|Heartstopper|One Step Ahead|Polymath|Soul Drinker|Spellbreaker|Swift Killer|Avatar of the Wilds|Enduring Suffusion|Experienced Herbalist|Lesson of the Seasons|Mother's Teachings|Oath of Spring|Oath of Summer|Oath of Winter|Seasoned Hunter",0,"Allocates # if you have the matching modifier on Forbidden Flesh",True +"Assassin|Berserker|Champion|Chieftain|Deadeye|Deathmarked|Elementalist|Gladiator|Guardian|Hierophant|Inquisitor|Juggernaut|Necromancer|Occultist|Pathfinder|Saboteur|Slayer|Trickster|Unleashed Potential|Warden|Fatal Flourish|Indomitable Resolve|Fury of Nature|Searing Purity|Nine Lives|Ambush and Assassinate|Deadly Infusion|Mistwalker|Noxious Strike|Opportunistic|Toxic Delivery|Unstable Infusion|Aspect of Carnage|Blitz|Gore Dancer|Ancestral Fury|Crave the Slaughter|Defy Pain|Flawless Savagery|Rite of Ruin|War Bringer|Conqueror|Worthy Causes|First to Strike, Last to Fall|Fortitude|Inspirational|Master of Metal|Unstoppable Hero|Worthy Foe|Hinekora, Death's Fury|Ngamahu, Flame's Advance|Ramako, Sun's Light|Sione, Sun's Roar|Tasalio, Cleansing Water|Tawhoa, Forest's Strength|Tukohama, War's Herald|Valako, Storm's Embrace|Avidity|Endless Munitions|Far Shot|Focal Point|Gathering Winds|Occupying Force|Ricochet|Wind Ward|Bastion of Elements|Elemancer|Bringer of Ruin|Heart of Destruction|Liege of the Primordial|Mastermind of Discord|Shaper of Flames|Shaper of Storms|Shaper of Winter|Determined Survivor|Gratuitous Violence|Jagged Technique|Measured Retaliation|More Than Skill|War of Attrition|Weapon Master|Bastion of Hope|Harmony of Purpose|Radiant Crusade|Radiant Faith|Time of Need|Unwavering Crusade|Unwavering Faith|Arcane Blessing|Conviction of Power|Divine Guidance|Illuminated Devotion|Pursuit of Faith|Ritual of Awakening|Sanctuary of Thought|Sign of Purpose|Augury of Penitence|Inevitable Judgement|Instruments of Virtue|Instruments of Zeal|Pious Path|Righteous Providence|Sanctuary|Unbreakable|Undeniable|Unflinching|Unrelenting|Unstoppable|Untiring|Unyielding|Bone Barrier|Commander of Darkness|Corpse Pact|Essence Glutton|Mindless Aggression|Mistress of Sacrifice|Plaguebringer|Unnatural Strength|Forbidden Power|Frigid Wake|Profane Bloom|Unholy Authority|Vile Bastion|Void Beacon|Withering Presence|Master Alchemist|Master Distiller|Master Surgeon|Master Toxicist|Nature's Adrenaline|Nature's Boon|Nature's Reprisal|Bomb Specialist|Born in the Shadows|Chain Reaction|Demolitions Specialist|Explosives Expert|Shrapnel Specialist|Calculated Risk|Like Clockwork|Perfect Crime|Pyromaniac|Harness the Void|Bane of Legends|Brutal Fervour|Endless Hunger|Headsman|Impact|Masterful Form|Overwhelm|Escape Artist|Heartstopper|One Step Ahead|Polymath|Soul Drinker|Spellbreaker|Swift Killer|Avatar of the Wilds|Enduring Suffusion|Experienced Herbalist|Lesson of the Seasons|Mother's Teachings|Oath of Spring|Oath of Summer|Oath of Winter|Seasoned Hunter",0,"Allocates # if you have the matching modifier on Forbidden Flesh",True diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenFlesh.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenFlesh.csv index 5947e54b..f649070f 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenFlesh.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenFlesh.csv @@ -5,4 +5,4 @@ # Modifier distrubution: "rest":1 # Source: https://poedb.tw/Forbidden_Flesh textRolls,position,effect,unique -"Assassin|Berserker|Champion|Chieftain|Deadeye|Elementalist|Gladiator|Guardian|Hierophant|Inquisitor|Juggernaut|Necromancer|Occultist|Pathfinder|Saboteur|Slayer|Trickster|Unleashed Potential|Warden|Fatal Flourish|Indomitable Resolve|Fury of Nature|Searing Purity|Nine Lives|Ambush and Assassinate|Deadly Infusion|Mistwalker|Noxious Strike|Opportunistic|Toxic Delivery|Unstable Infusion|Aspect of Carnage|Blitz|Gore Dancer|Ancestral Fury|Crave the Slaughter|Defy Pain|Flawless Savagery|Rite of Ruin|War Bringer|Conqueror|Worthy Causes|First to Strike, Last to Fall|Fortitude|Inspirational|Master of Metal|Unstoppable Hero|Worthy Foe|Hinekora, Death's Fury|Ngamahu, Flame's Advance|Ramako, Sun's Light|Sione, Sun's Roar|Tasalio, Cleansing Water|Tawhoa, Forest's Strength|Tukohama, War's Herald|Valako, Storm's Embrace|Avidity|Endless Munitions|Far Shot|Focal Point|Gathering Winds|Occupying Force|Ricochet|Wind Ward|Bastion of Elements|Elemancer|Bringer of Ruin|Heart of Destruction|Liege of the Primordial|Mastermind of Discord|Shaper of Flames|Shaper of Storms|Shaper of Winter|Determined Survivor|Gratuitous Violence|Jagged Technique|Measured Retaliation|More Than Skill|War of Attrition|Weapon Master|Bastion of Hope|Harmony of Purpose|Radiant Crusade|Radiant Faith|Time of Need|Unwavering Crusade|Unwavering Faith|Arcane Blessing|Conviction of Power|Divine Guidance|Illuminated Devotion|Pursuit of Faith|Ritual of Awakening|Sanctuary of Thought|Sign of Purpose|Augury of Penitence|Inevitable Judgement|Instruments of Virtue|Instruments of Zeal|Pious Path|Righteous Providence|Sanctuary|Unbreakable|Undeniable|Unflinching|Unrelenting|Unstoppable|Untiring|Unyielding|Bone Barrier|Commander of Darkness|Corpse Pact|Essence Glutton|Mindless Aggression|Mistress of Sacrifice|Plaguebringer|Unnatural Strength|Forbidden Power|Frigid Wake|Profane Bloom|Unholy Authority|Vile Bastion|Void Beacon|Withering Presence|Master Alchemist|Master Distiller|Master Surgeon|Master Toxicist|Nature's Adrenaline|Nature's Boon|Nature's Reprisal|Bomb Specialist|Born in the Shadows|Chain Reaction|Demolitions Specialist|Explosives Expert|Shrapnel Specialist|Calculated Risk|Like Clockwork|Perfect Crime|Pyromaniac|Harness the Void|Bane of Legends|Brutal Fervour|Endless Hunger|Headsman|Impact|Masterful Form|Overwhelm|Escape Artist|Heartstopper|One Step Ahead|Polymath|Soul Drinker|Spellbreaker|Swift Killer|Avatar of the Wilds|Enduring Suffusion|Experienced Herbalist|Lesson of the Seasons|Mother's Teachings|Oath of Spring|Oath of Summer|Oath of Winter|Seasoned Hunter",0,"Allocates # if you have the matching modifier on Forbidden Flame",True +"Assassin|Berserker|Champion|Chieftain|Deadeye|Deathmarked|Elementalist|Gladiator|Guardian|Hierophant|Inquisitor|Juggernaut|Necromancer|Occultist|Pathfinder|Saboteur|Slayer|Trickster|Unleashed Potential|Warden|Fatal Flourish|Indomitable Resolve|Fury of Nature|Searing Purity|Nine Lives|Ambush and Assassinate|Deadly Infusion|Mistwalker|Noxious Strike|Opportunistic|Toxic Delivery|Unstable Infusion|Aspect of Carnage|Blitz|Gore Dancer|Ancestral Fury|Crave the Slaughter|Defy Pain|Flawless Savagery|Rite of Ruin|War Bringer|Conqueror|Worthy Causes|First to Strike, Last to Fall|Fortitude|Inspirational|Master of Metal|Unstoppable Hero|Worthy Foe|Hinekora, Death's Fury|Ngamahu, Flame's Advance|Ramako, Sun's Light|Sione, Sun's Roar|Tasalio, Cleansing Water|Tawhoa, Forest's Strength|Tukohama, War's Herald|Valako, Storm's Embrace|Avidity|Endless Munitions|Far Shot|Focal Point|Gathering Winds|Occupying Force|Ricochet|Wind Ward|Bastion of Elements|Elemancer|Bringer of Ruin|Heart of Destruction|Liege of the Primordial|Mastermind of Discord|Shaper of Flames|Shaper of Storms|Shaper of Winter|Determined Survivor|Gratuitous Violence|Jagged Technique|Measured Retaliation|More Than Skill|War of Attrition|Weapon Master|Bastion of Hope|Harmony of Purpose|Radiant Crusade|Radiant Faith|Time of Need|Unwavering Crusade|Unwavering Faith|Arcane Blessing|Conviction of Power|Divine Guidance|Illuminated Devotion|Pursuit of Faith|Ritual of Awakening|Sanctuary of Thought|Sign of Purpose|Augury of Penitence|Inevitable Judgement|Instruments of Virtue|Instruments of Zeal|Pious Path|Righteous Providence|Sanctuary|Unbreakable|Undeniable|Unflinching|Unrelenting|Unstoppable|Untiring|Unyielding|Bone Barrier|Commander of Darkness|Corpse Pact|Essence Glutton|Mindless Aggression|Mistress of Sacrifice|Plaguebringer|Unnatural Strength|Forbidden Power|Frigid Wake|Profane Bloom|Unholy Authority|Vile Bastion|Void Beacon|Withering Presence|Master Alchemist|Master Distiller|Master Surgeon|Master Toxicist|Nature's Adrenaline|Nature's Boon|Nature's Reprisal|Bomb Specialist|Born in the Shadows|Chain Reaction|Demolitions Specialist|Explosives Expert|Shrapnel Specialist|Calculated Risk|Like Clockwork|Perfect Crime|Pyromaniac|Harness the Void|Bane of Legends|Brutal Fervour|Endless Hunger|Headsman|Impact|Masterful Form|Overwhelm|Escape Artist|Heartstopper|One Step Ahead|Polymath|Soul Drinker|Spellbreaker|Swift Killer|Avatar of the Wilds|Enduring Suffusion|Experienced Herbalist|Lesson of the Seasons|Mother's Teachings|Oath of Spring|Oath of Summer|Oath of Winter|Seasoned Hunter",0,"Allocates # if you have the matching modifier on Forbidden Flame",True diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenShako.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenShako.csv index c4997ffa..aa39b68f 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenShako.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ForbiddenShako.csv @@ -7,4 +7,4 @@ minRoll,maxRoll,textRolls,position,effect,unique 25,30,,0,"+# to all Attributes",True 1,35,,0,"Socketed Gems are Supported by Level # #",True -,,"Greater Multiple Projectiles|Void Manipulation|Vile Toxins|Unleash|Unbound Ailments|Advanced Traps|Trap and Mine Damage|Trap|Swift Assembly|Summon Phantasm|Elemental Army|Stun|Infused Channelling|Ancestral Call|Spell Totem|Intensify|Spell Cascade|Slower Projectiles|Shockwave|Second Wind|Ruthless|High-Impact Mine|Blastchain Mine|Inspiration|Less Duration|Swift Affliction|Ballista Totem|Rage|Pulverise|Power Charge On Critical|Critical Strike Affliction|Point Blank|Pierce|Physical to Lightning|Vicious Projectiles|Volley|Momentum|Nightblade|Multiple Traps|Multiple Totems|Multistrike|Spell Echo|Mirage Archer|Minefield|Melee Splash|Melee Physical Damage|Damage on Full Life|Meat Shield|Mana Leech|Maim|Lightning Penetration|Life Leech|Life Gain on Hit|Chance to Poison|Lesser Multiple Projectiles|Knockback|Item Rarity|Iron Will|Iron Grip|Innervate|Infernal Legion|Minion Speed|Minion Life|Minion Damage|More Duration|Increased Critical Strikes|Increased Critical Damage|Burning Damage|Increased Area of Effect|Impale|Immolate|Ignite Proliferation|Ice Bite|Hypothermia|Greater Volley|Elemental Damage with Attacks|Generosity|Charged Traps|Fortify|Fork|Chance to Flee|Fire Penetration|Feeding Frenzy|Faster Projectiles|Faster Casting|Faster Attacks|Energy Leech|Endurance Charge on Melee Stun|Elemental Proliferation|Elemental Focus|Efficacy|Decay|Predator|Deadly Ailments|Hextouch|Culling Strike|Controlled Destruction|Concentrated Effect|Cold to Fire|Cold Penetration|Cluster Traps|Close Combat|Charged Mines|Withering Touch|Combustion|Chance to Bleed|Chain|Cast while Channelling|Cast when Stunned|Cast on Melee Kill|Cast on Death|Cast when Damage Taken|Cast On Critical Strike|Brutality|Bonechill|Arrogance|Bloodlust|Blind|Blasphemy|Barrage|Arrow Nova|Archmage|Arcane Surge|Additional Accuracy|Added Lightning Damage|Added Fire Damage|Added Cold Damage|Added Chaos Damage|Fist of War|Swiftbrand|Urgent Orders|Pinpoint|Impending Doom|Trinity|Bloodthirst|Cruelty|Lifetap|Focused Ballista|Focused Channelling|Earthbreaker|Behead|Mark On Hit|Divine Blessing|Eternal Blessing|Overcharge|Cursed Ground|Hex Bloom|Manaforged Arrows|Prismatic Burst|Returning Projectiles|Trauma|Spellblade|Devour|Fresh Meat|Flamewood|Corrupting Cry|Volatility|Guardian's Blessing|Sacrifice|Frigid Bond|Locus Mine|Sadism|Controlled Blaze|Rupture|Overexertion|Sacred Wisps|Expert Retaliation",1,"Socketed Gems are Supported by Level # #",True +,,"Greater Multiple Projectiles|Void Manipulation|Vile Toxins|Unleash|Unbound Ailments|Advanced Traps|Trap and Mine Damage|Trap|Swift Assembly|Summon Phantasm|Elemental Army|Stun|Infused Channelling|Ancestral Call|Spell Totem|Intensify|Spell Cascade|Slower Projectiles|Shockwave|Second Wind|Ruthless|High-Impact Mine|Blastchain Mine|Inspiration|Less Duration|Swift Affliction|Ballista Totem|Rage|Pulverise|Power Charge On Critical|Critical Strike Affliction|Point Blank|Pierce|Physical to Lightning|Vicious Projectiles|Volley|Momentum|Nightblade|Multiple Traps|Multiple Totems|Multistrike|Spell Echo|Mirage Archer|Minefield|Melee Splash|Melee Physical Damage|Damage on Full Life|Meat Shield|Mana Leech|Maim|Lightning Penetration|Life Leech|Life Gain on Hit|Chance to Poison|Lesser Multiple Projectiles|Knockback|Item Rarity|Iron Will|Iron Grip|Innervate|Infernal Legion|Minion Speed|Minion Life|Minion Damage|More Duration|Increased Critical Strikes|Increased Critical Damage|Burning Damage|Increased Area of Effect|Impale|Immolate|Ignite Proliferation|Ice Bite|Hypothermia|Greater Volley|Elemental Damage with Attacks|Generosity|Charged Traps|Fortify|Fork|Chance to Flee|Fire Penetration|Feeding Frenzy|Faster Projectiles|Faster Casting|Faster Attacks|Energy Leech|Endurance Charge on Melee Stun|Elemental Proliferation|Elemental Focus|Efficacy|Decay|Predator|Deadly Ailments|Hextouch|Culling Strike|Controlled Destruction|Concentrated Effect|Cold to Fire|Cold Penetration|Cluster Traps|Close Combat|Charged Mines|Withering Touch|Combustion|Chance to Bleed|Chain|Cast while Channelling|Cast when Stunned|Cast on Melee Kill|Cast on Death|Cast when Damage Taken|Cast On Critical Strike|Brutality|Bonechill|Arrogance|Bloodlust|Blind|Blasphemy|Barrage|Arrow Nova|Archmage|Arcane Surge|Additional Accuracy|Added Lightning Damage|Added Fire Damage|Added Cold Damage|Added Chaos Damage|Fist of War|Swiftbrand|Urgent Orders|Pinpoint|Impending Doom|Trinity|Bloodthirst|Cruelty|Lifetap|Focused Ballista|Focused Channelling|Earthbreaker|Behead|Mark On Hit|Divine Blessing|Eternal Blessing|Overcharge|Cursed Ground|Hex Bloom|Manaforged Arrows|Prismatic Burst|Returning Projectiles|Trauma|Spellblade|Devour|Fresh Meat|Flamewood|Corrupting Cry|Volatility|Guardian's Blessing|Sacrifice|Frigid Bond|Locus Mine|Sadism|Controlled Blaze|Rupture|Overexertion|Sacred Wisps|Expert Retaliation|Kinetic Instability|Living Lightning|Windburst",1,"Socketed Gems are Supported by Level # #",True diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ImpossibleEscape.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ImpossibleEscape.csv index f2da540c..c00d2f8d 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ImpossibleEscape.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ImpossibleEscape.csv @@ -2,7 +2,7 @@ # Base Types: Viridian Jewel # Total modifiers on each item: 1 # Can have duplicate modifiers: False -# Modifier distrubution: "rest":1 +# Modifier distrubution: "rest":1 # Source: https://poedb.tw/Impossible_Escape textRolls,position,effect,unique -"Divine Shield|The Agnostic|Resolute Technique|Ancestral Bond|Blood Magic|Zealot's Oath|Avatar of Fire|Glancing Blows|Runebinder|Call to Arms|Arsenal of Vengeance|Eternal Youth|Imbalanced Guard|Elemental Overload|Mind Over Matter|The Impaler|Unwavering Stance|Crimson Dance|Iron Will|Versatile Combatant|Iron Grip|Necromantic Aegis|Worship the Blightheart|Solipsism|Magebane|Iron Reflexes|Hex Master|Minion Instability|Conduit|Vaal Pact|Wicked Ward|Elemental Equilibrium|Pain Attunement|Bloodsoaked Blade|Eldritch Battery|Supreme Ego|Precise Technique|Point Blank|Wind Dancer|Chaos Inoculation|Lethe Shade|Arrow Dancing|Ghost Dance|Ghost Reaver|Acrobatics|Perfect Agony",0,"Passives in Radius of # can be Allocated without being connected to your tree",True \ No newline at end of file +"Divine Shield|The Agnostic|Resolute Technique|Ancestral Bond|Blood Magic|Zealot's Oath|Avatar of Fire|Glancing Blows|Runebinder|Call to Arms|Arsenal of Vengeance|Eternal Youth|Imbalanced Guard|Elemental Overload|Mind Over Matter|The Impaler|Unwavering Stance|Crimson Dance|Iron Will|Versatile Combatant|Iron Grip|Necromantic Aegis|Worship the Blightheart|Solipsism|Magebane|Iron Reflexes|Hex Master|Minion Instability|Conduit|Vaal Pact|Wicked Ward|Elemental Equilibrium|Pain Attunement|Bloodsoaked Blade|Eldritch Battery|Supreme Ego|Precise Technique|Point Blank|Wind Dancer|Chaos Inoculation|Lethe Shade|Arrow Dancing|Ghost Dance|Ghost Reaver|Acrobatics|Perfect Agony",0,"Passive Skills in Radius of # can be Allocated without being connected to your tree Passage",True diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ReplicaDragonfangsFlight.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ReplicaDragonfangsFlight.csv index 9304d5b4..33caae91 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ReplicaDragonfangsFlight.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ReplicaDragonfangsFlight.csv @@ -5,7 +5,7 @@ # Modifier distrubution: "rest":4 # Source: https://poedb.tw/Replica_Dragonfangs_Flight minRoll,maxRoll,textRolls,position,effect,unique -,,"Fireball|Ice Nova|Leap Slam|Sweep|Ground Slam|Cleave|Shield Charge|Enduring Cry|Double Strike|Elemental Hit|Immortal Call|Dual Strike|Whirling Blades|Frenzy|Cold Snap|Raise Zombie|Detonate Dead|Caustic Arrow|Creeping Frost|Ice Shot|Static Strike|Split Arrow|Blood Rage|Righteous Fire|Discharge|Flicker Strike|Spark|Ice Spear|Raise Spectre|Infernal Blow|Glacial Hammer|Frost Wall|Freezing Pulse|Shock Nova|Viper Strike|Phase Run|Explosive Arrow|Temporal Chains|Elemental Weakness|Warlord's Mark|Punishment|Enfeeble|Assassin's Mark|Sniper's Mark|Despair|Lightning Warp|Summon Skeletons|Glacial Shield Swipe|Crushing Fist|Swordstorm|Heavy Strike|Dominating Blow|Rain of Arrows|Firestorm|Lightning Strike|Tempest Shield|Molten Shell|Power Siphon|Puncture|Lightning Arrow|Arc|Haste|Purity of Elements|Vitality|Discipline|Grace|Determination|Anger|Hatred|Wrath|Burning Arrow|Clarity|Shockwave Totem|Rejuvenation Totem|Conversion Trap|Bear Trap|Fire Trap|Decoy Totem|Devouring Totem|Ethereal Knives|Arctic Armour|Holy Flame Totem|Flammability|Frostbite|Conductivity|Incinerate|Cyclone|Searing Bond|Reave|Lightning Trap|Pyroclast Mine|Smoke Mine|Icicle Mine|Stormblast Mine|Animate Guardian|Spectral Throw|Animate Weapon|Purity of Fire|Purity of Ice|Purity of Lightning|Storm Call|Flameblast|Barrage|Ball Lightning|Summon Raging Spirit|Flame Surge|Desecrate|Flesh Offering|Bone Offering|Glacial Cascade|Convocation|Molten Strike|Tornado Shot|Herald of Ash|Herald of Ice|Herald of Thunder|Poacher's Mark|Lightning Tendrils|Mirror Arrow|Blink Arrow|Kinetic Blast|Summon Chaos Golem|Summon Ice Golem|Summon Flame Golem|Summon Lightning Golem|Ice Crash|Rallying Cry|Infernal Cry|Vigilant Strike|Rolling Magma|Flame Dash|Frost Blades|Wild Strike|Galvanic Arrow|Blast Rain|Bladefall|Siege Ballista|Blade Vortex|Contagion|Wither|Essence Drain|Ice Trap|Orb of Storms|Frost Bomb|Summon Stone Golem|Eviscerate|Snipe|Vengeful Cry|Earthquake|Sunder|Lacerate|Spirit Offering|Frostbolt|Vortex|Blight|Scorching Ray|Blade Flurry|Charged Dash|Dark Pact|Storm Burst|Cremation|Bodyswap|Volatile Dead|Unearth|Explosive Trap|Siphoning Trap|Flamethrower Trap|Lightning Spire Trap|Seismic Trap|Vulnerability|Tectonic Slam|Spectral Shield Throw|Herald of Purity|Herald of Agony|Consecrated Path|Smite|Scourge Arrow|Toxic Rain|Summon Holy Relic|Winter Orb|Storm Brand|Armageddon Brand|Brand Recall|War Banner|Dread Banner|Shattering Steel|Lancing Steel|Purifying Flame|Soulrend|Bane|Divine Ire|Wave of Conviction|Zealotry|Malevolence|Precision|Steelskin|Dash|Bladestorm|Blood and Sand|Berserk|Perforate|Chain Hook|Frostblink|Flesh and Stone|Pride|Cobra Lash|Withering Step|Venom Gyre|Summon Skitterbots|Plague Bearer|Pestilent Strike|Summon Carrion Golem|Artillery Ballista|Shrapnel Ballista|Ensnaring Arrow|Stormbind|Blade Blast|Spellslinger|Kinetic Bolt|Arcane Cloak|Intimidating Cry|Ancestral Cry|Seismic Cry|General's Cry|Arcanist Brand|Penance Brand|Wintertide Brand|Earthshatter|Sigil of Power|Splitting Steel|Flame Wall|Blazing Salvo|Crackling Lance|Void Sphere|Frost Shield|Hydrosphere|Hexblast|Exsanguinate|Corrupting Fever|Petrified Blood|Reap|Defiance Banner|Storm Rain|Rage Vortex|Shield Crush|Summon Reaper|Boneshatter|Ambush|Voltaxic Burst|Battlemage's Cry|Absolution|Eye of Winter|Spectral Helix|Forbidden Rite|Blade Trap|Manabond|Explosive Concoction|Poisonous Concoction|Temporal Rift|Energy Blade|Tornado|Soul Link|Flame Link|Intuitive Link|Protective Link|Vampiric Link|Destructive Link|Galvanic Field|Lightning Conduit|Alchemist's Mark|Frozen Legion|Volcanic Fissure|Automation|Autoexertion|Divine Retribution",0,"+3 to Level of all # Gems",True +,,"Fireball|Ice Nova|Leap Slam|Sweep|Ground Slam|Cleave|Shield Charge|Enduring Cry|Double Strike|Elemental Hit|Immortal Call|Dual Strike|Whirling Blades|Frenzy|Cold Snap|Raise Zombie|Detonate Dead|Caustic Arrow|Creeping Frost|Ice Shot|Static Strike|Split Arrow|Blood Rage|Righteous Fire|Discharge|Flicker Strike|Spark|Ice Spear|Raise Spectre|Infernal Blow|Glacial Hammer|Frost Wall|Freezing Pulse|Shock Nova|Viper Strike|Phase Run|Explosive Arrow|Temporal Chains|Elemental Weakness|Warlord's Mark|Punishment|Enfeeble|Assassin's Mark|Sniper's Mark|Despair|Lightning Warp|Summon Skeletons|Glacial Shield Swipe|Crushing Fist|Swordstorm|Heavy Strike|Dominating Blow|Rain of Arrows|Firestorm|Lightning Strike|Tempest Shield|Molten Shell|Power Siphon|Puncture|Lightning Arrow|Arc|Haste|Purity of Elements|Vitality|Discipline|Grace|Determination|Anger|Hatred|Wrath|Burning Arrow|Clarity|Shockwave Totem|Rejuvenation Totem|Conversion Trap|Bear Trap|Fire Trap|Decoy Totem|Devouring Totem|Ethereal Knives|Arctic Armour|Holy Flame Totem|Flammability|Frostbite|Conductivity|Incinerate|Cyclone|Searing Bond|Reave|Lightning Trap|Pyroclast Mine|Smoke Mine|Icicle Mine|Stormblast Mine|Animate Guardian|Spectral Throw|Animate Weapon|Purity of Fire|Purity of Ice|Purity of Lightning|Storm Call|Flameblast|Barrage|Ball Lightning|Summon Raging Spirit|Flame Surge|Desecrate|Flesh Offering|Bone Offering|Glacial Cascade|Convocation|Molten Strike|Tornado Shot|Herald of Ash|Herald of Ice|Herald of Thunder|Poacher's Mark|Lightning Tendrils|Mirror Arrow|Blink Arrow|Kinetic Blast|Summon Chaos Golem|Summon Ice Golem|Summon Flame Golem|Summon Lightning Golem|Ice Crash|Rallying Cry|Infernal Cry|Vigilant Strike|Rolling Magma|Flame Dash|Frost Blades|Wild Strike|Galvanic Arrow|Blast Rain|Bladefall|Siege Ballista|Blade Vortex|Contagion|Wither|Essence Drain|Ice Trap|Orb of Storms|Frost Bomb|Summon Stone Golem|Eviscerate|Snipe|Vengeful Cry|Earthquake|Sunder|Lacerate|Spirit Offering|Frostbolt|Vortex|Blight|Scorching Ray|Blade Flurry|Charged Dash|Dark Pact|Storm Burst|Cremation|Bodyswap|Volatile Dead|Unearth|Explosive Trap|Siphoning Trap|Flamethrower Trap|Lightning Spire Trap|Seismic Trap|Vulnerability|Tectonic Slam|Spectral Shield Throw|Herald of Purity|Herald of Agony|Consecrated Path|Smite|Scourge Arrow|Toxic Rain|Summon Holy Relic|Winter Orb|Storm Brand|Armageddon Brand|Brand Recall|War Banner|Dread Banner|Shattering Steel|Lancing Steel|Purifying Flame|Soulrend|Bane|Divine Ire|Wave of Conviction|Zealotry|Malevolence|Precision|Steelskin|Dash|Bladestorm|Blood and Sand|Berserk|Perforate|Chain Hook|Frostblink|Flesh and Stone|Pride|Cobra Lash|Withering Step|Venom Gyre|Summon Skitterbots|Plague Bearer|Pestilent Strike|Summon Carrion Golem|Artillery Ballista|Shrapnel Ballista|Ensnaring Arrow|Stormbind|Blade Blast|Spellslinger|Kinetic Bolt|Arcane Cloak|Intimidating Cry|Ancestral Cry|Seismic Cry|General's Cry|Arcanist Brand|Penance Brand|Wintertide Brand|Earthshatter|Sigil of Power|Splitting Steel|Flame Wall|Blazing Salvo|Crackling Lance|Void Sphere|Frost Shield|Hydrosphere|Hexblast|Exsanguinate|Corrupting Fever|Petrified Blood|Reap|Defiance Banner|Storm Rain|Rage Vortex|Shield Crush|Summon Reaper|Boneshatter|Ambush|Voltaxic Burst|Battlemage's Cry|Absolution|Eye of Winter|Spectral Helix|Forbidden Rite|Blade Trap|Manabond|Explosive Concoction|Poisonous Concoction|Temporal Rift|Energy Blade|Tornado|Soul Link|Flame Link|Intuitive Link|Protective Link|Vampiric Link|Destructive Link|Galvanic Field|Lightning Conduit|Alchemist's Mark|Frozen Legion|Volcanic Fissure|Automation|Autoexertion|Divine Retribution|Somatic Shell|Kinetic Fusillade|Kinetic Rain|Wall of Force|Conflagration|Thunderstorm",0,"+3 to Level of all # Gems",True 5,10,,0,"+#% to all Elemental Resistances",True 5,10,,0,"#% increased Reservation Efficiency of Skills",True 5,10,,0,"Items and Gems have #% reduced Attribute Requirements",True diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ThreadOfHope.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ThreadOfHope.csv index bdfce79c..63c37082 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ThreadOfHope.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data/ThreadOfHope.csv @@ -2,9 +2,9 @@ # Base Types: Crimson Jewel # Total modifiers on each item: 3 # Can have duplicate modifiers: False -# Modifier distrubution: "rest":3 +# Modifier distrubution: "rest":3 # Source: https://poedb.tw/Thread_of_Hope minRoll,maxRoll,textRolls,position,effect,static,unique ,,"Small|Medium|Large|Very Large|Massive",0,"Only affects Passives in # Ring",,True -,,,0,"Passives in Radius can be Allocated without being connected to your tree",True,True --20,-10,,0,"#% to all Elemental Resistances",,True \ No newline at end of file +,,,0,"Passive Skills in Radius can be Allocated without being connected to your tree Passage",True,True +-20,-10,,0,"#% to all Elemental Resistances",,True From 682f318548366305de0a449da498eba3feed65ca Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sat, 22 Nov 2025 16:55:41 +0100 Subject: [PATCH 05/18] #802 New carantene mod schemas and endpoints for handling it in api --- ...3_added_dynamicallycreated_to_modifiers.py | 30 +++++++++++ src/backend_api/app/api/api_message_util.py | 10 ++-- .../app/api/routes/carantene_modifier.py | 49 ++++++++++++++---- src/backend_api/app/api/routes/modifier.py | 51 +++++++++++++++++++ src/backend_api/app/core/models/models.py | 8 +-- src/backend_api/app/core/schemas/__init__.py | 2 + .../app/core/schemas/carantene_modifier.py | 4 ++ src/backend_api/app/core/schemas/modifier.py | 6 +++ src/backend_api/app/crud/base.py | 15 ++++-- .../model_exceptions/db_exception.py | 2 +- src/backend_api/scripts/prestart.sh | 1 + 11 files changed, 156 insertions(+), 22 deletions(-) create mode 100644 src/backend_api/app/alembic/versions/42c53ebbc633_added_dynamicallycreated_to_modifiers.py diff --git a/src/backend_api/app/alembic/versions/42c53ebbc633_added_dynamicallycreated_to_modifiers.py b/src/backend_api/app/alembic/versions/42c53ebbc633_added_dynamicallycreated_to_modifiers.py new file mode 100644 index 00000000..5f99b87f --- /dev/null +++ b/src/backend_api/app/alembic/versions/42c53ebbc633_added_dynamicallycreated_to_modifiers.py @@ -0,0 +1,30 @@ +"""Added dynamicallyCreated to modifiers + +Revision ID: 42c53ebbc633 +Revises: d789cc84cb4d +Create Date: 2025-11-22 15:47:08.893031 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '42c53ebbc633' +down_revision: Union[str, None] = 'd789cc84cb4d' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/src/backend_api/app/api/api_message_util.py b/src/backend_api/app/api/api_message_util.py index 3fb45c9a..ae9c359e 100644 --- a/src/backend_api/app/api/api_message_util.py +++ b/src/backend_api/app/api/api_message_util.py @@ -10,7 +10,7 @@ def get_delete_return_msg( model_table_name: str, - filter: dict[str, Any], + filter: dict[str, Any] | list[Any], ) -> Message: """Returns a message indicating the object was deleted successfully. @@ -23,9 +23,11 @@ def get_delete_return_msg( str: Message indicating the object was deleted successfully. """ - return Message( - message=f"{model_table_name} with filter {filter} was deleted successfully" - ) + if isinstance(filter, list): + message = f"{model_table_name} with total filter: {len(filter)} and 10 samples: {filter[:10]} was deleted successfully" + else: + message = f"{model_table_name} with filter {filter} was deleted successfully" + return Message(message=message) def get_user_active_change_msg(username: str, active: bool) -> Message: diff --git a/src/backend_api/app/api/routes/carantene_modifier.py b/src/backend_api/app/api/routes/carantene_modifier.py index 2a91975d..1dd212c4 100644 --- a/src/backend_api/app/api/routes/carantene_modifier.py +++ b/src/backend_api/app/api/routes/carantene_modifier.py @@ -13,7 +13,7 @@ get_db, ) from app.api.params import FilterParams -from app.core.models.models import Modifier +from app.core.models.models import CaranteneModifier from app.core.rate_limit.rate_limit_config import rate_limit_settings from app.core.rate_limit.rate_limiters import ( apply_user_rate_limits, @@ -28,7 +28,7 @@ @router.get( "/{caranteneModifierId}", - response_model=schemas.Modifier | list[schemas.Modifier], + response_model=schemas.CaranteneModifier | list[schemas.CaranteneModifier], dependencies=[Depends(get_current_active_user)], ) @apply_user_rate_limits( @@ -47,8 +47,6 @@ async def get_carantene_modifier( Get carantene modifier or list of carantene modifiers by key and value for "caranteneModifierId" - Dominant key is "caranteneModifierId". - Returns one or a list of carantene_modifiers. """ @@ -62,7 +60,7 @@ async def get_carantene_modifier( @router.get( "/", - response_model=schemas.Modifier | list[schemas.Modifier], + response_model=schemas.CaranteneModifier | list[schemas.CaranteneModifier], dependencies=[Depends(get_current_active_user)], ) @apply_user_rate_limits( @@ -92,11 +90,14 @@ async def get_all_carantene_modifiers( @router.post( "/", - response_model=schemas.ModifierCreate | list[schemas.ModifierCreate] | None, + response_model=schemas.CaranteneModifierCreate + | list[schemas.CaranteneModifierCreate] + | None, dependencies=[Depends(get_current_active_superuser)], ) async def create_carantene_modifier( - carantene_modifier: schemas.ModifierCreate | list[schemas.ModifierCreate], + carantene_modifier: schemas.CaranteneModifierCreate + | list[schemas.CaranteneModifierCreate], return_nothing: bool | None = None, db: Session = Depends(get_db), ): @@ -113,12 +114,12 @@ async def create_carantene_modifier( @router.put( "/", - response_model=schemas.Modifier, + response_model=schemas.CaranteneModifier, dependencies=[Depends(get_current_active_superuser)], ) async def update_carantene_modifier( caranteneModifierId: int, - carantene_modifier_update: schemas.ModifierUpdate, + carantene_modifier_update: schemas.CaranteneModifierUpdate, db: Session = Depends(get_db), ): """ @@ -161,5 +162,33 @@ async def delete_carantene_modifier( await CRUD_carantene_modifier.remove(db=db, filter=carantene_modifier_map) return get_delete_return_msg( - model_table_name=Modifier.__tablename__, filter=carantene_modifier_map + model_table_name=CaranteneModifier.__tablename__, filter=carantene_modifier_map + ).message + + +@router.delete( + "/bulk-delete/", + response_model=str, + dependencies=[Depends(get_current_active_superuser)], +) +async def bulk_delete_carantene_modifier( + caranteneModifierIds: list[schemas.CaranteneModifiersPK], + db: Session = Depends(get_db), +): + """ + Delete a list of carantene modifier by list of key "caranteneModifierId" and values. + + Returns a message that the carantene modifier was deleted. + """ + filter = [car_id.caranteneModifierId for car_id in caranteneModifierIds] + await CRUD_carantene_modifier.remove( + db=db, + filter=filter, + max_deletion_limit=99999999999, + deletion_key="caranteneModifierId", + ) + + return get_delete_return_msg( + model_table_name=CaranteneModifier.__tablename__, + filter=caranteneModifierIds, ).message diff --git a/src/backend_api/app/api/routes/modifier.py b/src/backend_api/app/api/routes/modifier.py index 2a55852e..074dc69f 100644 --- a/src/backend_api/app/api/routes/modifier.py +++ b/src/backend_api/app/api/routes/modifier.py @@ -187,3 +187,54 @@ async def delete_modifier( return get_delete_return_msg( model_table_name=Modifier.__tablename__, filter=modifier_map ).message + + +@router.put( + "/update-related-uniques/", + response_model=str, + dependencies=[Depends(get_current_active_superuser)], +) +async def update_related_unique_modifiers( + modifier_related_uniques_update: list[schemas.ModifierRelatedUniquesMap], + db: Session = Depends(get_db), +): + """ + Update a modifier by key and value for "modifierId" + + Dominant key is "modifierId". + + Returns the updated modifier. + """ + + for update_rel_modifier in modifier_related_uniques_update: + modifier_map = {"modifierId": update_rel_modifier.modifierId} + + modifier = await CRUD_modifier.get( + db=db, + filter=modifier_map, + ) + assert not isinstance(modifier, list) and modifier is not None + + update_modifier = schemas.ModifierUpdate( + position=modifier.position, + relatedUniques=update_rel_modifier.relatedUniques, + minRoll=modifier.minRoll, + maxRoll=modifier.maxRoll, + textRolls=modifier.textRolls, + static=modifier.static, + effect=modifier.effect, + regex=modifier.regex, + implicit=modifier.implicit, + explicit=modifier.explicit, + delve=modifier.delve, + fractured=modifier.fractured, + synthesised=modifier.synthesised, + unique=modifier.unique, + corrupted=modifier.corrupted, + enchanted=modifier.enchanted, + veiled=modifier.veiled, + dynamicallyCreated=modifier.dynamicallyCreated, + ) + + await CRUD_modifier.update(db_obj=modifier, obj_in=update_modifier, db=db) + return f"Updated related uniques for count={len(modifier_related_uniques_update)} modifiers" diff --git a/src/backend_api/app/core/models/models.py b/src/backend_api/app/core/models/models.py index ab759cbc..d227c205 100644 --- a/src/backend_api/app/core/models/models.py +++ b/src/backend_api/app/core/models/models.py @@ -160,11 +160,11 @@ class Modifier(Base): corrupted: Mapped[bool | None] = mapped_column(Boolean) enchanted: Mapped[bool | None] = mapped_column(Boolean) veiled: Mapped[bool | None] = mapped_column(Boolean) - # mutated: Mapped[bool | None] = mapped_column(Boolean) + mutated: Mapped[bool | None] = mapped_column(Boolean) static: Mapped[bool | None] = mapped_column(Boolean) - # dynamicallyCreated: Mapped[bool | None] = mapped_column( - # Boolean - # ) # Modifier got created during extract of items + dynamicallyCreated: Mapped[bool | None] = mapped_column( + Boolean + ) # Modifier got created during ETL explicit: Mapped[bool | None] = mapped_column(Boolean) effect: Mapped[str] = mapped_column(Text, nullable=False) relatedUniques: Mapped[str | None] = mapped_column(Text) diff --git a/src/backend_api/app/core/schemas/__init__.py b/src/backend_api/app/core/schemas/__init__.py index 3a927596..c35fb8a4 100644 --- a/src/backend_api/app/core/schemas/__init__.py +++ b/src/backend_api/app/core/schemas/__init__.py @@ -7,12 +7,14 @@ ModifierInDB, ModifierCreate, ModifierUpdate, + ModifierRelatedUniquesMap, ) from .carantene_modifier import ( CaranteneModifier, CaranteneModifierInDB, CaranteneModifierCreate, CaranteneModifierUpdate, + CaranteneModifiersPK, ) from .item_base_type import ( ItemBaseType, diff --git a/src/backend_api/app/core/schemas/carantene_modifier.py b/src/backend_api/app/core/schemas/carantene_modifier.py index cf5d6831..7d07eaf8 100644 --- a/src/backend_api/app/core/schemas/carantene_modifier.py +++ b/src/backend_api/app/core/schemas/carantene_modifier.py @@ -46,3 +46,7 @@ class CaranteneModifier(CaranteneModifierInDBBase): # Properties stored in DB class CaranteneModifierInDB(CaranteneModifierInDBBase): pass + + +class CaranteneModifiersPK(_pydantic.BaseModel): + caranteneModifierId: int diff --git a/src/backend_api/app/core/schemas/modifier.py b/src/backend_api/app/core/schemas/modifier.py index 99976443..d5726008 100644 --- a/src/backend_api/app/core/schemas/modifier.py +++ b/src/backend_api/app/core/schemas/modifier.py @@ -24,6 +24,7 @@ class _BaseModifier(_pydantic.BaseModel): corrupted: bool | None = None enchanted: bool | None = None veiled: bool | None = None + dynamicallyCreated: bool | None = None class GroupedModifierProperties(_pydantic.BaseModel): @@ -64,3 +65,8 @@ class Modifier(ModifierInDBBase): # Properties stored in DB class ModifierInDB(ModifierInDBBase): pass + + +class ModifierRelatedUniquesMap(_pydantic.BaseModel): + modifierId: int + relatedUniques: str diff --git a/src/backend_api/app/crud/base.py b/src/backend_api/app/crud/base.py index f243bcdd..8a6c8471 100644 --- a/src/backend_api/app/crud/base.py +++ b/src/backend_api/app/crud/base.py @@ -342,12 +342,19 @@ async def remove( self, db: Session, *, - filter: Any, + filter: dict[str, Any] | list[Any], sort_key: str | None = None, sort_method: Literal["asc", "dec"] | None = None, max_deletion_limit: int = 12, + deletion_key: str | None = None, ) -> ModelType: - db_objs = db.query(self.model).filter_by(**filter).all() + if isinstance(filter, list) and deletion_key is not None: + deletion_key_model = getattr(self.model, deletion_key, None) + if deletion_key_model is None: + raise ValueError("Couldn't find deletion key in model") + db_objs = db.query(self.model).filter(deletion_key_model.in_(filter)).all() + else: + db_objs = db.query(self.model).filter_by(**filter).all() if not db_objs: raise DbObjectDoesNotExistError( model_table_name=self.model.__tablename__, @@ -369,7 +376,9 @@ async def remove( db_objs = db_objs[0] db.delete(db_objs) else: - db_objs = self._sort_objects(db_objs, key=sort_key, sort_method=sort_method) + db_objs = self._sort_objects( + db_objs, sort_key=sort_key, sort_method=sort_method + ) [db.delete(obj) for obj in db_objs] db.commit() return self.validate(db_objs) diff --git a/src/backend_api/app/exceptions/model_exceptions/db_exception.py b/src/backend_api/app/exceptions/model_exceptions/db_exception.py index c83119ae..547c4e4b 100644 --- a/src/backend_api/app/exceptions/model_exceptions/db_exception.py +++ b/src/backend_api/app/exceptions/model_exceptions/db_exception.py @@ -107,7 +107,7 @@ def __init__( self, *, model_table_name: str, - filter: dict[str, Any] | None = None, + filter: dict[str, Any] | list[dict[str, Any]] | None = None, function_name: str | None = "Unknown function", class_name: str | None = None, status_code: int | None = status.HTTP_404_NOT_FOUND, diff --git a/src/backend_api/scripts/prestart.sh b/src/backend_api/scripts/prestart.sh index 03cd269b..47131a04 100644 --- a/src/backend_api/scripts/prestart.sh +++ b/src/backend_api/scripts/prestart.sh @@ -12,4 +12,5 @@ alembic upgrade head # Create initial data in DB python /app/app/initial_data.py + fastapi run --reload "app/main.py" From 687047adab215bae9a1a9c1d794ba2656c4ffb76 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sat, 22 Nov 2025 16:56:13 +0100 Subject: [PATCH 06/18] #802 Carantene modifier implementation in data retrieval --- .../data_retrieval_app/data_deposit/main.py | 16 +- .../modifier/modifier_data_depositor.py | 1 - .../external_data_retrieval/config.py | 7 +- .../detectors/unique_detector.py | 17 +- .../transforming_data/roll_processor.py | 167 ++++++++++++++++-- .../transform_poe_api_data.py | 26 ++- .../data_retrieval_app/utils.py | 103 ++++++++++- 7 files changed, 292 insertions(+), 45 deletions(-) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py index d5e5490a..4debeda0 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py @@ -4,9 +4,13 @@ from data_retrieval_app.data_deposit.item_base_type.item_base_type_data_depositor import ( ItemBaseTypeDataDepositor, ) +from data_retrieval_app.data_deposit.modifier.carantene_modifier_processor import ( + check_carantene_modifiers, +) from data_retrieval_app.data_deposit.modifier.modifier_data_depositor import ( ModifierDataDepositor, ) +from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.logs.logger import data_deposit_logger as logger from data_retrieval_app.logs.logger import setup_logging @@ -15,12 +19,16 @@ def main(): setup_logging() logger.info("Starting deposit phase.") data_depositors: dict[Literal["modifier", "itemBaseType"], DataDepositorBase] = { - "modifer": ModifierDataDepositor(), + "modifier": ModifierDataDepositor(), "itemBaseType": ItemBaseTypeDataDepositor(), } - for key, data_depositor in data_depositors.items(): - logger.info(f"Depositing {key} data.") - data_depositor.deposit_data() + if settings.LOAD_INITIAL_DATA: + for key, data_depositor in data_depositors.items(): + logger.info(f"Depositing {key} data.") + data_depositor.deposit_data() + logger.info("Checking carantene modifiers") + check_carantene_modifiers() + logger.info("Finished checking carantene modifiers") return 0 diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data_depositor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data_depositor.py index 1728c95c..de8fc49d 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data_depositor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data_depositor.py @@ -183,7 +183,6 @@ def _remove_duplicates(self, new_modifiers_df: pd.DataFrame) -> pd.DataFrame: .str.lower() .isin(current_modifiers_df["effect"].str.lower()) ) - duplicate_df = new_modifiers_df.loc[duplicate_mask].copy() self._update_duplicates(duplicate_df, current_modifiers_df) non_duplicate_df = new_modifiers_df.loc[~duplicate_mask].copy() diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py index 3f47e30c..d224e55a 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py @@ -28,22 +28,27 @@ def BACKEND_BASE_URL(self) -> HttpUrl: FIRST_SUPERUSER: str FIRST_SUPERUSER_PASSWORD: str CURRENT_SOFTCORE_LEAGUE: str + @computed_field # type: ignore[prop-decorator] @property def CURRENT_HARDCORE_LEAGUE(self) -> str: return f"Hardcore {self.CURRENT_SOFTCORE_LEAGUE}" + POE_PUBLIC_STASHES_AUTH_TOKEN: str OAUTH_CLIENT_ID: str OAUTH_CLIENT_SECRET: str MINI_BATCH_SIZE: int = 30 - N_CHECKPOINTS_PER_TRANSFORMATION: int = 10 + N_CHECKPOINTS_PER_TRANSFORMATION: int = 1 TIME_BETWEEN_RESTART: int = 3600 MAX_TIME_PER_MINI_BATCH: int = 3 * 60 LEAGUE_LAUNCH_TIME: str + LOAD_INITIAL_DATA: bool = False + CHECK_CARANTENE_MODIFIERS: bool = True + @computed_field # type: ignore[prop-decorator] @property def LEAGUE_LAUNCH_DATETIME_OBJECT(self) -> datetime: diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py index 7cc990b2..c4ccbc9e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py @@ -1,6 +1,7 @@ import pandas as pd from data_retrieval_app.external_data_retrieval.detectors.base import DetectorBase +from data_retrieval_app.logs.logger import main_logger as logger class UniqueDetector(DetectorBase): @@ -27,18 +28,18 @@ def _specialized_filter(self, df: pd.DataFrame) -> pd.DataFrame: class UniqueFoulbornDetector(UniqueDetector): def _check_if_wanted(self, df: pd.DataFrame) -> pd.DataFrame: - """ - Uses the icon to identify which unique it is, then saving that name. - If the name attribute still has a length of 0 it means no matching unique - was found. - """ if "mutated" not in df.columns: + logger.error("MUTATED NOT IN") return pd.DataFrame(columns=df.columns) - df = df.loc[df["mutated"]] + df_filtered = df[df["mutated"].astype(str) == "True"].loc[ + df["name"].str.len() != 0 + ] - df = df.loc[df["name"].str.len() != 0] - return df + return df_filtered + + def __str__(self): + return "Unique Foulborn Detector" class UniqueUnidentifiedDetector(UniqueDetector): diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/roll_processor.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/roll_processor.py index a7a63989..1bf620b9 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/roll_processor.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/roll_processor.py @@ -1,12 +1,52 @@ import re import pandas as pd +from pydantic import BaseModel, HttpUrl +from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.logs.logger import transform_logger as logger +from data_retrieval_app.utils import bulk_update_data, insert_data pd.set_option("display.max_colwidth", None) +class ModifierSchema(BaseModel): + modifierId: int + position: int + minRoll: int + maxRoll: int + implicit: bool + explicit: bool + delve: bool + fractured: bool + synthesised: bool + unique: bool + corrupted: bool + enchanted: bool + veiled: bool + static: bool + effect: str + relatedUniques: str + textRolls: str + regex: str + dynamicallyCreated: bool + + +class CaranteneModifierSchema(BaseModel): + effect: str + relatedUnique: str + implicit: str + explicit: str + delve: str + fractured: str + synthesised: str + unique: str + corrupted: str + enchanted: str + veiled: str + mutated: str + + class RollProcessor: @property def modifier_df(self) -> pd.DataFrame: @@ -39,6 +79,37 @@ def _pre_processing(self, df: pd.DataFrame) -> pd.DataFrame: return df + def _update_related_unique_modifier(self, item_modifier_df: pd.DataFrame) -> None: + if "relatedUniques" not in item_modifier_df.columns: + item_modifier_df["relatedUniques"] = "" + item_modifier_df["relatedUniques"] = ( + item_modifier_df["relatedUniques"].fillna("").astype(str) + ) + + item_modifier_df["related_unique_contains"] = item_modifier_df.apply( + lambda row: row["name"] in row["relatedUniques"].split("|"), axis=1 + ) + + item_modifier_df["relatedUniques"] = item_modifier_df.apply( + lambda row: f"{row['relatedUniques']}|{row['name']}" + if not row["related_unique_contains"] and pd.notna(row["name"]) + else row["relatedUniques"], + axis=1, + ) + + modifier_cols = ModifierSchema.model_fields.keys() + + new_modifiers = item_modifier_df[modifier_cols] + logger.info( + f"Updating new modifiers related uniques, count: \n {len(new_modifiers)}" + ) + + bulk_update_data( + new_modifiers, + table_name="modifier", + sub_endpoint="update-related-uniques", + ) + def _process_static( self, df: pd.DataFrame, static_modifers_mask: pd.Series ) -> pd.DataFrame: @@ -59,6 +130,7 @@ def _process_static( merged_static_df = static_df.merge( static_modifier_df, on=["effect", "position"], how="left" ) + failed_df = merged_static_df.loc[merged_static_df["static"].isna()] if not failed_df.empty: @@ -69,9 +141,13 @@ def _process_static( # NOTE this should never happen merged_static_df = merged_static_df.loc[~merged_static_df["static"].isna()] + self._update_related_unique_modifier(merged_static_df) + return merged_static_df - def _get_rolls(self, dynamic_df: pd.DataFrame) -> pd.DataFrame: + def _get_rolls( + self, dynamic_df: pd.DataFrame + ) -> tuple[pd.DataFrame, pd.DataFrame | None]: """ Uses regex matching groups to extract the rolls and adds the correct effect. @@ -94,9 +170,14 @@ def extract_rolls(matchobj: re.Match) -> str: for effect, regex in dynamic_modifier_df[["effect", "regex"]].itertuples( index=False ): - matched_modifiers = dynamic_df["modifier"].str.replace( - regex, extract_rolls, regex=True, case=False - ) + try: + matched_modifiers = dynamic_df["modifier"].str.replace( + regex, extract_rolls, regex=True, case=False + ) + except Exception as e: + raise Exception( + f"Found unprocessable regex pattern: {regex} \n for effect: {effect} \n error: {e}" + ) matched_modifiers_mask = matched_modifiers.str.contains("matched", na=False) dynamic_w_rolls_df.loc[matched_modifiers_mask, "effect"] = effect @@ -115,19 +196,60 @@ def extract_rolls(matchobj: re.Match) -> str: # If there are rows in the dataframe which contain empty lists, something has failed failed_df = dynamic_df.loc[dynamic_df["roll"].isna()] if not failed_df.empty: - logger.critical( + logger.warning( "Failed to add rolls to listed modifiers, this likely means" - " the modifier is legacy or there was a new expansion." + " the modifier is carantene, legacy or there was a new expansion." ) - logger.critical( + logger.warning( f"These items have missing modifiers: {failed_df['name'].unique().tolist()}" ) - logger.critical( - f"These modifiers were not present in the database: {failed_df['effect'].unique().tolist()}" + modifiers_failed = failed_df["effect"].unique().tolist() + logger.warning( + f"These first 15 modifiers of total {len(modifiers_failed)} were not present in the database: {modifiers_failed[:16]}" ) dynamic_df = dynamic_df.loc[~dynamic_df["roll"].isna()] - return dynamic_df + return dynamic_df, failed_df if not failed_df.empty else None + + def insert_carantene_modifiers(self, item_df: pd.DataFrame) -> None: + # Currently only create carantene mods from mutated items + mutated_item_df = item_df[item_df["mutated"].astype(str) == "True"] + if mutated_item_df.empty: + return None + + def create_carantene_modifiers(item_df: pd.DataFrame) -> pd.DataFrame: + "Build a DataFrame of carantene modifiers from item data." + + base = item_df.loc[:, ["effect", "name", "mutated"]].copy() + + modifiers = base.assign( + relatedUnique=lambda df: df["name"], + explicit=True, + unique=lambda df: df["name"].notna() & (df["name"] != ""), + ).reset_index(drop=True) + + return modifiers + + carantene_modifier_cols = CaranteneModifierSchema.model_fields.keys() + carantene_modifiers = create_carantene_modifiers(mutated_item_df) + + assert isinstance(carantene_modifiers, pd.DataFrame) + + existing_cols = [ + col for col in carantene_modifier_cols if col in carantene_modifiers.columns + ] + carantene_modifiers = carantene_modifiers[existing_cols] + + logger.info( + f"Found {len(carantene_modifiers)} to carantene modifiers, inserting to db..." + ) + + insert_data( + carantene_modifiers, + url=HttpUrl(settings.BACKEND_BASE_URL), + table_name="carantene_modifier", + logger=logger, + ) def _process_dynamic( self, df: pd.DataFrame, static_modifers_mask: pd.Series @@ -140,11 +262,13 @@ def _process_dynamic( dynamic_modifier_df = self.dynamic_modifier_df dynamic_df = df.loc[~static_modifers_mask] # Everything not static is dynamic if dynamic_df.empty: - raise Exception("ASDHSDHASDHHSADDSHAD") + return pd.DataFrame( + columns=dynamic_df.columns.append(dynamic_modifier_df.columns) + ) dynamic_df.loc[:, "effect"] = dynamic_df.loc[:, "modifier"] - dynamic_df = self._get_rolls(dynamic_df.copy()) + dynamic_df, failed_dynamic_df = self._get_rolls(dynamic_df.copy()) # Creates a column for position, which contains a list of numerical strings dynamic_df.loc[:, "position"] = dynamic_df.loc[:, "roll"].apply( @@ -159,15 +283,22 @@ def _process_dynamic( ) # If all of these fields are still NA, it means that modifier was not matched with a modifier in our DB - failed_df = merged_dynamic_df.loc[merged_dynamic_df["roll"].isna()] - if not failed_df.empty: - logger.exception( - "Some modifiers did not find their counterpart in the database." - " This likely means the modifier is new or has been reworded.\n" - f"{failed_df[['effect', 'roll']].to_string()}" + if failed_dynamic_df is not None: + # logger.info( + # "Some modifiers did not find their counterpart in the database." + # " This likely means the modifier is new or has been reworded.\n" + # f"{non_matched_modifier_df[['effect', 'roll']].to_string()}" + # ) + logger.info( + f"Checking carantene modifiers from non matched modifiers, count={len(failed_dynamic_df)}" ) + + self.insert_carantene_modifiers(failed_dynamic_df) + merged_dynamic_df = merged_dynamic_df.loc[~merged_dynamic_df["roll"].isna()] + self._update_related_unique_modifier(merged_dynamic_df) + def convert_text_roll_to_index(row: pd.DataFrame) -> int: text_rolls: str = row["textRolls"] if text_rolls != "None": diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py index 0d9476a8..6357b8de 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py @@ -103,8 +103,20 @@ def _transform_item_table( represented by `price` and `b/o` respectively. """ - def transform_base_types(element): - return item_base_types[element] + missing_base_type_rows = [] + + def transform_base_types(row): + base_type = row["baseType"] + if base_type not in item_base_types: + log_row = ( + "name", + row["name"], + "baseType", + base_type, + ) + missing_base_type_rows.append(log_row) + return None + return item_base_types[base_type] def get_currency_amount(element): if len(element) == 3: @@ -128,8 +140,12 @@ def transform_influences(row: pd.DataFrame, influence_columns: list[str]): ] = True return influence_dict - base_type_series = item_df["baseType"] - item_df["itemBaseTypeId"] = base_type_series.apply(transform_base_types) + item_df["itemBaseTypeId"] = item_df.apply(transform_base_types, axis=1) + if missing_base_type_rows: + logger.critical( + f"Missing base types registered: {set(missing_base_type_rows)}" + ) + raise ValueError("Missing base types registered") influence_columns = [ column for column in item_df.columns if "influences" in column @@ -427,7 +443,7 @@ def _create_item_modifier_table( A similiar process to creating the item table, only this time the relevant column contains a list and not a JSON-object """ - item_modifier_columns = ["name", "explicitMods"] + item_modifier_columns = ["name", "explicitMods", "mutated"] item_modifier_df = df.loc[ self.price_found_mask, item_modifier_columns, diff --git a/src/backend_data_retrieval/data_retrieval_app/utils.py b/src/backend_data_retrieval/data_retrieval_app/utils.py index e45cbc0e..69586440 100644 --- a/src/backend_data_retrieval/data_retrieval_app/utils.py +++ b/src/backend_data_retrieval/data_retrieval_app/utils.py @@ -1,7 +1,7 @@ import logging from collections.abc import Generator from datetime import UTC, datetime -from typing import Any +from typing import Any, Literal, overload import pandas as pd import requests @@ -9,6 +9,7 @@ from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.logs.logger import main_logger as logger +from data_retrieval_app.pom_api_authentication import get_superuser_token_headers def _chunks(lst: list[Any], n: int) -> Generator[Any, None, None]: @@ -27,6 +28,20 @@ def remove_empty_fields(json_in: list[dict[str, str]]) -> list[dict[str, Any]]: return json_out +@overload +def df_to_JSON( + df: pd.DataFrame | pd.Series, request_method: Literal["post"] +) -> list[dict[str, Any]]: + ... + + +@overload +def df_to_JSON( + df: pd.DataFrame | pd.Series, request_method: Literal["put"] +) -> dict[str, Any]: + ... + + def df_to_JSON( df: pd.DataFrame | pd.Series, request_method: str ) -> list[dict[str, Any]] | dict[str, Any] | Any: @@ -77,6 +92,7 @@ def insert_data( logger: logging.Logger, on_duplicate_pkey_do_nothing: bool = False, headers: dict[str, str] | None = None, + method: Literal["post", "put"] = "post", ) -> None: logger.debug("Inserting data into database.") if df.empty: @@ -87,6 +103,8 @@ def insert_data( if on_duplicate_pkey_do_nothing: params["on_duplicate_pkey_do_nothing"] = True logger.debug("Sending data to database.") + if headers is None: + headers = get_superuser_token_headers(url) response = requests.post( f"{url}/{table_name}/", json=data, headers=headers, params=params ) @@ -96,20 +114,24 @@ def insert_data( f"Recieved a 422 response, indicating an unprocessable entity was submitted, while posting a {table_name} table.\nSending smaller batches, trying to locate specific error." ) for data_chunk in _chunks(data, n=15): - response = requests.post( - f"{url}/{table_name}/", json=data_chunk, headers=headers - ) + if method == "post": + response = requests.post( + f"{url}/{table_name}/", json=data_chunk, headers=headers + ) if response.status_code == 422: logger.warning( "Located chunk of data that contains the unprocessable entity." ) for individual_data in data_chunk: - response = requests.post( - f"{url}/{table_name}/", json=individual_data, headers=headers - ) + if method == "post": + response = requests.post( + f"{url}/{table_name}/", + json=individual_data, + headers=headers, + ) if response.status_code == 422: logger.warning( - "Located the unprocessable entity:\n", individual_data + f"Located the unprocessable entity: {individual_data}\n", ) elif response.status_code == 500: logger.exception( @@ -122,3 +144,68 @@ def insert_data( f"Recieved a {response.status_code} response. Error msg: {response.text[:10000]}" ) response.raise_for_status() + + +def bulk_update_data( + df: pd.DataFrame, + *, + table_name: str, + sub_endpoint: str, # formatted: `name`/ +) -> None: + update_dicts = df_to_JSON(df, request_method="post") + if not isinstance(update_dicts, list): + update_dicts = [update_dicts] + pom_api_headers = get_superuser_token_headers(settings.BACKEND_BASE_URL) + + try: + sub_endpoint = sub_endpoint or "" + response = requests.put( + f"{settings.BACKEND_BASE_URL}/{table_name}/{sub_endpoint}/", + json=update_dicts, + headers=pom_api_headers, + ) + response.raise_for_status() + + logger.debug( + f"Updated total len data: {len(update_dicts)} \n 5 samples: {update_dicts[:5]} \n in endpoint {table_name}" + ) + + except requests.HTTPError as e: + logger.warning( + f"Couldn't update the data in endpoint {table_name}, error: {e.response.status_code} {e.response.json()}" + ) + except KeyError: + unique_keys = { + key for update_dict in update_dicts for key in update_dict.keys() + } + logger.warning( + f"Key in `log_cols` not in `update_dict`'s keys, update keys: {unique_keys}" + ) + + except Exception as e: + logger.warning(f"Couldn't update the data in endpoint {table_name}, error: {e}") + + +def bulk_delete_data( + primary_key: str, + primary_key_values: list[Any], + table_name: str, +) -> None: + delete_url = f"{settings.BACKEND_BASE_URL}/{table_name}/bulk-delete/" + pom_api_headers = get_superuser_token_headers(settings.BACKEND_BASE_URL) + data = [{primary_key: val} for val in primary_key_values] + try: + response = requests.delete( + delete_url, + json=data, + headers=pom_api_headers, + ) + response.raise_for_status() + except requests.HTTPError as e: + logger.warning( + f"Couldn't delete the data:\n {primary_key}: {primary_key_values[:50]} ... \n in endpoint {table_name}, error: {e.response.status_code} {e.response.json()}" + ) + except Exception as e: + logger.warning( + f"Couldn't delete the data:\n {primary_key}: {primary_key_values[:50]} ... \n in endpoint {table_name}, error: {e}" + ) From d6e817dd4a232ad0b93ca066ea356419a0f7b5bc Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sat, 22 Nov 2025 16:56:43 +0100 Subject: [PATCH 07/18] #802 updated grafana schemas --- src/grafana/dashboards/backend-api.json | 47 +++++++++++-------- .../dashboards/backend-data-retrieval.json | 44 +++++++++-------- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/grafana/dashboards/backend-api.json b/src/grafana/dashboards/backend-api.json index 36430e4a..1024bd1d 100644 --- a/src/grafana/dashboards/backend-api.json +++ b/src/grafana/dashboards/backend-api.json @@ -18,7 +18,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 2, + "id": 1, "links": [], "panels": [ { @@ -55,6 +55,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -69,7 +70,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": 0 }, { "color": "red", @@ -100,7 +102,7 @@ "sort": "none" } }, - "pluginVersion": "12.0.1+security-01", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -164,6 +166,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -179,7 +182,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": 0 }, { "color": "red", @@ -211,7 +215,7 @@ "sort": "none" } }, - "pluginVersion": "12.0.1+security-01", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -275,6 +279,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -289,7 +294,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": 0 }, { "color": "red", @@ -320,7 +326,7 @@ "sort": "none" } }, - "pluginVersion": "12.0.1+security-01", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -417,7 +423,7 @@ "sortOrder": "Ascending", "wrapLogMessage": false }, - "pluginVersion": "12.0.1+security-01", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -443,10 +449,10 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 12, + "h": 25, + "w": 24, "x": 0, - "y": 16 + "y": 26 }, "id": 5, "options": { @@ -456,17 +462,18 @@ "prettifyLogMessage": false, "showCommonLabels": false, "showLabels": false, - "showTime": false, + "showTime": true, "sortOrder": "Descending", "wrapLogMessage": false }, - "pluginVersion": "12.0.1+security-01", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { "type": "loki", "uid": "pomodifiers-loki" }, + "direction": "backward", "editorMode": "code", "expr": "{job=\"vector\"} |= \"src-backend-1\" != \"INFO\" or `0.0.0.0:80` | json | keep level, request_host, request_method, request_process_time_ms, request_status_code, request_url, message | line_format \"{{.level}} {{.request_host}} {{.request_method}} {{.request_url}} {{.request_status_code}} {{.request_process_time}} {{.message}}\" | request_status_code != \"0\"", "queryType": "range", @@ -510,6 +517,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -524,7 +532,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": 0 }, { "color": "red", @@ -540,7 +549,7 @@ "h": 8, "w": 12, "x": 0, - "y": 24 + "y": 51 }, "id": 4, "options": { @@ -556,7 +565,7 @@ "sort": "none" } }, - "pluginVersion": "12.0.1+security-01", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -599,7 +608,7 @@ "h": 16, "w": 24, "x": 0, - "y": 32 + "y": 59 }, "id": 8, "options": { @@ -613,7 +622,7 @@ "sortOrder": "Ascending", "wrapLogMessage": true }, - "pluginVersion": "12.0.1+security-01", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -632,7 +641,7 @@ } ], "preload": false, - "schemaVersion": 41, + "schemaVersion": 42, "tags": [], "templating": { "list": [] diff --git a/src/grafana/dashboards/backend-data-retrieval.json b/src/grafana/dashboards/backend-data-retrieval.json index 1bf866cf..11ad407c 100644 --- a/src/grafana/dashboards/backend-data-retrieval.json +++ b/src/grafana/dashboards/backend-data-retrieval.json @@ -18,7 +18,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 26, + "id": 2, "links": [], "panels": [ { @@ -55,6 +55,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -70,7 +71,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -97,11 +98,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, - "pluginVersion": "11.4.0-79146", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -165,6 +167,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -180,7 +183,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -207,11 +210,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, - "pluginVersion": "11.4.0-79146", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -251,14 +255,15 @@ "overrides": [] }, "gridPos": { - "h": 18, - "w": 19, + "h": 19, + "w": 24, "x": 0, "y": 8 }, "id": 2, "options": { "dedupStrategy": "none", + "enableInfiniteScrolling": false, "enableLogDetails": true, "prettifyLogMessage": false, "showCommonLabels": false, @@ -267,7 +272,7 @@ "sortOrder": "Ascending", "wrapLogMessage": false }, - "pluginVersion": "11.4.0-79146", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -292,14 +297,15 @@ "overrides": [] }, "gridPos": { - "h": 24, - "w": 19, + "h": 25, + "w": 24, "x": 0, - "y": 26 + "y": 27 }, "id": 1, "options": { "dedupStrategy": "none", + "enableInfiniteScrolling": false, "enableLogDetails": true, "prettifyLogMessage": false, "showCommonLabels": false, @@ -308,7 +314,7 @@ "sortOrder": "Ascending", "wrapLogMessage": false }, - "pluginVersion": "11.4.0-79146", + "pluginVersion": "12.2.0", "targets": [ { "direction": "backward", @@ -332,14 +338,15 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 12, + "h": 16, + "w": 24, "x": 0, - "y": 50 + "y": 52 }, "id": 5, "options": { "dedupStrategy": "none", + "enableInfiniteScrolling": false, "enableLogDetails": true, "prettifyLogMessage": false, "showCommonLabels": false, @@ -348,7 +355,7 @@ "sortOrder": "Ascending", "wrapLogMessage": true }, - "pluginVersion": "11.4.0-79146", + "pluginVersion": "12.2.0", "targets": [ { "datasource": { @@ -366,7 +373,7 @@ } ], "preload": false, - "schemaVersion": 40, + "schemaVersion": 42, "tags": [], "templating": { "list": [] @@ -379,6 +386,5 @@ "timezone": "browser", "title": "Backend Data Retrieval", "uid": "fe4c6i7cxq5moc", - "version": 25, - "weekStart": "" + "version": 1 } From 9a3c3c8e0f298d78b985bbeb818850b816cc0569 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sat, 22 Nov 2025 16:57:01 +0100 Subject: [PATCH 08/18] #802 Belts and quivers base types --- .../item_base_type/item_base_type_data/Belts.csv | 8 ++++++++ .../item_base_type/item_base_type_data/Quivers.csv | 6 ++++++ 2 files changed, 14 insertions(+) create mode 100644 src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Belts.csv create mode 100644 src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Quivers.csv diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Belts.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Belts.csv new file mode 100644 index 00000000..00e2f043 --- /dev/null +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Belts.csv @@ -0,0 +1,8 @@ +# Source: NA +# Note: Only contains bases relevant to the uniques we are tracking +baseType,category,subCategory,relatedUniques +"Chain Belt","belt","belt", +"Heavy Belt","belt","belt", +"Leather Belt","belt","belt", +"Cloth Belt","belt","belt", + diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Quivers.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Quivers.csv new file mode 100644 index 00000000..3a5640a6 --- /dev/null +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Quivers.csv @@ -0,0 +1,6 @@ +# Source: NA +# Note: Only contains bases relevant to the uniques we are tracking +baseType,category,subCategory,relatedUniques +"Penetrating Arrow Quiver","quiver","quiver" +"Blunt Arrow Quiver","quiver","quiver" +"Spike-Point Arrow Quiver","quiver","quiver" From 64909fe52ccb6c61b806020f5ba328dc9be67134 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sat, 22 Nov 2025 17:00:19 +0100 Subject: [PATCH 09/18] #802 Carantene modifier processor --- .../modifier/carantene_modifier_processor.py | 554 ++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py new file mode 100644 index 00000000..6fa80103 --- /dev/null +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py @@ -0,0 +1,554 @@ +import re +from difflib import SequenceMatcher +from typing import Any + +import pandas as pd +import requests + +from data_retrieval_app.external_data_retrieval.config import settings +from data_retrieval_app.logs.logger import data_deposit_logger as logger +from data_retrieval_app.pom_api_authentication import get_superuser_token_headers +from data_retrieval_app.utils import bulk_delete_data, insert_data + +NUM_PATTERN = re.compile(r"-?\d+(?:\.\d+)?") + + +class EmptyCarModDF(Exception): + def __init__(self, message="Carantene modifier dataframe is empty", value=None): + self.message = message + self.value = value + super().__init__( + f"{self.message}: {self.value}" if self.value is not None else self.message + ) + + +def _tokenize(s: str) -> list[str]: + return s.split() + + +def _lcs_len(a: list[str], b: list[str]) -> float: + n, m = len(a), len(b) + dp = [0] * (m + 1) + for i in range(1, n + 1): + prev = 0 + ai = a[i - 1] + for j in range(1, m + 1): + cur = dp[j] + if ai == b[j - 1]: + dp[j] = prev + 1 + else: + dp[j] = max(dp[j], dp[j - 1]) + prev = cur + return dp[m] + + +def _diff_chunks(a: list[str], b: list[str]) -> list[tuple[str, str, int]]: + "Returns [(text_roll_1, text_roll_2, position), (..)] for different tokens between `a` and `b`" + sm = SequenceMatcher(a=a, b=b, autojunk=False) + diffs = [] + for tag, i1, i2, j1, j2 in sm.get_opcodes(): + if tag == "equal": + continue + base_chunk = " ".join(a[i1:i2]).strip() + other_chunk = " ".join(b[j1:j2]).strip() + if base_chunk and other_chunk: + diffs.append((base_chunk, other_chunk, i1)) + return diffs + + +def _build_text_rolls(df: pd.DataFrame, min_overlap=0.8, min_words=4, min_count=10): + dfx = df.copy() + dfx["relatedUnique"] = dfx["relatedUnique"].apply( + lambda x: x if isinstance(x, list) else [x] + ) + dfx = dfx.explode("relatedUnique", ignore_index=True) + dfx["textRolls"] = [[] for _ in range(len(dfx))] + + counts = dfx.groupby(["relatedUnique", "effect"])["relatedUnique"].transform("size") + dfx = dfx[counts >= min_count].copy().reset_index(drop=True) + + if dfx.empty: + raise EmptyCarModDF + + for _, g_df in dfx.groupby("relatedUnique", sort=False): + idxs: list[int] = g_df.index.tolist() + effects_token_map: dict[int, list[str]] = { + i: _tokenize(dfx.at[i, "effect"]) for i in idxs + } + for i in idxs: + + def skippable_token(t: str): + return not any(c.isdigit() for c in t) or t.lower() in { + "decreased", + "increased", + } + + a_effect_tokens = [ + t for t in effects_token_map[i] if not skippable_token(t) + ] + if len(a_effect_tokens) < min_words: + continue + for j in idxs: + if i == j: + continue + b_effect_tokens = [ + t for t in effects_token_map[j] if not skippable_token(t) + ] + lcs_ratio = _lcs_len(a_effect_tokens, b_effect_tokens) / max( + 1, len(a_effect_tokens) + ) + if lcs_ratio >= min_overlap: + diffs = _diff_chunks(a_effect_tokens, b_effect_tokens) + if diffs: + roll = list(diffs) + dfx.at[i, "textRolls"] = dfx.at[i, "textRolls"] + roll + return dfx + + +def _replace_text_rolls(row): + filled_effect: str = row["effect"] + for group in row["textRolls"]: + for text_roll in group[:-1]: # group[-1] is position + if isinstance(text_roll, str) and text_roll in filled_effect: + filled_effect = filled_effect.replace(text_roll, "#") + return filled_effect + + +def _build_counter_position(row): + result = {} + for pos, value, unique in zip( + row["positionNum"], row["numValue"], row["relatedUnique"], strict=True + ): + mod = row["replacedNumEffect"] + mod_unique_key = f"{mod}|{pos}|{value}|{unique}" + if mod_unique_key not in result: + result[mod_unique_key] = { + "replacedNumEffect": mod, + "positionNum": pos, + "numValue": value, + "relatedUnique": unique, + "count": 0, + } + result[mod_unique_key]["count"] += 1 + return result + + +def _expand_effect(row): + car_mod_id = row["caranteneModifierId"] + effect = row["effect"] + text_rolls = row["textRolls"] + related_uniq = row["relatedUnique"] + + # Replace every full numeric match (int or float) with "d#" + replaced_num_effect = NUM_PATTERN.sub("d#", effect) + matches = list(NUM_PATTERN.finditer(effect)) + + results = [] + if not matches: + results.append( + { + "caranteneModifierId": car_mod_id, + "effect": effect, + "relatedUnique": related_uniq, + "mutated": True, # ToDo: adjust if this expands beyond mutated + "replacedNumEffect": effect, + "positionNum": 0, + "numValue": None, + "textRolls": text_rolls, + "unique": bool(related_uniq), + } + ) + return results + + for i, m in enumerate(matches): + group = m.group(0) + + # Decide int vs float based on the text + if "." in group: + num = float(group) + else: + num = int(group) + + results.append( + { + "caranteneModifierId": car_mod_id, + "effect": effect, + "relatedUnique": related_uniq, + "mutated": True, # ToDo: adjust if this expands beyond mutated + "replacedNumEffect": replaced_num_effect, + "positionNum": i, + "numValue": num, + "textRolls": text_rolls, + "unique": bool(related_uniq), + } + ) + + return results + + +def _fill_hashes( + template: str, pos_to_value: dict[int, int | float | None] +) -> tuple[str, list[int]]: + """Replace each # in order; only fill when mapping has a concrete value.""" + + def repl(_): + nonlocal k + v = pos_to_value.get(k, None) + if v is not None: + r = str(v) + else: + r = "d#" + positions.append(k) + k += 1 + return r + + k = 0 + positions: list[int] = [] + result = re.sub(r"d#", repl, template) + return result, positions + + +def _coerce_numeric(v: Any) -> int | float | Any: + """Convert strings to int/float without destroying floats; leave non-numeric as-is.""" + if isinstance(v, int | float): + return v + + if isinstance(v, str): + s = v.strip() + if not s: + return v + + # Heuristic: if it looks like a float (has '.' or exponent), parse as float + if any(c in s for c in ".eE"): + try: + return float(s) + except ValueError: + return v + try: + return int(s) + except ValueError: + try: + return float(s) + except ValueError: + return v + + return v + + +def _consistent_values_from_counter( + counter_dict: dict[str, dict], + template: str, + related_unique: str, +) -> dict[int, float | int | None]: + """ + From the counter dict, compute for one relatedUnique: + - For each positionNum, if exactly one numValue appears -> that value (int or float) + - Otherwise -> None (inconsistent) + """ + by_pos: dict[int, set] = {} + + for key, info in counter_dict.items(): + # Key format expected: + # 'replacedNumEffect|positionNum|numValue|relatedUnique' + try: + t, pos, val, ru = key.split("|", 3) + pos_int = int(pos) + + if t == template and ru == related_unique and info.get("count", 0) > 0: + # Prefer numeric from info['numValue']; fall back to val from the key + num_val = info.get("numValue", None) + if num_val is None: + num_val = val + + by_pos.setdefault(pos_int, set()).add(num_val) + except Exception: + # Ignore malformed keys silently + continue + + result: dict[int, float | int | None] = {} + + for pos, vals in by_pos.items(): + # If we have exactly one distinct value, it's "consistent" + # Note: {1, 1.0} collapses to length 1 because 1 == 1.0 + if len(vals) == 1: + v = next(iter(vals)) + v = _coerce_numeric(v) + if isinstance(v, int | float): + result[pos] = v + else: + # Not numeric after all – treat as inconsistent + result[pos] = None + else: + result[pos] = None + + return result + + +def _apply_static_num_replacements(df: pd.DataFrame) -> pd.DataFrame: + """ + For each input row: + - create one output per relatedUnique + - replace # only where (relatedUnique, positionNum) has a single numValue across the group + - merge outputs that result in identical strings by aggregating relatedUniques + """ + if df.empty: + raise EmptyCarModDF + + out_rows = [] + + for _, row in df.iterrows(): + carantene_mod_id = row["caranteneModifierId"] + template = row["replacedNumEffect"] + text_rolls = row["textRolls"] + related_list = list(row["relatedUnique"]) + counter = row.get("counter", None) + + related_uniques = sorted(set(related_list)) + per_related_outputs = [] + + for ru in related_uniques: + pos_to_value = {} + if isinstance(counter, dict) and counter: + pos_to_value = _consistent_values_from_counter(counter, template, ru) + + effect_inserted, positions = _fill_hashes(template, pos_to_value) + per_related_outputs.append( + { + "caranteneModifierId": carantene_mod_id, + "replacedNumEffect": template, + "filledNumEffect": effect_inserted, + "relatedUnique": ru, + "positionNum": positions, + "textRolls": text_rolls, + } + ) + df = pd.DataFrame(per_related_outputs).explode("positionNum", ignore_index=True) + merged = df.groupby( + ["replacedNumEffect", "filledNumEffect"], as_index=False + ).agg( + { + "caranteneModifierId": lambda s: list(s), + "relatedUnique": lambda s: sorted(set(s)), + "positionNum": lambda s: sorted(set(s)), + "textRolls": lambda t: sorted(t), + } + ) + out_rows.append(merged) + + return pd.concat(out_rows, ignore_index=True) + + +def _build_modifier_from_carantene(df: pd.DataFrame): + if df.empty: + raise EmptyCarModDF + + rows = [] + + all_car_mod_ids = [] + for _, row in df.iterrows(): + try: + carantene_mod_ids = row["caranteneModifierId"] + all_car_mod_ids.extend(carantene_mod_ids) + + template = row["filledNumEffect"] + effect = template.replace("d#", "#") + + # find hashtag placeholders in order (numeric vs text) + kinds = [] + for m in re.finditer(r"d#|#", template): + token = m.group() + kinds.append("num" if token == "d#" else "text") + + # collect textRolls by token-position in filledNumEffect + textpos_map = {} + tr = row.get("textRolls") + if isinstance(tr, list): + for outer in tr: + for inner in outer: + for t1, t2, pos in inner: + s = textpos_map.setdefault(pos, set()) + s.add(f"{t1}") + s.add(f"{t2}") + textpos_map = {p: "|".join(sorted(v)) for p, v in textpos_map.items()} + + # map text-roll positions to hashtag indices (by order) + text_placeholder_idx = [i for i, k in enumerate(kinds) if k == "text"] + sorted_token_pos = sorted(textpos_map) + pos_to_textroll = {} + for idx, token_pos in zip( + text_placeholder_idx, sorted_token_pos, strict=True + ): + pos_to_textroll[idx] = textpos_map[token_pos] + + # build regex from template using placeholder index + parts = [] + i = 0 + h_idx = 0 + while i < len(template): + if template.startswith("d#", i): + parts.append("([0-9]*[.]?[0-9]+)") + i += 2 + h_idx += 1 + elif template[i] == "#": + texts = pos_to_textroll.get(h_idx) + if texts: + options = sorted(set(texts.split("|"))) + parts.append("(" + "|".join(options) + ")") + else: + parts.append("(.+?)") + i += 1 + h_idx += 1 + elif template[i] == "+" or template[i] == "-": + parts.append("[+-]") + i += 1 + else: + parts.append(template[i]) + i += 1 + regex = "^" + "".join(parts) + "$" + + related = row.get("relatedUnique", []) + if isinstance(related, list): + related_str = "|".join(related) + else: + related_str = related if pd.notna(related) else "" + + static_val = None if ("d#" in template or "#" in template) else True + kinds = kinds if kinds else [None] + for pos, kind in enumerate(kinds): + row = { + "position": pos, + "relatedUniques": related_str, + "effect": effect, + "regex": regex if not static_val else None, + "explicit": True, + "unique": True, + "static": static_val, + "dynamicallyCreated": True, + } + if kind == "num": + row["minRoll"] = -999999 + row["maxRoll"] = 999999 + row["textRolls"] = None + elif "text": + row["minRoll"] = None + row["maxRoll"] = None + row["textRolls"] = pos_to_textroll.get(pos) + else: + row["minRoll"] = None + row["maxRoll"] = None + row["textRolls"] = None + rows.append(row) + + except Exception as e: + raise Exception(f"Failed to create mod on row {row}, exception: {e}") + mod_cols = [ + "position", + "relatedUniques", + "minRoll", + "maxRoll", + "textRolls", + "explicit", + "unique", + "static", + "effect", + "regex", + "dynamicallyCreated", + ] + + modifier_ids = list({int(item) for sublist in all_car_mod_ids for item in sublist}) + + return pd.DataFrame(rows, columns=mod_cols), modifier_ids + + +def _transform_carantene_modifier( + carantene_modifier_df: pd.DataFrame, +) -> tuple[pd.DataFrame, list[int]] | tuple[None, None]: + def _expand_num_effect(df: pd.DataFrame) -> pd.DataFrame: + if df.empty: + raise EmptyCarModDF + + return ( + df.apply(lambda row: _expand_effect(row), axis=1) + .explode() + .apply(pd.Series) + .reset_index(drop=True) + ) + + def _group_and_add_counter(df: pd.DataFrame) -> pd.DataFrame: + if df.empty: + return df + + grouped = df.groupby("replacedNumEffect").agg(list).reset_index() + grouped["counter"] = grouped.apply(_build_counter_position, axis=1) + + def row_has_min_count(d, min_count=10): + # d is the dict-of-dicts + return any(inner.get("count", 0) >= min_count for inner in d.values()) + + grouped = grouped[grouped["counter"].apply(row_has_min_count)] + if grouped.empty: + raise EmptyCarModDF + return grouped + + try: + carantene_modifier_df, carantene_modifier_ids = ( + carantene_modifier_df.pipe(_build_text_rolls) + .assign(effect=lambda df: df.apply(_replace_text_rolls, axis=1)) + .pipe(_expand_num_effect) + .pipe(_group_and_add_counter) + .pipe(_apply_static_num_replacements) + .pipe(_build_modifier_from_carantene) + ) + except EmptyCarModDF as e: + logger.info(e.message) + return None, None + + return carantene_modifier_df, carantene_modifier_ids + + +def check_carantene_modifiers() -> None: + base_url = str(settings.BACKEND_BASE_URL) + pom_auth_headers = get_superuser_token_headers(base_url) + get_carantene_response = requests.get( + f"{base_url}/carantene_modifier", headers=pom_auth_headers + ) + + get_carantene_response.raise_for_status() + + carantene_dump = get_carantene_response.json() + + if not carantene_dump: + return + + carantene_df = pd.DataFrame(carantene_dump) + + # with open("car_df_json_dump.json", "w") as f: + # json.dump(carantene_dump, f, indent=4) + + carantene_modifier_df, carantene_modifier_ids = _transform_carantene_modifier( + carantene_df + ) + + if carantene_modifier_df is not None and carantene_modifier_ids is not None: + logger.info( + f"Inserting {len(carantene_modifier_df)} new modifiers from carantene modifiers" + ) + + insert_data( + carantene_modifier_df, + url=base_url, + table_name="modifier", + logger=logger, + headers=pom_auth_headers, + method="post", + ) + + logger.info( + f"Deleting {len(carantene_modifier_ids)} of the old carantene modifiers present" + ) + + bulk_delete_data( + primary_key="caranteneModifierId", + primary_key_values=carantene_modifier_ids, + table_name="carantene_modifier", + ) From 937120ac39dab38169d70d6bc71d836135010d28 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sat, 22 Nov 2025 22:35:58 +0100 Subject: [PATCH 10/18] #802 int fix, timestamp filter for textroll and tuning --- .../modifier/carantene_modifier_processor.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py index 6fa80103..8ac31a50 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py @@ -56,16 +56,21 @@ def _diff_chunks(a: list[str], b: list[str]) -> list[tuple[str, str, int]]: return diffs -def _build_text_rolls(df: pd.DataFrame, min_overlap=0.8, min_words=4, min_count=10): +def _build_text_rolls( + df: pd.DataFrame, min_overlap=0.65, min_days_since_created=3, min_words=3 +): dfx = df.copy() + dfx["relatedUnique"] = dfx["relatedUnique"].apply( lambda x: x if isinstance(x, list) else [x] ) dfx = dfx.explode("relatedUnique", ignore_index=True) dfx["textRolls"] = [[] for _ in range(len(dfx))] + dfx["numForTextReplaced"] = dfx["effect"].str.replace(NUM_PATTERN, "d#", regex=True) - counts = dfx.groupby(["relatedUnique", "effect"])["relatedUnique"].transform("size") - dfx = dfx[counts >= min_count].copy().reset_index(drop=True) + dfx["createdAt"] = pd.to_datetime(dfx["createdAt"]) + cutoff = pd.Timestamp.now(tz="UTC") - pd.Timedelta(days=min_days_since_created) + dfx = dfx[dfx["createdAt"] <= cutoff].copy().reset_index(drop=True) if dfx.empty: raise EmptyCarModDF @@ -274,6 +279,7 @@ def _consistent_values_from_counter( v = next(iter(vals)) v = _coerce_numeric(v) if isinstance(v, int | float): + v = int(v) if v.is_integer() else float(v) result[pos] = v else: # Not numeric after all – treat as inconsistent From 2ada75db9eaecc291ea4f68c80a2c1f3687dfc01 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sat, 22 Nov 2025 22:36:22 +0100 Subject: [PATCH 11/18] #802 grafana remove auth and new dashboard --- src/docker-compose.override.yml | 2 - src/grafana/dashboards/backend-api.json | 195 ++++++++++++------------ 2 files changed, 99 insertions(+), 98 deletions(-) diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 02a72fae..ad7f7c39 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -152,8 +152,6 @@ services: env_file: - .env environment: - - GF_SECURITY_ADMIN_USER=${GRAFANA_USERNAME?Grafana username not set} - - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD?Grafan password not set} - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - GF_AUTH_ANONYMOUS_ENABLED=false - GF_AUTH_BASIC_ENABLED=false diff --git a/src/grafana/dashboards/backend-api.json b/src/grafana/dashboards/backend-api.json index 1024bd1d..41ef0f66 100644 --- a/src/grafana/dashboards/backend-api.json +++ b/src/grafana/dashboards/backend-api.json @@ -102,7 +102,7 @@ "sort": "none" } }, - "pluginVersion": "12.2.0", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -215,7 +215,7 @@ "sort": "none" } }, - "pluginVersion": "12.2.0", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -326,7 +326,7 @@ "sort": "none" } }, - "pluginVersion": "12.2.0", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -395,94 +395,6 @@ ], "type": "timeseries" }, - { - "datasource": { - "type": "loki", - "uid": "pomodifiers-loki" - }, - "description": "Displays all information about general requests to the API.\n\nFilters status code \"0\" requests ", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 18, - "w": 12, - "x": 12, - "y": 8 - }, - "id": 1, - "options": { - "dedupStrategy": "none", - "enableInfiniteScrolling": false, - "enableLogDetails": true, - "prettifyLogMessage": false, - "showCommonLabels": false, - "showLabels": false, - "showTime": true, - "sortOrder": "Ascending", - "wrapLogMessage": false - }, - "pluginVersion": "12.2.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "pomodifiers-loki" - }, - "editorMode": "builder", - "expr": "{job=\"vector\"} |= `pom_app.request` | json | keep level, request_host, request_method, request_process_time_ms, request_status_code, request_url | line_format `{{.level}} {{.request_host}} {{.request_method}} {{.request_url}} {{.request_status_code}} {{.request_process_time_ms}}` | request_status_code != `0`", - "queryType": "range", - "refId": "A" - } - ], - "title": "Backend API - Requests", - "type": "logs" - }, - { - "datasource": { - "type": "loki", - "uid": "pomodifiers-loki" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 25, - "w": 24, - "x": 0, - "y": 26 - }, - "id": 5, - "options": { - "dedupStrategy": "none", - "enableInfiniteScrolling": false, - "enableLogDetails": true, - "prettifyLogMessage": false, - "showCommonLabels": false, - "showLabels": false, - "showTime": true, - "sortOrder": "Descending", - "wrapLogMessage": false - }, - "pluginVersion": "12.2.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "pomodifiers-loki" - }, - "direction": "backward", - "editorMode": "code", - "expr": "{job=\"vector\"} |= \"src-backend-1\" != \"INFO\" or `0.0.0.0:80` | json | keep level, request_host, request_method, request_process_time_ms, request_status_code, request_url, message | line_format \"{{.level}} {{.request_host}} {{.request_method}} {{.request_url}} {{.request_status_code}} {{.request_process_time}} {{.message}}\" | request_status_code != \"0\"", - "queryType": "range", - "refId": "A" - } - ], - "title": "Backend API - Errors (except from host:0.0.0.0)", - "type": "logs" - }, { "datasource": { "type": "loki", @@ -548,8 +460,8 @@ "gridPos": { "h": 8, "w": 12, - "x": 0, - "y": 51 + "x": 12, + "y": 8 }, "id": 4, "options": { @@ -565,7 +477,7 @@ "sort": "none" } }, - "pluginVersion": "12.2.0", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -595,6 +507,96 @@ ], "type": "timeseries" }, + { + "datasource": { + "type": "loki", + "uid": "pomodifiers-loki" + }, + "description": "Displays all information about general requests to the API.\n\nFilters status code \"0\" requests ", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 25, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 1, + "options": { + "dedupStrategy": "none", + "enableInfiniteScrolling": false, + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showControls": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Ascending", + "wrapLogMessage": false + }, + "pluginVersion": "12.3.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "pomodifiers-loki" + }, + "editorMode": "builder", + "expr": "{job=\"vector\"} |= `pom_app.request` | json | keep level, request_host, request_method, request_process_time_ms, request_status_code, request_url | line_format `{{.level}} {{.request_host}} {{.request_method}} {{.request_url}} {{.request_status_code}} {{.request_process_time_ms}}` | request_status_code != `0`", + "queryType": "range", + "refId": "A" + } + ], + "title": "Backend API - Requests", + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "pomodifiers-loki" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 25, + "w": 24, + "x": 0, + "y": 41 + }, + "id": 5, + "options": { + "dedupStrategy": "none", + "enableInfiniteScrolling": false, + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showControls": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "pluginVersion": "12.3.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "pomodifiers-loki" + }, + "direction": "backward", + "editorMode": "code", + "expr": "{job=\"vector\"} |= \"src-backend-1\" != \"INFO\" or `0.0.0.0:80` | json | keep level, request_host, request_method, request_process_time_ms, request_status_code, request_url, message | line_format \"{{.level}} {{.request_host}} {{.request_method}} {{.request_url}} {{.request_status_code}} {{.request_process_time}} {{.message}}\" | request_status_code != \"0\"", + "queryType": "range", + "refId": "A" + } + ], + "title": "Backend API - Errors (except from host:0.0.0.0)", + "type": "logs" + }, { "datasource": { "type": "loki", @@ -608,7 +610,7 @@ "h": 16, "w": 24, "x": 0, - "y": 59 + "y": 66 }, "id": 8, "options": { @@ -617,12 +619,13 @@ "enableLogDetails": true, "prettifyLogMessage": true, "showCommonLabels": false, + "showControls": false, "showLabels": false, "showTime": true, "sortOrder": "Ascending", "wrapLogMessage": true }, - "pluginVersion": "12.2.0", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { From 83e88f32031cb283265b6e64dba9923df5b3f9b1 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sun, 23 Nov 2025 13:11:49 +0100 Subject: [PATCH 12/18] #802 new base types --- .../item_base_type_data/Armour.csv | 27 ++++++++++++++++ .../item_base_type_data/Belts.csv | 2 ++ .../item_base_type_data/Quivers.csv | 8 +++-- .../item_base_type_data/Weapons.csv | 32 +++++++++++++------ 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv index bfd23661..f9934a23 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv @@ -7,15 +7,21 @@ baseType,category,subCategory,relatedUniques "Sinner Tricorne","any armour","helmet", "Iron Mask","any armour","helmet", "Golden Mask","any armour","helmet", +"Raven Mask","any armour","helmet", "Regicide Mask","any armour","helmet", "Lunaris Circlet","any armour","helmet", "Royal Burgonet","any armour","helmet", +"Hubris Circlet","any armour","helmet", "Nightmare Bascinet","any armour","helmet", "Gilded Sallet","any armour","helmet", "Great Crown","any armour","helmet", "Great Helmet","any armour","helmet", "Zealot Helmet","any armour","helmet", "Praetor Crown","any armour","helmet", +"Leather Hood","any armour","helmet", +"Close Helmet","any armour","helmet", +"Harlequin Mask","any armour","helmet", +"Iron Circlet","any armour","helmet", "Carnal Armour","any armour","body armour","Shroud of the Lightless" "Simple Robe","any armour","body armour","Skin of the Lords" "Zodiac Leather","any armour","body armour", @@ -29,8 +35,10 @@ baseType,category,subCategory,relatedUniques "Sage's Robe","any armour","body armour", "Silken Vest","any armour","body armour", "Scholar's Robe","any armour","body armour", +"Assassin's Garb","any armour","body armour", "Spidersilk Robe","any armour","body armour", "Necromancer Silks","any armour","body armour", +"Widowsilk Robe","any armour","body armour", "Plate Vest","any armour","body armour", "Copper Plate","any armour","body armour", "Golden Plate","any armour","body armour", @@ -39,12 +47,22 @@ baseType,category,subCategory,relatedUniques "Triumphant Lamellar","any armour","body armour", "Wyrmscale Doublet","any armour","body armour", "Crusader Chainmail","any armour","body armour", +"Devout Chainmail","any armour","body armour", +"Desert Brigandine","any armour","body armour", +"Astral Plate","any armour","body armour", +"Sadist Garb","any armour","body armour", +"Holy Chainmail","any armour","body armour", +"Full Wyrmscale","any armour","body armour", +"Occultist's Vestment","any armour","body armour", +"Saintly Chainmail","any armour","body armour", "Nubuck Boots","any armour","boots", "Goathide Boots","any armour","boots", "Sorcerer Boots","any armour","boots", "Titan Greaves","any armour","boots", +"Reinforced Greaves","any armour","boots", "Bronzescale Boots","any armour","boots", "Dragonscale Boots","any armour","boots", +"Legion Boots","any armour","boots", "Soldier Boots","any armour","boots", "Deerskin Gloves","any armour","gloves", "Strapped Mitts","any armour","gloves", @@ -55,10 +73,15 @@ baseType,category,subCategory,relatedUniques "Samite Gloves","any armour","gloves", "Conjurer Gloves","any armour","gloves", "Titan Gauntlets","any armour","gloves", +"Murder Mitts","any armour","gloves", "Ironscale Gauntlets","any armour","gloves", "Wyrmscale Gauntlets","any armour","gloves", +"Bronzescale Gauntlets","any armour","gloves", +"Steelscale Gauntlets","any armour","gloves", "Chain Gloves","any armour","gloves", "Legion Gloves","any armour","gloves", +"Silk Gloves","any armour","gloves", +"Crusader Gloves","any armour","gloves", "War Buckler","any armour","shield", "Corrugated Buckler","any armour","shield", "Ironwood Buckler","any armour","shield", @@ -70,3 +93,7 @@ baseType,category,subCategory,relatedUniques "Ezomyte Tower Shield","any armour","shield", "Elegant Round Shield","any armour","shield", "Mosaic Kite Shield","any armour","shield", +"Steel Kite Shield","any armour","shield", +"Champion Kite Shield","any armour","shield", +"Archon Kite Shield","any armour","shield", +"Pinnacle Tower Shield","any armour","shield", diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Belts.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Belts.csv index 00e2f043..3a530fbe 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Belts.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Belts.csv @@ -5,4 +5,6 @@ baseType,category,subCategory,relatedUniques "Heavy Belt","belt","belt", "Leather Belt","belt","belt", "Cloth Belt","belt","belt", +"Studded Belt","belt","belt", +"Crystal Belt","belt","belt", diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Quivers.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Quivers.csv index 3a5640a6..8f348d0e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Quivers.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Quivers.csv @@ -1,6 +1,8 @@ # Source: NA # Note: Only contains bases relevant to the uniques we are tracking baseType,category,subCategory,relatedUniques -"Penetrating Arrow Quiver","quiver","quiver" -"Blunt Arrow Quiver","quiver","quiver" -"Spike-Point Arrow Quiver","quiver","quiver" +"Penetrating Arrow Quiver","quiver","quiver", +"Blunt Arrow Quiver","quiver","quiver", +"Spike-Point Arrow Quiver","quiver","quiver", +"Two-Point Arrow Quiver","quiver","quiver", +"Ornate Quiver","quiver","quiver", diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv index 5da8915c..5984e76e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv @@ -2,28 +2,37 @@ # Note: Only contains bases relevant to the uniques we are tracking baseType,category,subCategory,relatedUniques "Vaal Rapier","weapon","one-handed sword","Paradoxica" -"Midnight Blade","weapon","one-handed sword" +"Cutlass","weapon","one-handed sword", +"Midnight Blade","weapon","one-handed sword", +"Eternal Sword","weapon","one-handed sword", "Serpentine Staff","weapon","warstaff","Cane of Kulemak" +"Vile Staff","weapon","warstaff", +"Military Staff","weapon","warstaff", "Ranger Bow","weapon","bow", "Citadel Bow","weapon","bow", "Short Bow","weapon","bow", +"Death Bow","weapon","bow", "Assassin Bow","weapon","bow", "Terror Claw","weapon","claw", "Imperial Claw","weapon","claw", "Timeworn Claw","weapon","claw", +"Thresher Claw","weapon","claw", "Fiend Dagger","weapon","dagger", -"Fishing Rod","weapon","fishing rod" -"Tomahawk","weapon","one-handed axe" -"Royal Axe","weapon","one-handed axe" -"Ornate Mace","weapon","one-handed mace" -"Gavel","weapon","one-handed mace" -"Cutlass","weapon","one-handed sword" -"Platinum Sceptre","weapon","sceptre" +"Fishing Rod","weapon","fishing rod", +"Tomahawk","weapon","one-handed axe", +"Royal Axe","weapon","one-handed axe", +"Ornate Mace","weapon","one-handed mace", +"Gavel","weapon","one-handed mace", +"Terror Maul","weapon","two-handed mace", +"Steelhead","weapon","two-handed mace", +"Platinum Sceptre","weapon","sceptre", +"Karui Sceptre","weapon","sceptre", +"Vaal Sceptre","weapon","sceptre", "Serpentine Staff","weapon","staff", "Vaal Axe","weapon","two-handed axe", "Jasper Chopper","weapon","two-handed axe", -"Terror Maul","weapon","two-handed mace", -"Steelhead","weapon","two-handed mace", +"Karui Chopper","weapon","two-handed axe", +"Abyssal Axe","weapon","two-handed axe", "Etched Greatsword","weapon","two-handed sword", "Lion Sword","weapon","two-handed sword", "Reaver Sword","weapon","two-handed sword", @@ -31,3 +40,6 @@ baseType,category,subCategory,relatedUniques "Opal Wand","weapon","wand", "Calling Wand","weapon","wand", "Kinetic Wand","weapon","wand", +"Somatic Wand","weapon","wand", +"Prophecy Wand","weapon","wand", +"Spiraled Wand","weapon","wand", From 6df4a258669203689c4521c3f50d1adfa781649b Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sun, 23 Nov 2025 13:12:54 +0100 Subject: [PATCH 13/18] #802 carantene modifier init logic --- .../app/api/routes/carantene_modifier.py | 28 -------- src/backend_api/app/api/routes/modifier.py | 37 +++++++++++ src/backend_api/app/core/config.py | 6 ++ src/backend_api/app/core/models/init_db.py | 31 ++++++++- .../app/crud/extensions/crud_modifier.py | 66 ++++++++++++++++++- 5 files changed, 137 insertions(+), 31 deletions(-) diff --git a/src/backend_api/app/api/routes/carantene_modifier.py b/src/backend_api/app/api/routes/carantene_modifier.py index 1dd212c4..0ad7c8de 100644 --- a/src/backend_api/app/api/routes/carantene_modifier.py +++ b/src/backend_api/app/api/routes/carantene_modifier.py @@ -164,31 +164,3 @@ async def delete_carantene_modifier( return get_delete_return_msg( model_table_name=CaranteneModifier.__tablename__, filter=carantene_modifier_map ).message - - -@router.delete( - "/bulk-delete/", - response_model=str, - dependencies=[Depends(get_current_active_superuser)], -) -async def bulk_delete_carantene_modifier( - caranteneModifierIds: list[schemas.CaranteneModifiersPK], - db: Session = Depends(get_db), -): - """ - Delete a list of carantene modifier by list of key "caranteneModifierId" and values. - - Returns a message that the carantene modifier was deleted. - """ - filter = [car_id.caranteneModifierId for car_id in caranteneModifierIds] - await CRUD_carantene_modifier.remove( - db=db, - filter=filter, - max_deletion_limit=99999999999, - deletion_key="caranteneModifierId", - ) - - return get_delete_return_msg( - model_table_name=CaranteneModifier.__tablename__, - filter=caranteneModifierIds, - ).message diff --git a/src/backend_api/app/api/routes/modifier.py b/src/backend_api/app/api/routes/modifier.py index 074dc69f..e75129d9 100644 --- a/src/backend_api/app/api/routes/modifier.py +++ b/src/backend_api/app/api/routes/modifier.py @@ -1,6 +1,7 @@ from typing import Annotated from fastapi import APIRouter, Depends, Query, Request, Response +from sqlalchemy import text from sqlalchemy.orm import Session import app.core.schemas as schemas @@ -59,6 +60,31 @@ async def get_modifier( return modifier +@router.get( + "/latest-dynamically-created/", + response_model=str | None, + dependencies=[Depends(get_current_active_superuser)], +) +async def modifier_latest_dynamic_created_datetime( + db: Session = Depends(get_db), +): + """ + Delete a list of carantene modifier by list of key "caranteneModifierId" and values. + + Returns a message that the carantene modifier was deleted. + """ + result = db.execute( + text( + """SELECT MAX("createdAt") FROM modifier WHERE "dynamicallyCreated" IS TRUE""" + ) + ).fetchone() + + if not result or not result[0]: + return None + + return str(result[0]) + + @router.get( "/", response_model=schemas.Modifier | list[schemas.Modifier], @@ -238,3 +264,14 @@ async def update_related_unique_modifiers( await CRUD_modifier.update(db_obj=modifier, obj_in=update_modifier, db=db) return f"Updated related uniques for count={len(modifier_related_uniques_update)} modifiers" + + +@router.post( + "/initial-dynamically-created/", + response_model=str, + dependencies=[Depends(get_current_active_superuser)], +) +async def create_initial_dynamically_created_mod(db: Session = Depends(get_db)) -> str: + await CRUD_modifier.create_initial_dynamically_created_mod(db) + + return "Successfully created or updated initial dynamically created modifier to mininum 3 days since created at" diff --git a/src/backend_api/app/core/config.py b/src/backend_api/app/core/config.py index 971b2cf0..01f549a7 100644 --- a/src/backend_api/app/core/config.py +++ b/src/backend_api/app/core/config.py @@ -81,6 +81,12 @@ def ASYNC_DATABASE_URI(self) -> PostgresDsn: path=self.POSTGRES_DB, ) + CURRENT_SOFTCORE_LEAGUE: str + LEAGUE_LAUNCH_TIME: str + MIN_DAYS_BEFORE_NEW_INIT_DYNAMIC_MODIFIER: int = ( + 3 # Mininum amount of days before a new dynamic mod get created + ) + REDIS_PORT: int = 6379 REDIS_SERVER: str REDIS_CACHE: str = str(0) diff --git a/src/backend_api/app/core/models/init_db.py b/src/backend_api/app/core/models/init_db.py index d2e37d9d..81050cad 100644 --- a/src/backend_api/app/core/models/init_db.py +++ b/src/backend_api/app/core/models/init_db.py @@ -1,13 +1,15 @@ -from sqlalchemy import select +from sqlalchemy import select, text from sqlalchemy.orm import Session from app.core.config import settings from app.core.models.models import User -from app.core.schemas.user import UserCreate +from app.core.schemas import UserCreate from app.crud import CRUD_user def init_db(session: Session) -> None: + fix_sequences(session) + user = session.execute( select(User).where(User.email == settings.FIRST_SUPERUSER) ).first() @@ -19,3 +21,28 @@ def init_db(session: Session) -> None: isSuperuser=True, ) user = CRUD_user.create(db=session, user_create=user_in) + + +def fix_sequences(session: Session) -> None: + "Reset the identity/sequence for tables" + session.execute( + text( + """ + SELECT setval( + pg_get_serial_sequence('item_base_type', 'itemBaseTypeId'), + (SELECT COALESCE(MAX("itemBaseTypeId"), 0) FROM item_base_type) + ) + """ + ) + ) + + session.execute( + text( + """ + SELECT setval( + pg_get_serial_sequence('modifier', 'modifierId'), + (SELECT COALESCE(MAX("modifierId"), 0) FROM modifier) + ) + """ + ) + ) diff --git a/src/backend_api/app/crud/extensions/crud_modifier.py b/src/backend_api/app/crud/extensions/crud_modifier.py index 864e6bf6..75a46478 100644 --- a/src/backend_api/app/crud/extensions/crud_modifier.py +++ b/src/backend_api/app/crud/extensions/crud_modifier.py @@ -1,9 +1,12 @@ +from datetime import datetime, timezone + import pandas as pd from fastapi import HTTPException from pydantic import TypeAdapter -from sqlalchemy import select +from sqlalchemy import desc, select, text from sqlalchemy.orm import Session +from app.core.config import settings from app.core.models.models import Modifier as model_Modifier from app.core.schemas.modifier import ( GroupedModifierByEffect, @@ -76,3 +79,64 @@ async def get_grouped_modifier_by_effect(self, db: Session): ).validate_python return validate(grouped_modifier_by_effect_record) + + async def create_initial_dynamically_created_mod(self, db: Session) -> None: + existing_dynamically_created = db.execute( + select(model_Modifier) + .where(model_Modifier.dynamicallyCreated) + .order_by(desc(model_Modifier.dynamicallyCreated)) + ).first()[0] + + if not existing_dynamically_created: + modifier_in = ModifierCreate( + position=0, + effect="EMPTY", + dynamicallyCreated=True, + static=True, + ) + await self.create(db=db, obj_in=modifier_in) + else: + "I know this shit is messy" + # Check if latest item inserted is older than 3 days, if so then create new initial dynamic modifier + + item_result = db.execute( + text( + f"""SELECT MAX("createdHoursSinceLaunch") FROM item WHERE "league"='{settings.CURRENT_SOFTCORE_LEAGUE}'""" + ) + ).fetchone() + + existing_item_hours_since_launch = item_result[0] + now = datetime.now(timezone.utc) + + league_launch_datetime = datetime.fromisoformat(settings.LEAGUE_LAUNCH_TIME) + + delta = now - league_launch_datetime + hours_since_launch = delta.total_seconds() / 3600 + + existing_item_hours_since_launch_diff = None + if existing_item_hours_since_launch is not None: + existing_item_hours_since_launch_diff = ( + hours_since_launch - existing_item_hours_since_launch + ) + + min_hours_between = settings.MIN_DAYS_BEFORE_NEW_INIT_DYNAMIC_MODIFIER * 24 + + if ( + existing_item_hours_since_launch is None + or existing_item_hours_since_launch_diff is None + or existing_item_hours_since_launch_diff > min_hours_between + ): + empty_filter = {"effect": "EMPTY"} + existing_empty_mod = await self.get(db=db, filter=empty_filter) + assert not isinstance( + existing_empty_mod, list + ), "Found duplicate `EMPTY` effect in modifier table, though there should be max 1" + await self.remove(db=db, filter=empty_filter) + + modifier_in = ModifierCreate( + position=0, + effect="EMPTY", + dynamicallyCreated=True, + static=True, + ) + await self.create(db=db, obj_in=modifier_in) From b7ce371b03deb61d876ec6c9f29a9a5ce62a2a1b Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sun, 23 Nov 2025 13:13:31 +0100 Subject: [PATCH 14/18] #802 init logic in backend data retrieval for carantene mods --- .../data_retrieval_app/data_deposit/main.py | 5 +- .../modifier/carantene_modifier_processor.py | 114 ++++++++++++++---- .../external_data_retrieval/config.py | 11 +- 3 files changed, 105 insertions(+), 25 deletions(-) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py index 4debeda0..2333a5fd 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py @@ -6,6 +6,7 @@ ) from data_retrieval_app.data_deposit.modifier.carantene_modifier_processor import ( check_carantene_modifiers, + initial_dynamically_created_modifier, ) from data_retrieval_app.data_deposit.modifier.modifier_data_depositor import ( ModifierDataDepositor, @@ -27,7 +28,9 @@ def main(): logger.info(f"Depositing {key} data.") data_depositor.deposit_data() logger.info("Checking carantene modifiers") - check_carantene_modifiers() + if settings.CHECK_CARANTENE_MODIFIERS: + initial_dynamically_created_modifier() + check_carantene_modifiers() logger.info("Finished checking carantene modifiers") return 0 diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py index 8ac31a50..43f1f2e4 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py @@ -1,4 +1,5 @@ import re +from datetime import UTC, datetime from difflib import SequenceMatcher from typing import Any @@ -57,8 +58,8 @@ def _diff_chunks(a: list[str], b: list[str]) -> list[tuple[str, str, int]]: def _build_text_rolls( - df: pd.DataFrame, min_overlap=0.65, min_days_since_created=3, min_words=3 -): + df: pd.DataFrame, +) -> pd.DataFrame: dfx = df.copy() dfx["relatedUnique"] = dfx["relatedUnique"].apply( @@ -69,8 +70,6 @@ def _build_text_rolls( dfx["numForTextReplaced"] = dfx["effect"].str.replace(NUM_PATTERN, "d#", regex=True) dfx["createdAt"] = pd.to_datetime(dfx["createdAt"]) - cutoff = pd.Timestamp.now(tz="UTC") - pd.Timedelta(days=min_days_since_created) - dfx = dfx[dfx["createdAt"] <= cutoff].copy().reset_index(drop=True) if dfx.empty: raise EmptyCarModDF @@ -91,7 +90,7 @@ def skippable_token(t: str): a_effect_tokens = [ t for t in effects_token_map[i] if not skippable_token(t) ] - if len(a_effect_tokens) < min_words: + if len(a_effect_tokens) < settings.MIN_WORDS_CREATE_TEXT_ROLLS: continue for j in idxs: if i == j: @@ -102,7 +101,7 @@ def skippable_token(t: str): lcs_ratio = _lcs_len(a_effect_tokens, b_effect_tokens) / max( 1, len(a_effect_tokens) ) - if lcs_ratio >= min_overlap: + if lcs_ratio >= settings.MIN_OVERLAP_EFFECT_CREATE_TEXT_ROLLS: diffs = _diff_chunks(a_effect_tokens, b_effect_tokens) if diffs: roll = list(diffs) @@ -512,7 +511,42 @@ def row_has_min_count(d, min_count=10): return carantene_modifier_df, carantene_modifier_ids -def check_carantene_modifiers() -> None: +def _get_latest_dynamic_modifier_created_at() -> datetime: + url = f"{settings.BACKEND_BASE_URL}/modifier/latest-dynamically-created/" + pom_api_headers = get_superuser_token_headers(settings.BACKEND_BASE_URL) + try: + response = requests.get(url=url, headers=pom_api_headers) + response.raise_for_status() + + latest_datetime_str = response.json() + + assert isinstance(latest_datetime_str, str) + latest_datetime_str = latest_datetime_str.replace( + "+00", "+00:00" + ) # why why why + + except requests.HTTPError as e: + raise requests.HTTPError( + f"POM API HTTP request error, failed to create initial dynamically created modifier, error: {e}" + ) + except Exception as e: + raise Exception( + f"Failed to create initial dynamically created modifier, error: {e}" + ) + + return datetime.fromisoformat(latest_datetime_str) + + +def _check_days_since_last_created() -> bool: + latest_created_at = _get_latest_dynamic_modifier_created_at() + days_since_last_created_at = (datetime.now(UTC) - latest_created_at).days + if not days_since_last_created_at > settings.MIN_DAYS_SINCE_DYNAMICALLY_CREATED_AT: + raise Exception("DAYS SINCE LAST WAS NOT GOOD ENOUGH") + return False + return True + + +def _get_carantene_df() -> pd.DataFrame: base_url = str(settings.BACKEND_BASE_URL) pom_auth_headers = get_superuser_token_headers(base_url) get_carantene_response = requests.get( @@ -528,9 +562,44 @@ def check_carantene_modifiers() -> None: carantene_df = pd.DataFrame(carantene_dump) + return carantene_df + + +def _insert_modifiers_from_carantene( + carantene_modifier_df: pd.DataFrame, carantene_modifier_ids: list[int] +) -> None: + base_url = str(settings.BACKEND_BASE_URL) + pom_auth_headers = get_superuser_token_headers(base_url) + + insert_data( + carantene_modifier_df, + url=base_url, + table_name="modifier", + logger=logger, + headers=pom_auth_headers, + method="post", + ) + + logger.info( + f"Deleting {len(carantene_modifier_ids)} of the old carantene modifiers present" + ) + + bulk_delete_data( + primary_key="caranteneModifierId", + primary_key_values=carantene_modifier_ids, + table_name="carantene_modifier", + ) + + +def check_carantene_modifiers() -> None: + if not _check_days_since_last_created(): + return None + # with open("car_df_json_dump.json", "w") as f: # json.dump(carantene_dump, f, indent=4) + carantene_df = _get_carantene_df() + carantene_modifier_df, carantene_modifier_ids = _transform_carantene_modifier( carantene_df ) @@ -539,22 +608,23 @@ def check_carantene_modifiers() -> None: logger.info( f"Inserting {len(carantene_modifier_df)} new modifiers from carantene modifiers" ) + _insert_modifiers_from_carantene(carantene_modifier_df, carantene_modifier_ids) - insert_data( - carantene_modifier_df, - url=base_url, - table_name="modifier", - logger=logger, - headers=pom_auth_headers, - method="post", - ) - logger.info( - f"Deleting {len(carantene_modifier_ids)} of the old carantene modifiers present" - ) +def initial_dynamically_created_modifier() -> None: + "Creates an initial dynamically created modifier, if one does not exist in the database" + + url = f"{settings.BACKEND_BASE_URL}/modifier/initial-dynamically-created/" + pom_api_headers = get_superuser_token_headers(settings.BACKEND_BASE_URL) + try: + response = requests.post(url=url, headers=pom_api_headers) + response.raise_for_status() - bulk_delete_data( - primary_key="caranteneModifierId", - primary_key_values=carantene_modifier_ids, - table_name="carantene_modifier", + except requests.HTTPError as e: + raise requests.HTTPError( + f"POM API HTTP request error, failed to create initial dynamically created modifier, error: {e}" + ) + except Exception as e: + raise Exception( + f"Failed to create initial dynamically created modifier, error: {e}" ) diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py index d224e55a..71776ad9 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py @@ -46,8 +46,15 @@ def CURRENT_HARDCORE_LEAGUE(self) -> str: LEAGUE_LAUNCH_TIME: str - LOAD_INITIAL_DATA: bool = False - CHECK_CARANTENE_MODIFIERS: bool = True + LOAD_INITIAL_DATA: bool = ( + True # Whether to load modifiers, base types etc to database in beginning + ) + CHECK_CARANTENE_MODIFIERS: bool = ( + True # Wether to check and update carantene modifiers to a modifier + ) + MIN_DAYS_SINCE_DYNAMICALLY_CREATED_AT: int = 3 # Update interval in days for every time new dynamically modifiers get created from carantene modifiers + MIN_OVERLAP_EFFECT_CREATE_TEXT_ROLLS: float = 0.65 # Minimal lowest common sequence ratio between modifier.effects before text rolls get created + MIN_WORDS_CREATE_TEXT_ROLLS: int = 3 # Minimal amount of words in an modifier.effect before a text roll gets created @computed_field # type: ignore[prop-decorator] @property From 7dbe08def2291530a8b1b909c4c61aec96399cce Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sun, 23 Nov 2025 13:13:48 +0100 Subject: [PATCH 15/18] #802 docker env vars and grafana login remove --- src/docker-compose.override.yml | 7 ++++--- src/docker-compose.yml | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index ad7f7c39..97f6ea6c 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -152,9 +152,10 @@ services: env_file: - .env environment: - - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - - GF_AUTH_ANONYMOUS_ENABLED=false - - GF_AUTH_BASIC_ENABLED=false + - GF_AUTH_DISABLE_LOGIN_FORM=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer + - GF_AUTH_ANONYMOUS_ENABLED=true + - NODE_ENV=development - GF_FEATURE_TOGGLES_ENABLE=accessControlOnCall - GF_INSTALL_PLUGINS=https://storage.googleapis.com/integration-artifacts/grafana-lokiexplore-app/grafana-lokiexplore-app-latest.zip;grafana-lokiexplore-app ports: diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 17341261..221388d5 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -79,6 +79,8 @@ services: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - REDIS_HOST=cache - REDIS_PASSWORD=${REDIS_PASSWORD?Variable not set} + - LEAGUE_LAUNCH_TIME=${LEAGUE_LAUNCH_TIME?Variable not set} + - CURRENT_SOFTCORE_LEAGUE=${CURRENT_SOFTCORE_LEAGUE?Variable not set} build: context: ./backend_api args: From 89e62e85389ff1d11a1cae10e3891af9116d2501 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sun, 15 Feb 2026 09:03:49 +0100 Subject: [PATCH 16/18] #802 fixed more base types --- .../item_base_type/item_base_type_data/Amulets.csv | 1 - .../data_deposit/item_base_type/item_base_type_data/Armour.csv | 2 ++ .../item_base_type/item_base_type_data/Weapons.csv | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Amulets.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Amulets.csv index 3ef88965..ae1f573a 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Amulets.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Amulets.csv @@ -10,4 +10,3 @@ baseType,category,relatedUniques "Turquoise Amulet",amulet, "Agate Amulet",amulet, "Citrine Amulet",amulet, -"Agate Amulet",amulet, diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv index f9934a23..c7c82240 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Armour.csv @@ -22,6 +22,7 @@ baseType,category,subCategory,relatedUniques "Close Helmet","any armour","helmet", "Harlequin Mask","any armour","helmet", "Iron Circlet","any armour","helmet", +"Mind Cage","any armour","helmet", "Carnal Armour","any armour","body armour","Shroud of the Lightless" "Simple Robe","any armour","body armour","Skin of the Lords" "Zodiac Leather","any armour","body armour", @@ -97,3 +98,4 @@ baseType,category,subCategory,relatedUniques "Champion Kite Shield","any armour","shield", "Archon Kite Shield","any armour","shield", "Pinnacle Tower Shield","any armour","shield", +"Branded Kite Shield","any armour","shield", diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv index 5984e76e..86258c05 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data/Weapons.csv @@ -3,6 +3,7 @@ baseType,category,subCategory,relatedUniques "Vaal Rapier","weapon","one-handed sword","Paradoxica" "Cutlass","weapon","one-handed sword", +"Vaal Blade","weapon","one-handed sword", "Midnight Blade","weapon","one-handed sword", "Eternal Sword","weapon","one-handed sword", "Serpentine Staff","weapon","warstaff","Cane of Kulemak" @@ -12,6 +13,7 @@ baseType,category,subCategory,relatedUniques "Citadel Bow","weapon","bow", "Short Bow","weapon","bow", "Death Bow","weapon","bow", +"Royal Bow","weapon","bow", "Assassin Bow","weapon","bow", "Terror Claw","weapon","claw", "Imperial Claw","weapon","claw", @@ -29,6 +31,7 @@ baseType,category,subCategory,relatedUniques "Karui Sceptre","weapon","sceptre", "Vaal Sceptre","weapon","sceptre", "Serpentine Staff","weapon","staff", +"Lathi","weapon","staff", "Vaal Axe","weapon","two-handed axe", "Jasper Chopper","weapon","two-handed axe", "Karui Chopper","weapon","two-handed axe", From 4697401d6bd7483ddabe83a7b4dda1953e68c768 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sun, 15 Feb 2026 09:04:39 +0100 Subject: [PATCH 17/18] #802 added bulk delete api duplicates --- ...3_added_dynamicallycreated_to_modifiers.py | 30 --------- .../app/api/routes/carantene_modifier.py | 61 +++++++++++++++++++ .../app/crud/extensions/crud_modifier.py | 5 +- 3 files changed, 64 insertions(+), 32 deletions(-) delete mode 100644 src/backend_api/app/alembic/versions/42c53ebbc633_added_dynamicallycreated_to_modifiers.py diff --git a/src/backend_api/app/alembic/versions/42c53ebbc633_added_dynamicallycreated_to_modifiers.py b/src/backend_api/app/alembic/versions/42c53ebbc633_added_dynamicallycreated_to_modifiers.py deleted file mode 100644 index 5f99b87f..00000000 --- a/src/backend_api/app/alembic/versions/42c53ebbc633_added_dynamicallycreated_to_modifiers.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Added dynamicallyCreated to modifiers - -Revision ID: 42c53ebbc633 -Revises: d789cc84cb4d -Create Date: 2025-11-22 15:47:08.893031 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '42c53ebbc633' -down_revision: Union[str, None] = 'd789cc84cb4d' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/src/backend_api/app/api/routes/carantene_modifier.py b/src/backend_api/app/api/routes/carantene_modifier.py index 0ad7c8de..c799a93d 100644 --- a/src/backend_api/app/api/routes/carantene_modifier.py +++ b/src/backend_api/app/api/routes/carantene_modifier.py @@ -1,6 +1,7 @@ from typing import Annotated from fastapi import APIRouter, Depends, Query, Request, Response +from sqlalchemy import text from sqlalchemy.orm import Session import app.core.schemas as schemas @@ -164,3 +165,63 @@ async def delete_carantene_modifier( return get_delete_return_msg( model_table_name=CaranteneModifier.__tablename__, filter=carantene_modifier_map ).message + + +@router.delete( + "/bulk-delete/", + response_model=str, + dependencies=[Depends(get_current_active_superuser)], +) +async def bulk_delete_carantene_modifier( + caranteneModifierIds: list[schemas.CaranteneModifiersPK], + db: Session = Depends(get_db), +): + """ + Delete a carantene modifier by key and value for "caranteneModifierId" + + Returns a message that the carantene modifier was deleted. + Always deletes one carantene_modifier. + """ + + filter = [car_id.caranteneModifierId for car_id in caranteneModifierIds] + await CRUD_carantene_modifier.remove( + db=db, filter=filter, deletion_key="caranteneModifierId" + ) + + return get_delete_return_msg( + model_table_name=CaranteneModifier.__tablename__, filter=filter + ).message + + +@router.post( + "/delete-grouped-dupes/", + response_model=str, + dependencies=[Depends(get_current_active_superuser)], +) +async def delete_grouped_dupes_carantene_modifier( + db: Session = Depends(get_db), +): + group_amount = 12 + + db.execute( + text( + f""" + DELETE FROM "carantene_modifier" + WHERE "caranteneModifierId" IN ( + SELECT "caranteneModifierId" + FROM ( + SELECT + "caranteneModifierId", + ROW_NUMBER() OVER ( + PARTITION BY "effect" + ORDER BY "createdAt" DESC + ) AS rn + FROM "carantene_modifier" + ) AS ranked + WHERE rn > {group_amount} + ) + """ + ) + ) + + return "Successfully deleted duplicate carantene modifier groups by effect" diff --git a/src/backend_api/app/crud/extensions/crud_modifier.py b/src/backend_api/app/crud/extensions/crud_modifier.py index 75a46478..ca20f982 100644 --- a/src/backend_api/app/crud/extensions/crud_modifier.py +++ b/src/backend_api/app/crud/extensions/crud_modifier.py @@ -85,7 +85,7 @@ async def create_initial_dynamically_created_mod(self, db: Session) -> None: select(model_Modifier) .where(model_Modifier.dynamicallyCreated) .order_by(desc(model_Modifier.dynamicallyCreated)) - ).first()[0] + ).first() if not existing_dynamically_created: modifier_in = ModifierCreate( @@ -98,6 +98,7 @@ async def create_initial_dynamically_created_mod(self, db: Session) -> None: else: "I know this shit is messy" # Check if latest item inserted is older than 3 days, if so then create new initial dynamic modifier + existing_dynamically_created = existing_dynamically_created[0] item_result = db.execute( text( @@ -105,7 +106,7 @@ async def create_initial_dynamically_created_mod(self, db: Session) -> None: ) ).fetchone() - existing_item_hours_since_launch = item_result[0] + existing_item_hours_since_launch = item_result[0] if item_result else None now = datetime.now(timezone.utc) league_launch_datetime = datetime.fromisoformat(settings.LEAGUE_LAUNCH_TIME) From 9be105338fd5e3e8de55d6c3744ee9eef3a9d826 Mon Sep 17 00:00:00 2001 From: Ivareh Date: Sun, 15 Feb 2026 09:11:39 +0100 Subject: [PATCH 18/18] #802 deleted duped grouped car mods in retrieval --- .../data_retrieval_app/data_deposit/main.py | 5 + .../modifier/carantene_modifier_processor.py | 146 ++-- .../detectors/unique_detector.py | 7 +- src/backend_data_retrieval/pyproject.toml | 3 + src/backend_data_retrieval/uv.lock | 636 ++++++++++++++++++ src/docker-compose.override.yml | 2 +- 6 files changed, 742 insertions(+), 57 deletions(-) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py index 2333a5fd..1c51fb2a 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py @@ -6,6 +6,7 @@ ) from data_retrieval_app.data_deposit.modifier.carantene_modifier_processor import ( check_carantene_modifiers, + delete_grouped_dupes, initial_dynamically_created_modifier, ) from data_retrieval_app.data_deposit.modifier.modifier_data_depositor import ( @@ -29,7 +30,11 @@ def main(): data_depositor.deposit_data() logger.info("Checking carantene modifiers") if settings.CHECK_CARANTENE_MODIFIERS: + import nltk + + nltk.download("stopwords") initial_dynamically_created_modifier() + delete_grouped_dupes() check_carantene_modifiers() logger.info("Finished checking carantene modifiers") return 0 diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py index 43f1f2e4..4440ddd1 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/carantene_modifier_processor.py @@ -5,11 +5,13 @@ import pandas as pd import requests +from nltk.corpus import stopwords +from spacy.lang.en.stop_words import STOP_WORDS from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.logs.logger import data_deposit_logger as logger from data_retrieval_app.pom_api_authentication import get_superuser_token_headers -from data_retrieval_app.utils import bulk_delete_data, insert_data +from data_retrieval_app.utils import insert_data NUM_PATTERN = re.compile(r"-?\d+(?:\.\d+)?") @@ -60,6 +62,7 @@ def _diff_chunks(a: list[str], b: list[str]) -> list[tuple[str, str, int]]: def _build_text_rolls( df: pd.DataFrame, ) -> pd.DataFrame: + logger.info("Building text rolls...") dfx = df.copy() dfx["relatedUnique"] = dfx["relatedUnique"].apply( @@ -71,26 +74,37 @@ def _build_text_rolls( dfx["createdAt"] = pd.to_datetime(dfx["createdAt"]) - if dfx.empty: - raise EmptyCarModDF + groups = list(dfx.groupby("relatedUnique", sort=False)) + total_groups = len(groups) + + def skippable_token(t: str): + return any(c.isdigit() or c == "#" for c in t) + + stopwords_set = ( + set(stopwords.words("english")) + | STOP_WORDS + | {"increased", "decreased", "reduced"} + ) + + for idx, (_, g_df) in enumerate(groups, start=1): + logger.info( + "Processing textrolls, related unique group %s/%s (size=%s)", + idx, + total_groups, + len(g_df), + ) - for _, g_df in dfx.groupby("relatedUnique", sort=False): idxs: list[int] = g_df.index.tolist() effects_token_map: dict[int, list[str]] = { - i: _tokenize(dfx.at[i, "effect"]) for i in idxs + i: _tokenize(dfx.at[i, "numForTextReplaced"]) for i in idxs } for i in idxs: - - def skippable_token(t: str): - return not any(c.isdigit() for c in t) or t.lower() in { - "decreased", - "increased", - } - a_effect_tokens = [ t for t in effects_token_map[i] if not skippable_token(t) ] - if len(a_effect_tokens) < settings.MIN_WORDS_CREATE_TEXT_ROLLS: + if ( + len(effects_token_map[i]) < settings.MIN_WORDS_CREATE_TEXT_ROLLS + ): # check with skips/numbers for min words continue for j in idxs: if i == j: @@ -99,13 +113,23 @@ def skippable_token(t: str): t for t in effects_token_map[j] if not skippable_token(t) ] lcs_ratio = _lcs_len(a_effect_tokens, b_effect_tokens) / max( - 1, len(a_effect_tokens) + 1, len(effects_token_map[i]) ) if lcs_ratio >= settings.MIN_OVERLAP_EFFECT_CREATE_TEXT_ROLLS: diffs = _diff_chunks(a_effect_tokens, b_effect_tokens) if diffs: - roll = list(diffs) - dfx.at[i, "textRolls"] = dfx.at[i, "textRolls"] + roll + diff_rolls = [ + diff_tuple + for diff_tuple in diffs + if all( + roll.lower() not in stopwords_set + for roll in diff_tuple[:-1] + ) + ] + dfx.at[i, "textRolls"] = dfx.at[i, "textRolls"] + diff_rolls + logger.info( + "Successfully built text rolls, performing further transforms on carantene..." + ) return dfx @@ -143,7 +167,6 @@ def _expand_effect(row): text_rolls = row["textRolls"] related_uniq = row["relatedUnique"] - # Replace every full numeric match (int or float) with "d#" replaced_num_effect = NUM_PATTERN.sub("d#", effect) matches = list(NUM_PATTERN.finditer(effect)) @@ -222,7 +245,6 @@ def _coerce_numeric(v: Any) -> int | float | Any: if not s: return v - # Heuristic: if it looks like a float (has '.' or exponent), parse as float if any(c in s for c in ".eE"): try: return float(s) @@ -273,7 +295,6 @@ def _consistent_values_from_counter( for pos, vals in by_pos.items(): # If we have exactly one distinct value, it's "consistent" - # Note: {1, 1.0} collapses to length 1 because 1 == 1.0 if len(vals) == 1: v = next(iter(vals)) v = _coerce_numeric(v) @@ -281,7 +302,6 @@ def _consistent_values_from_counter( v = int(v) if v.is_integer() else float(v) result[pos] = v else: - # Not numeric after all – treat as inconsistent result[pos] = None else: result[pos] = None @@ -340,6 +360,9 @@ def _apply_static_num_replacements(df: pd.DataFrame) -> pd.DataFrame: ) out_rows.append(merged) + logger.info( + "Successfully added static numbers replacements, performing further transforms on carantene..." + ) return pd.concat(out_rows, ignore_index=True) @@ -420,7 +443,7 @@ def _build_modifier_from_carantene(df: pd.DataFrame): static_val = None if ("d#" in template or "#" in template) else True kinds = kinds if kinds else [None] for pos, kind in enumerate(kinds): - row = { + out_row = { "position": pos, "relatedUniques": related_str, "effect": effect, @@ -431,18 +454,18 @@ def _build_modifier_from_carantene(df: pd.DataFrame): "dynamicallyCreated": True, } if kind == "num": - row["minRoll"] = -999999 - row["maxRoll"] = 999999 - row["textRolls"] = None - elif "text": - row["minRoll"] = None - row["maxRoll"] = None - row["textRolls"] = pos_to_textroll.get(pos) + out_row["minRoll"] = -999999 + out_row["maxRoll"] = 999999 + out_row["textRolls"] = None + elif kind == "text": + out_row["minRoll"] = None + out_row["maxRoll"] = None + out_row["textRolls"] = pos_to_textroll.get(pos) else: - row["minRoll"] = None - row["maxRoll"] = None - row["textRolls"] = None - rows.append(row) + out_row["minRoll"] = None + out_row["maxRoll"] = None + out_row["textRolls"] = None + rows.append(out_row) except Exception as e: raise Exception(f"Failed to create mod on row {row}, exception: {e}") @@ -462,6 +485,7 @@ def _build_modifier_from_carantene(df: pd.DataFrame): modifier_ids = list({int(item) for sublist in all_car_mod_ids for item in sublist}) + logger.info("Successfully built modifiers from carantene modifiers") return pd.DataFrame(rows, columns=mod_cols), modifier_ids @@ -493,6 +517,10 @@ def row_has_min_count(d, min_count=10): grouped = grouped[grouped["counter"].apply(row_has_min_count)] if grouped.empty: raise EmptyCarModDF + + logger.info( + "Successfully grouped and added counters, performing further transforms on carantene..." + ) return grouped try: @@ -515,11 +543,10 @@ def _get_latest_dynamic_modifier_created_at() -> datetime: url = f"{settings.BACKEND_BASE_URL}/modifier/latest-dynamically-created/" pom_api_headers = get_superuser_token_headers(settings.BACKEND_BASE_URL) try: - response = requests.get(url=url, headers=pom_api_headers) + response = requests.get(url=url, headers=pom_api_headers, timeout=60) response.raise_for_status() latest_datetime_str = response.json() - assert isinstance(latest_datetime_str, str) latest_datetime_str = latest_datetime_str.replace( "+00", "+00:00" @@ -541,7 +568,6 @@ def _check_days_since_last_created() -> bool: latest_created_at = _get_latest_dynamic_modifier_created_at() days_since_last_created_at = (datetime.now(UTC) - latest_created_at).days if not days_since_last_created_at > settings.MIN_DAYS_SINCE_DYNAMICALLY_CREATED_AT: - raise Exception("DAYS SINCE LAST WAS NOT GOOD ENOUGH") return False return True @@ -565,9 +591,7 @@ def _get_carantene_df() -> pd.DataFrame: return carantene_df -def _insert_modifiers_from_carantene( - carantene_modifier_df: pd.DataFrame, carantene_modifier_ids: list[int] -) -> None: +def _insert_modifiers_from_carantene(carantene_modifier_df: pd.DataFrame) -> None: base_url = str(settings.BACKEND_BASE_URL) pom_auth_headers = get_superuser_token_headers(base_url) @@ -580,35 +604,42 @@ def _insert_modifiers_from_carantene( method="post", ) - logger.info( - f"Deleting {len(carantene_modifier_ids)} of the old carantene modifiers present" - ) - - bulk_delete_data( - primary_key="caranteneModifierId", - primary_key_values=carantene_modifier_ids, - table_name="carantene_modifier", - ) - def check_carantene_modifiers() -> None: if not _check_days_since_last_created(): return None + logger.info( + "Its been over 3 days since last created modifiers from carantene modifiers. Retrieving carantene modifiers..." + ) + # with open("car_df_json_dump.json", "w") as f: # json.dump(carantene_dump, f, indent=4) carantene_df = _get_carantene_df() + logger.info("Transforming carantene modifiers...") + carantene_modifier_df, carantene_modifier_ids = _transform_carantene_modifier( carantene_df ) if carantene_modifier_df is not None and carantene_modifier_ids is not None: logger.info( - f"Inserting {len(carantene_modifier_df)} new modifiers from carantene modifiers" + f"Inserting {len(carantene_modifier_df)} new modifiers from carantene modifiers..." ) - _insert_modifiers_from_carantene(carantene_modifier_df, carantene_modifier_ids) + + _insert_modifiers_from_carantene(carantene_modifier_df) + + logger.info( + f"Deleting {len(carantene_modifier_ids)} of the old carantene modifiers present" + ) + + # bulk_delete_data( + # primary_key="caranteneModifierId", + # primary_key_values=carantene_modifier_ids, + # table_name="carantene_modifier", + # ) def initial_dynamically_created_modifier() -> None: @@ -628,3 +659,18 @@ def initial_dynamically_created_modifier() -> None: raise Exception( f"Failed to create initial dynamically created modifier, error: {e}" ) + + +def delete_grouped_dupes() -> None: + url = f"{settings.BACKEND_BASE_URL}/carantene_modifier/delete-grouped-dupes/" + pom_api_headers = get_superuser_token_headers(settings.BACKEND_BASE_URL) + try: + response = requests.post(url=url, headers=pom_api_headers) + response.raise_for_status() + + except requests.HTTPError as e: + raise requests.HTTPError( + f"POM API HTTP request error, failed to delete grouped dupes, error: {e}" + ) + except Exception as e: + raise Exception(f"Failed to delete grouped dupes, error: {e}") diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py index c4ccbc9e..f6eae061 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/unique_detector.py @@ -1,7 +1,6 @@ import pandas as pd from data_retrieval_app.external_data_retrieval.detectors.base import DetectorBase -from data_retrieval_app.logs.logger import main_logger as logger class UniqueDetector(DetectorBase): @@ -28,11 +27,7 @@ def _specialized_filter(self, df: pd.DataFrame) -> pd.DataFrame: class UniqueFoulbornDetector(UniqueDetector): def _check_if_wanted(self, df: pd.DataFrame) -> pd.DataFrame: - if "mutated" not in df.columns: - logger.error("MUTATED NOT IN") - return pd.DataFrame(columns=df.columns) - - df_filtered = df[df["mutated"].astype(str) == "True"].loc[ + df_filtered = df[df["identified"] & df["mutated"].astype(str) == "True"].loc[ df["name"].str.len() != 0 ] diff --git a/src/backend_data_retrieval/pyproject.toml b/src/backend_data_retrieval/pyproject.toml index f0253627..2e23f428 100644 --- a/src/backend_data_retrieval/pyproject.toml +++ b/src/backend_data_retrieval/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "gunicorn>=23.0.0", "httpx>=0.28.1", "jinja2>=3.1.6", + "nltk>=3.9.2", "pandas>=2.3.0", "psycopg2>=2.9.10", "pydantic>=2.11.7", @@ -26,8 +27,10 @@ dependencies = [ "pyyaml>=6.0.2", "redis>=6.2.0", "requests>=2.32.4", + "scikit-learn>=1.7.2", "sentry-sdk>=2.30.0", "slowapi>=0.1.9", + "spacy>=3.8.11", "tenacity>=9.1.2", "tqdm>=4.67.1", ] diff --git a/src/backend_data_retrieval/uv.lock b/src/backend_data_retrieval/uv.lock index 5ab14fab..a438b818 100644 --- a/src/backend_data_retrieval/uv.lock +++ b/src/backend_data_retrieval/uv.lock @@ -127,6 +127,7 @@ dependencies = [ { name = "gunicorn" }, { name = "httpx" }, { name = "jinja2" }, + { name = "nltk" }, { name = "pandas" }, { name = "psycopg2" }, { name = "pydantic" }, @@ -137,8 +138,10 @@ dependencies = [ { name = "pyyaml" }, { name = "redis" }, { name = "requests" }, + { name = "scikit-learn" }, { name = "sentry-sdk" }, { name = "slowapi" }, + { name = "spacy" }, { name = "tenacity" }, { name = "tqdm" }, ] @@ -167,6 +170,7 @@ requires-dist = [ { name = "gunicorn", specifier = ">=23.0.0" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "jinja2", specifier = ">=3.1.6" }, + { name = "nltk", specifier = ">=3.9.2" }, { name = "pandas", specifier = ">=2.3.0" }, { name = "psycopg2", specifier = ">=2.9.10" }, { name = "pydantic", specifier = ">=2.11.7" }, @@ -177,8 +181,10 @@ requires-dist = [ { name = "pyyaml", specifier = ">=6.0.2" }, { name = "redis", specifier = ">=6.2.0" }, { name = "requests", specifier = ">=2.32.4" }, + { name = "scikit-learn", specifier = ">=1.7.2" }, { name = "sentry-sdk", specifier = ">=2.30.0" }, { name = "slowapi", specifier = ">=0.1.9" }, + { name = "spacy", specifier = ">=3.8.11" }, { name = "tenacity", specifier = ">=9.1.2" }, { name = "tqdm", specifier = ">=4.67.1" }, ] @@ -301,6 +307,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 }, ] +[[package]] +name = "blis" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663 }, + { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939 }, + { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835 }, + { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550 }, + { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686 }, + { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939 }, + { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759 }, + { url = "https://files.pythonhosted.org/packages/e6/f7/d26e62d9be3d70473a63e0a5d30bae49c2fe138bebac224adddcdef8a7ce/blis-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1e647341f958421a86b028a2efe16ce19c67dba2a05f79e8f7e80b1ff45328aa", size = 6928322 }, + { url = "https://files.pythonhosted.org/packages/4a/78/750d12da388f714958eb2f2fd177652323bbe7ec528365c37129edd6eb84/blis-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d563160f874abb78a57e346f07312c5323f7ad67b6370052b6b17087ef234a8e", size = 1229635 }, + { url = "https://files.pythonhosted.org/packages/e8/36/eac4199c5b200a5f3e93cad197da8d26d909f218eb444c4f552647c95240/blis-1.3.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:30b8a5b90cb6cb81d1ada9ae05aa55fb8e70d9a0ae9db40d2401bb9c1c8f14c4", size = 2815650 }, + { url = "https://files.pythonhosted.org/packages/bf/51/472e7b36a6bedb5242a9757e7486f702c3619eff76e256735d0c8b1679c6/blis-1.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9f5c53b277f6ac5b3ca30bc12ebab7ea16c8f8c36b14428abb56924213dc127", size = 11359008 }, + { url = "https://files.pythonhosted.org/packages/84/da/d0dfb6d6e6321ae44df0321384c32c322bd07b15740d7422727a1a49fc5d/blis-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6297e7616c158b305c9a8a4e47ca5fc9b0785194dd96c903b1a1591a7ca21ddf", size = 3011959 }, + { url = "https://files.pythonhosted.org/packages/20/c5/2b0b5e556fa0364ed671051ea078a6d6d7b979b1cfef78d64ad3ca5f0c7f/blis-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f966ca74f89f8a33e568b9a1d71992fc9a0d29a423e047f0a212643e21b5458", size = 14232456 }, + { url = "https://files.pythonhosted.org/packages/31/07/4cdc81a47bf862c0b06d91f1bc6782064e8b69ac9b5d4ff51d97e4ff03da/blis-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:7a0fc4b237a3a453bdc3c7ab48d91439fcd2d013b665c46948d9eaf9c3e45a97", size = 6192624 }, + { url = "https://files.pythonhosted.org/packages/5f/8a/80f7c68fbc24a76fc9c18522c46d6d69329c320abb18e26a707a5d874083/blis-1.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c3e33cfbf22a418373766816343fcfcd0556012aa3ffdf562c29cddec448a415", size = 6934081 }, + { url = "https://files.pythonhosted.org/packages/e5/52/d1aa3a51a7fc299b0c89dcaa971922714f50b1202769eebbdaadd1b5cff7/blis-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f165930e8d3a85c606d2003211497e28d528c7416fbfeafb6b15600963f7c9b", size = 1231486 }, + { url = "https://files.pythonhosted.org/packages/99/4f/badc7bd7f74861b26c10123bba7b9d16f99cd9535ad0128780360713820f/blis-1.3.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:878d4d96d8f2c7a2459024f013f2e4e5f46d708b23437dae970d998e7bff14a0", size = 2814944 }, + { url = "https://files.pythonhosted.org/packages/72/a6/f62a3bd814ca19ec7e29ac889fd354adea1217df3183e10217de51e2eb8b/blis-1.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f36c0ca84a05ee5d3dbaa38056c4423c1fc29948b17a7923dd2fed8967375d74", size = 11345825 }, + { url = "https://files.pythonhosted.org/packages/d4/6c/671af79ee42bc4c968cae35c091ac89e8721c795bfa4639100670dc59139/blis-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e5a662c48cd4aad5dae1a950345df23957524f071315837a4c6feb7d3b288990", size = 3008771 }, + { url = "https://files.pythonhosted.org/packages/be/92/7cd7f8490da7c98ee01557f2105885cc597217b0e7fd2eeb9e22cdd4ef23/blis-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de26fbd72bac900c273b76d46f0b45b77a28eace2e01f6ac6c2239531a413bb", size = 14219213 }, + { url = "https://files.pythonhosted.org/packages/0a/de/acae8e9f9a1f4bb393d41c8265898b0f29772e38eac14e9f69d191e2c006/blis-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:9e5fdf4211b1972400f8ff6dafe87cb689c5d84f046b4a76b207c0bd2270faaf", size = 6324695 }, +] + [[package]] name = "cachetools" version = "6.1.0" @@ -310,6 +348,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/f0/2ef431fe4141f5e334759d73e81120492b23b2824336883a91ac04ba710b/cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", size = 11189 }, ] +[[package]] +name = "catalogue" +version = "2.0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325 }, +] + [[package]] name = "certifi" version = "2025.6.15" @@ -384,6 +431,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, ] +[[package]] +name = "cloudpathlib" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/18/2ac35d6b3015a0c74e923d94fc69baf8307f7c3233de015d69f99e17afa8/cloudpathlib-0.23.0.tar.gz", hash = "sha256:eb38a34c6b8a048ecfd2b2f60917f7cbad4a105b7c979196450c2f541f4d6b4b", size = 53126 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -393,6 +449,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "confection" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "srsly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/d3/57c6631159a1b48d273b40865c315cf51f89df7a9d1101094ef12e3a37c2/confection-0.1.5.tar.gz", hash = "sha256:8e72dd3ca6bd4f48913cd220f10b8275978e740411654b6e8ca6d7008c590f0e", size = 38924 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/00/3106b1854b45bd0474ced037dfe6b73b90fe68a68968cef47c23de3d43d2/confection-0.1.5-py3-none-any.whl", hash = "sha256:e29d3c3f8eac06b3f77eb9dfb4bf2fc6bcc9622a98ca00a698e3d019c6430b14", size = 35451 }, +] + [[package]] name = "coverage" version = "7.9.1" @@ -456,6 +525,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747 }, ] +[[package]] +name = "cymem" +version = "2.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745 }, + { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927 }, + { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346 }, + { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843 }, + { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607 }, + { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421 }, + { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176 }, + { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959 }, + { url = "https://files.pythonhosted.org/packages/ce/0f/95a4d1e3bebfdfa7829252369357cf9a764f67569328cd9221f21e2c952e/cymem-2.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:891fd9030293a8b652dc7fb9fdc79a910a6c76fc679cd775e6741b819ffea476", size = 43478 }, + { url = "https://files.pythonhosted.org/packages/bf/a0/8fc929cc29ae466b7b4efc23ece99cbd3ea34992ccff319089c624d667fd/cymem-2.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89c4889bd16513ce1644ccfe1e7c473ba7ca150f0621e66feac3a571bde09e7e", size = 42695 }, + { url = "https://files.pythonhosted.org/packages/4a/b3/deeb01354ebaf384438083ffe0310209ef903db3e7ba5a8f584b06d28387/cymem-2.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:45dcaba0f48bef9cc3d8b0b92058640244a95a9f12542210b51318da97c2cf28", size = 250573 }, + { url = "https://files.pythonhosted.org/packages/36/36/bc980b9a14409f3356309c45a8d88d58797d02002a9d794dd6c84e809d3a/cymem-2.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e96848faaafccc0abd631f1c5fb194eac0caee4f5a8777fdbb3e349d3a21741c", size = 254572 }, + { url = "https://files.pythonhosted.org/packages/fd/dd/a12522952624685bd0f8968e26d2ed6d059c967413ce6eb52292f538f1b0/cymem-2.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e02d3e2c3bfeb21185d5a4a70790d9df40629a87d8d7617dc22b4e864f665fa3", size = 248060 }, + { url = "https://files.pythonhosted.org/packages/08/11/5dc933ddfeb2dfea747a0b935cb965b9a7580b324d96fc5f5a1b5ff8df29/cymem-2.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fece5229fd5ecdcd7a0738affb8c59890e13073ae5626544e13825f26c019d3c", size = 254601 }, + { url = "https://files.pythonhosted.org/packages/70/66/d23b06166864fa94e13a98e5922986ce774832936473578febce64448d75/cymem-2.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:38aefeb269597c1a0c2ddf1567dd8605489b661fa0369c6406c1acd433b4c7ba", size = 40103 }, + { url = "https://files.pythonhosted.org/packages/2f/9e/c7b21271ab88a21760f3afdec84d2bc09ffa9e6c8d774ad9d4f1afab0416/cymem-2.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:717270dcfd8c8096b479c42708b151002ff98e434a7b6f1f916387a6c791e2ad", size = 36016 }, + { url = "https://files.pythonhosted.org/packages/7f/28/d3b03427edc04ae04910edf1c24b993881c3ba93a9729a42bcbb816a1808/cymem-2.0.13-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7e1a863a7f144ffb345397813701509cfc74fc9ed360a4d92799805b4b865dd1", size = 46429 }, + { url = "https://files.pythonhosted.org/packages/35/a9/7ed53e481f47ebfb922b0b42e980cec83e98ccb2137dc597ea156642440c/cymem-2.0.13-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c16cb80efc017b054f78998c6b4b013cef509c7b3d802707ce1f85a1d68361bf", size = 46205 }, + { url = "https://files.pythonhosted.org/packages/61/39/a3d6ad073cf7f0fbbb8bbf09698c3c8fac11be3f791d710239a4e8dd3438/cymem-2.0.13-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d78a27c88b26c89bd1ece247d1d5939dba05a1dae6305aad8fd8056b17ddb51", size = 296083 }, + { url = "https://files.pythonhosted.org/packages/36/0c/20697c8bc19f624a595833e566f37d7bcb9167b0ce69de896eba7cfc9c2d/cymem-2.0.13-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d36710760f817194dacb09d9fc45cb6a5062ed75e85f0ef7ad7aeeb13d80cc3", size = 286159 }, + { url = "https://files.pythonhosted.org/packages/82/d4/9326e3422d1c2d2b4a8fb859bdcce80138f6ab721ddafa4cba328a505c71/cymem-2.0.13-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c8f30971cadd5dcf73bcfbbc5849b1f1e1f40db8cd846c4aa7d3b5e035c7b583", size = 288186 }, + { url = "https://files.pythonhosted.org/packages/ed/bc/68da7dd749b72884dc22e898562f335002d70306069d496376e5ff3b6153/cymem-2.0.13-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d441d0e45798ec1fd330373bf7ffa6b795f229275f64016b6a193e6e2a51522", size = 290353 }, + { url = "https://files.pythonhosted.org/packages/50/23/dbf2ad6ecd19b99b3aab6203b1a06608bbd04a09c522d836b854f2f30f73/cymem-2.0.13-cp313-cp313t-win_amd64.whl", hash = "sha256:d1c950eebb9f0f15e3ef3591313482a5a611d16fc12d545e2018cd607f40f472", size = 44764 }, + { url = "https://files.pythonhosted.org/packages/54/3f/35701c13e1fc7b0895198c8b20068c569a841e0daf8e0b14d1dc0816b28f/cymem-2.0.13-cp313-cp313t-win_arm64.whl", hash = "sha256:042e8611ef862c34a97b13241f5d0da86d58aca3cecc45c533496678e75c5a1f", size = 38964 }, + { url = "https://files.pythonhosted.org/packages/a7/2e/f0e1596010a9a57fa9ebd124a678c07c5b2092283781ae51e79edcf5cb98/cymem-2.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2a4bf67db76c7b6afc33de44fb1c318207c3224a30da02c70901936b5aafdf1", size = 43812 }, + { url = "https://files.pythonhosted.org/packages/bc/45/8ccc21df08fcbfa6aa3efeb7efc11a1c81c90e7476e255768bb9c29ba02a/cymem-2.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:92a2ce50afa5625fb5ce7c9302cee61e23a57ccac52cd0410b4858e572f8614b", size = 42951 }, + { url = "https://files.pythonhosted.org/packages/01/8c/fe16531631f051d3d1226fa42e2d76fd2c8d5cfa893ec93baee90c7a9d90/cymem-2.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc116a70cc3a5dc3d1684db5268eff9399a0be8603980005e5b889564f1ea42f", size = 249878 }, + { url = "https://files.pythonhosted.org/packages/47/4b/39d67b80ffb260457c05fcc545de37d82e9e2dbafc93dd6b64f17e09b933/cymem-2.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68489bf0035c4c280614067ab6a82815b01dc9fcd486742a5306fe9f68deb7ef", size = 252571 }, + { url = "https://files.pythonhosted.org/packages/53/0e/76f6531f74dfdfe7107899cce93ab063bb7ee086ccd3910522b31f623c08/cymem-2.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:03cb7bdb55718d5eb6ef0340b1d2430ba1386db30d33e9134d01ba9d6d34d705", size = 248555 }, + { url = "https://files.pythonhosted.org/packages/c7/7c/eee56757db81f0aefc2615267677ae145aff74228f529838425057003c0d/cymem-2.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1710390e7fb2510a8091a1991024d8ae838fd06b02cdfdcd35f006192e3c6b0e", size = 254177 }, + { url = "https://files.pythonhosted.org/packages/77/e0/a4b58ec9e53c836dce07ef39837a64a599f4a21a134fc7ca57a3a8f9a4b5/cymem-2.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:ac699c8ec72a3a9de8109bd78821ab22f60b14cf2abccd970b5ff310e14158ed", size = 40853 }, + { url = "https://files.pythonhosted.org/packages/61/81/9931d1f83e5aeba175440af0b28f0c2e6f71274a5a7b688bc3e907669388/cymem-2.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:90c2d0c04bcda12cd5cebe9be93ce3af6742ad8da96e1b1907e3f8e00291def1", size = 36970 }, + { url = "https://files.pythonhosted.org/packages/b7/ef/af447c2184dec6dec973be14614df8ccb4d16d1c74e0784ab4f02538433c/cymem-2.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff036bbc1464993552fd1251b0a83fe102af334b301e3896d7aa05a4999ad042", size = 46804 }, + { url = "https://files.pythonhosted.org/packages/8c/95/e10f33a8d4fc17f9b933d451038218437f9326c2abb15a3e7f58ce2a06ec/cymem-2.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb8291691ba7ff4e6e000224cc97a744a8d9588418535c9454fd8436911df612", size = 46254 }, + { url = "https://files.pythonhosted.org/packages/e7/7a/5efeb2d2ea6ebad2745301ad33a4fa9a8f9a33b66623ee4d9185683007a6/cymem-2.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d8d06ea59006b1251ad5794bcc00121e148434826090ead0073c7b7fedebe431", size = 296061 }, + { url = "https://files.pythonhosted.org/packages/0b/28/2a3f65842cc8443c2c0650cf23d525be06c8761ab212e0a095a88627be1b/cymem-2.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0046a619ecc845ccb4528b37b63426a0cbcb4f14d7940add3391f59f13701e6", size = 285784 }, + { url = "https://files.pythonhosted.org/packages/98/73/dd5f9729398f0108c2e71d942253d0d484d299d08b02e474d7cfc43ed0b0/cymem-2.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:18ad5b116a82fa3674bc8838bd3792891b428971e2123ae8c0fd3ca472157c5e", size = 288062 }, + { url = "https://files.pythonhosted.org/packages/5a/01/ffe51729a8f961a437920560659073e47f575d4627445216c1177ecd4a41/cymem-2.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:666ce6146bc61b9318aa70d91ce33f126b6344a25cf0b925621baed0c161e9cc", size = 290465 }, + { url = "https://files.pythonhosted.org/packages/fd/ac/c9e7d68607f71ef978c81e334ab2898b426944c71950212b1467186f69f9/cymem-2.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:84c1168c563d9d1e04546cb65e3e54fde2bf814f7c7faf11fc06436598e386d1", size = 46665 }, + { url = "https://files.pythonhosted.org/packages/66/66/150e406a2db5535533aa3c946de58f0371f2e412e23f050c704588023e6e/cymem-2.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:e9027764dc5f1999fb4b4cabee1d0322c59e330c0a6485b436a68275f614277f", size = 39715 }, +] + [[package]] name = "deprecated" version = "1.2.18" @@ -771,6 +888,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396 }, +] + [[package]] name = "limits" version = "5.4.0" @@ -970,6 +1096,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/d8/45e8fc9892a7386d074941429e033adb4640e59ff0780d96a8cf46fe788e/multidict-6.5.0-py3-none-any.whl", hash = "sha256:5634b35f225977605385f56153bd95a7133faffc0ffe12ad26e10517537e8dfc", size = 12181 }, ] +[[package]] +name = "murmurhash" +version = "1.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884 }, + { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855 }, + { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088 }, + { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978 }, + { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956 }, + { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184 }, + { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647 }, + { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338 }, + { url = "https://files.pythonhosted.org/packages/29/2f/ba300b5f04dae0409202d6285668b8a9d3ade43a846abee3ef611cb388d5/murmurhash-1.0.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe50dc70e52786759358fd1471e309b94dddfffb9320d9dfea233c7684c894ba", size = 27861 }, + { url = "https://files.pythonhosted.org/packages/34/02/29c19d268e6f4ea1ed2a462c901eed1ed35b454e2cbc57da592fad663ac6/murmurhash-1.0.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1349a7c23f6092e7998ddc5bd28546cc31a595afc61e9fdb3afc423feec3d7ad", size = 27840 }, + { url = "https://files.pythonhosted.org/packages/e2/63/58e2de2b5232cd294c64092688c422196e74f9fa8b3958bdf02d33df24b9/murmurhash-1.0.15-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ba6d05de2613535b5a9227d4ad8ef40a540465f64660d4a8800634ae10e04f", size = 133080 }, + { url = "https://files.pythonhosted.org/packages/aa/9a/d13e2e9f8ba1ced06840921a50f7cece0a475453284158a3018b72679761/murmurhash-1.0.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fa1b70b3cc2801ab44179c65827bbd12009c68b34e9d9ce7125b6a0bd35af63c", size = 132648 }, + { url = "https://files.pythonhosted.org/packages/b2/e1/47994f1813fa205c84977b0ff51ae6709f8539af052c7491a5f863d82bdc/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:213d710fb6f4ef3bc11abbfad0fa94a75ffb675b7dc158c123471e5de869f9af", size = 131502 }, + { url = "https://files.pythonhosted.org/packages/b9/ea/90c1fd00b4aeb704fb5e84cd666b33ffd7f245155048071ffbb51d2bb57d/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b65a5c4e7f5d71f7ccac2d2b60bdf7092d7976270878cfec59d5a66a533db823", size = 132736 }, + { url = "https://files.pythonhosted.org/packages/00/db/da73462dbfa77f6433b128d2120ba7ba300f8c06dc4f4e022c38d240a5f5/murmurhash-1.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:9aba94c5d841e1904cd110e94ceb7f49cfb60a874bbfb27e0373622998fb7c7c", size = 25682 }, + { url = "https://files.pythonhosted.org/packages/bb/83/032729ef14971b938fbef41ee125fc8800020ee229bd35178b6ede8ee934/murmurhash-1.0.15-cp313-cp313-win_arm64.whl", hash = "sha256:263807eca40d08c7b702413e45cca75ecb5883aa337237dc5addb660f1483378", size = 23370 }, + { url = "https://files.pythonhosted.org/packages/10/83/7547d9205e9bd2f8e5dfd0b682cc9277594f98909f228eb359489baec1df/murmurhash-1.0.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:694fd42a74b7ce257169d14c24aa616aa6cd4ccf8abe50eca0557e08da99d055", size = 29955 }, + { url = "https://files.pythonhosted.org/packages/b7/c7/3afd5de7a5b3ae07fe2d3a3271b327ee1489c58ba2b2f2159bd31a25edb9/murmurhash-1.0.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a2ea4546ba426390beff3cd10db8f0152fdc9072c4f2583ec7d8aa9f3e4ac070", size = 30108 }, + { url = "https://files.pythonhosted.org/packages/02/69/d6637ee67d78ebb2538c00411f28ea5c154886bbe1db16c49435a8a4ab16/murmurhash-1.0.15-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:34e5a91139c40b10f98d0b297907f5d5267b4b1b2e5dd2eb74a021824f751b98", size = 164054 }, + { url = "https://files.pythonhosted.org/packages/ab/4c/89e590165b4c7da6bf941441212a721a270195332d3aacfdfdf527d466ca/murmurhash-1.0.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc35606868a5961cf42e79314ca0bddf5a400ce377b14d83192057928d6252ec", size = 168153 }, + { url = "https://files.pythonhosted.org/packages/07/7a/95c42df0c21d2e413b9fcd17317a7587351daeb264dc29c6aec1fdbd26f8/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:43cc6ac3b91ca0f7a5ae9c063ba4d6c26972c97fd7c25280ecc666413e4c5535", size = 164345 }, + { url = "https://files.pythonhosted.org/packages/d0/22/9d02c880a88b83bb3ce7d6a38fb727373ab78d82e5f3d8d9fc5612219f90/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:847d712136cb462f0e4bd6229ee2d9eb996d8854eb8312dff3d20c8f5181fda5", size = 161990 }, + { url = "https://files.pythonhosted.org/packages/9a/e3/750232524e0dc262e8dcede6536dafc766faadd9a52f1d23746b02948ad8/murmurhash-1.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:2680851af6901dbe66cc4aa7ef8e263de47e6e1b425ae324caa571bdf18f8d58", size = 28812 }, + { url = "https://files.pythonhosted.org/packages/ff/89/4ad9d215ef6ade89f27a72dc4e86b98ef1a43534cc3e6a6900a362a0bf0a/murmurhash-1.0.15-cp313-cp313t-win_arm64.whl", hash = "sha256:189a8de4d657b5da9efd66601b0636330b08262b3a55431f2379097c986995d0", size = 25398 }, + { url = "https://files.pythonhosted.org/packages/1c/69/726df275edf07688146966e15eaaa23168100b933a2e1a29b37eb56c6db8/murmurhash-1.0.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c4280136b738e85ff76b4bdc4341d0b867ee753e73fd8b6994288080c040d0b", size = 28029 }, + { url = "https://files.pythonhosted.org/packages/59/8f/24ecf9061bc2b20933df8aba47c73e904274ea8811c8300cab92f6f82372/murmurhash-1.0.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d4d681f474830489e2ec1d912095cfff027fbaf2baa5414c7e9d25b89f0fab68", size = 27912 }, + { url = "https://files.pythonhosted.org/packages/ba/26/fff3caba25aa3c0622114e03c69fb66c839b22335b04d7cce91a3a126d44/murmurhash-1.0.15-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7e47c5746785db6a43b65fac47b9e63dd71dfbd89a8c92693425b9715e68c6e", size = 131847 }, + { url = "https://files.pythonhosted.org/packages/df/e4/0f2b9fc533467a27afb4e906c33f32d5f637477de87dd94690e0c44335a6/murmurhash-1.0.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e8e674f02a99828c8a671ba99cd03299381b2f0744e6f25c29cadfc6151dc724", size = 132267 }, + { url = "https://files.pythonhosted.org/packages/da/bf/9d1c107989728ec46e25773d503aa54070b32822a18cfa7f9d5f41bc17a5/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:26fd7c7855ac4850ad8737991d7b0e3e501df93ebaf0cf45aa5954303085fdba", size = 131894 }, + { url = "https://files.pythonhosted.org/packages/0d/81/dcf27c71445c0e993b10e33169a098ca60ee702c5c58fcbde205fa6332a6/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb8ebafae60d5f892acff533cc599a359954d8c016a829514cb3f6e9ee10f322", size = 132054 }, + { url = "https://files.pythonhosted.org/packages/bc/32/e874a14b2d2246bd2d16f80f49fad393a3865d4ee7d66d2cae939a67a29a/murmurhash-1.0.15-cp314-cp314-win_amd64.whl", hash = "sha256:898a629bf111f1aeba4437e533b5b836c0a9d2dd12d6880a9c75f6ca13e30e22", size = 26579 }, + { url = "https://files.pythonhosted.org/packages/af/8e/4fca051ed8ae4d23a15aaf0a82b18cb368e8cf84f1e3b474d5749ec46069/murmurhash-1.0.15-cp314-cp314-win_arm64.whl", hash = "sha256:88dc1dd53b7b37c0df1b8b6bce190c12763014492f0269ff7620dc6027f470f4", size = 24341 }, + { url = "https://files.pythonhosted.org/packages/38/9c/c72c2a4edd86aac829337ab9f83cf04cdb15e5d503e4c9a3a243f30a261c/murmurhash-1.0.15-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6cb4e962ec4f928b30c271b2d84e6707eff6d942552765b663743cfa618b294b", size = 30146 }, + { url = "https://files.pythonhosted.org/packages/ac/d7/72b47ebc86436cd0aa1fd4c6e8779521ec389397ac11389990278d0f7a47/murmurhash-1.0.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5678a3ea4fbf0cbaaca2bed9b445f556f294d5f799c67185d05ffcb221a77faf", size = 30141 }, + { url = "https://files.pythonhosted.org/packages/64/bb/6d2f09135079c34dc2d26e961c52742d558b320c61503f273eab6ba743d9/murmurhash-1.0.15-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ef19f38c6b858eef83caf710773db98c8f7eb2193b4c324650c74f3d8ba299e0", size = 163898 }, + { url = "https://files.pythonhosted.org/packages/b9/e2/9c1b462e33f9cb2d632056f07c90b502fc20bd7da50a15d0557343bd2fed/murmurhash-1.0.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22aa3ceaedd2e57078b491ed08852d512b84ff4ff9bb2ff3f9bf0eec7f214c9e", size = 168040 }, + { url = "https://files.pythonhosted.org/packages/e8/73/8694db1408fcdfa73589f7df6c445437ea146986fa1e393ec60d26d6e30c/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bba0e0262c0d08682b028cb963ac477bd9839029486fa1333fc5c01fb6072749", size = 164239 }, + { url = "https://files.pythonhosted.org/packages/2d/f9/8e360bdfc3c44e267e7e046f0e0b9922766da92da26959a6963f597e6bb5/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fd8189ee293a09f30f4931408f40c28ccd42d9de4f66595f8814879339378bc", size = 161811 }, + { url = "https://files.pythonhosted.org/packages/f9/31/97649680595b1096803d877ababb9a67c07f4378f177ec885eea28b9db6d/murmurhash-1.0.15-cp314-cp314t-win_amd64.whl", hash = "sha256:66395b1388f7daa5103db92debe06842ae3be4c0749ef6db68b444518666cdcc", size = 29817 }, + { url = "https://files.pythonhosted.org/packages/76/66/4fce8755f25d77324401886c00017c556be7ca3039575b94037aff905385/murmurhash-1.0.15-cp314-cp314t-win_arm64.whl", hash = "sha256:c22e56c6a0b70598a66e456de5272f76088bc623688da84ef403148a6d41851d", size = 26219 }, +] + [[package]] name = "mypy" version = "1.16.1" @@ -1005,6 +1179,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, ] +[[package]] +name = "nltk" +version = "3.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "joblib" }, + { name = "regex" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/76/3a5e4312c19a028770f86fd7c058cf9f4ec4321c6cf7526bab998a5b683c/nltk-3.9.2.tar.gz", hash = "sha256:0f409e9b069ca4177c1903c3e843eef90c7e92992fa4931ae607da6de49e1419", size = 2887629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/90/81ac364ef94209c100e12579629dc92bf7a709a84af32f8c551b02c07e94/nltk-3.9.2-py3-none-any.whl", hash = "sha256:1e209d2b3009110635ed9709a67a1a3e33a10f799490fa71cf4bec218c11c88a", size = 1513404 }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -1157,6 +1346,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/07/4e8d94f94c7d41ca5ddf8a9695ad87b888104e2fd41a35546c1dc9ca74ac/premailer-3.10.0-py2.py3-none-any.whl", hash = "sha256:021b8196364d7df96d04f9ade51b794d0b77bcc19e998321c515633a2273be1a", size = 19544 }, ] +[[package]] +name = "preshed" +version = "3.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cymem" }, + { name = "murmurhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/34/eb4f5f0f678e152a96e826da867d2f41c4b18a2d589e40e1dd3347219e91/preshed-3.0.12.tar.gz", hash = "sha256:b73f9a8b54ee1d44529cc6018356896cff93d48f755f29c134734d9371c0d685", size = 15027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/f7/ff3aca937eeaee19c52c45ddf92979546e52ed0686e58be4bc09c47e7d88/preshed-3.0.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2779861f5d69480493519ed123a622a13012d1182126779036b99d9d989bf7e9", size = 129958 }, + { url = "https://files.pythonhosted.org/packages/80/24/fd654a9c0f5f3ed1a9b1d8a392f063ae9ca29ad0b462f0732ae0147f7cee/preshed-3.0.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffe1fd7d92f51ed34383e20d8b734780c814ca869cfdb7e07f2d31651f90cdf4", size = 124550 }, + { url = "https://files.pythonhosted.org/packages/71/49/8271c7f680696f4b0880f44357d2a903d649cb9f6e60a1efc97a203104df/preshed-3.0.12-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:91893404858502cc4e856d338fef3d2a4a552135f79a1041c24eb919817c19db", size = 874987 }, + { url = "https://files.pythonhosted.org/packages/a3/a5/ca200187ca1632f1e2c458b72f1bd100fa8b55deecd5d72e1e4ebf09e98c/preshed-3.0.12-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9e06e8f2ba52f183eb9817a616cdebe84a211bb859a2ffbc23f3295d0b189638", size = 866499 }, + { url = "https://files.pythonhosted.org/packages/87/a1/943b61f850c44899910c21996cb542d0ef5931744c6d492fdfdd8457e693/preshed-3.0.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbe8b8a2d4f9af14e8a39ecca524b9de6defc91d8abcc95eb28f42da1c23272c", size = 878064 }, + { url = "https://files.pythonhosted.org/packages/3e/75/d7fff7f1fa3763619aa85d6ba70493a5d9c6e6ea7958a6e8c9d3e6e88bbe/preshed-3.0.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5d0aaac9c5862f5471fddd0c931dc64d3af2efc5fe3eb48b50765adb571243b9", size = 900540 }, + { url = "https://files.pythonhosted.org/packages/e4/12/a2285b78bd097a1e53fb90a1743bc8ce0d35e5b65b6853f3b3c47da398ca/preshed-3.0.12-cp312-cp312-win_amd64.whl", hash = "sha256:0eb8d411afcb1e3b12a0602fb6a0e33140342a732a795251a0ce452aba401dc0", size = 118298 }, + { url = "https://files.pythonhosted.org/packages/0b/34/4e8443fe99206a2fcfc63659969a8f8c8ab184836533594a519f3899b1ad/preshed-3.0.12-cp312-cp312-win_arm64.whl", hash = "sha256:dcd3d12903c9f720a39a5c5f1339f7f46e3ab71279fb7a39776768fb840b6077", size = 104746 }, + { url = "https://files.pythonhosted.org/packages/1e/36/1d3df6f9f37efc34be4ee3013b3bb698b06f1e372f80959851b54d8efdb2/preshed-3.0.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3deb3ab93d50c785eaa7694a8e169eb12d00263a99c91d56511fe943bcbacfb6", size = 128023 }, + { url = "https://files.pythonhosted.org/packages/fb/d4/3ca81f42978da1b81aa57b3e9b5193d8093e187787a3b2511d16b30b7c62/preshed-3.0.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604350001238dab63dc14774ee30c257b5d71c7be976dbecd1f1ed37529f60f", size = 122851 }, + { url = "https://files.pythonhosted.org/packages/17/73/f388398f8d789f69b510272d144a9186d658423f6d3ecc484c0fe392acec/preshed-3.0.12-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04fb860a8aab18d2201f06159337eda5568dc5eed218570d960fad79e783c7d0", size = 835926 }, + { url = "https://files.pythonhosted.org/packages/35/c6/b7170933451cbc27eaefd57b36f61a5e7e7c8da50ae24f819172e0ca8a4d/preshed-3.0.12-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d0c8fcd44996031c46a0aa6773c7b7aa5ee58c3ee87bc05236dacd5599d35063", size = 827294 }, + { url = "https://files.pythonhosted.org/packages/7d/ec/6504730d811c0a375721db2107d31684ec17ee5b7bb3796ecfa41e704d41/preshed-3.0.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b07efc3abd3714ce01cf67db0a2dada6e829ab7def74039d446e49ddb32538c5", size = 838809 }, + { url = "https://files.pythonhosted.org/packages/7e/1a/09d13240c1fbadcc0603e2fe029623045a36c88b4b50b02e7fdc89e3b88e/preshed-3.0.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f184ef184b76e0e4707bce2395008779e4dfa638456b13b18469c2c1a42903a6", size = 861448 }, + { url = "https://files.pythonhosted.org/packages/0d/35/9523160153037ee8337672249449be416ee92236f32602e7dd643767814f/preshed-3.0.12-cp313-cp313-win_amd64.whl", hash = "sha256:ebb3da2dc62ab09e5dc5a00ec38e7f5cdf8741c175714ab4a80773d8ee31b495", size = 117413 }, + { url = "https://files.pythonhosted.org/packages/79/eb/4263e6e896753b8e2ffa93035458165850a5ea81d27e8888afdbfd8fa9c4/preshed-3.0.12-cp313-cp313-win_arm64.whl", hash = "sha256:b36a2cf57a5ca6e78e69b569c92ef3bdbfb00e3a14859e201eec6ab3bdc27085", size = 104041 }, + { url = "https://files.pythonhosted.org/packages/77/39/7b33910b7ba3db9ce1515c39eb4657232913fb171fe701f792ef50726e60/preshed-3.0.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0d8b458dfbd6cc5007d045fa5638231328e3d6f214fd24ab999cc10f8b9097e5", size = 129211 }, + { url = "https://files.pythonhosted.org/packages/32/67/97dceebe0b2b4dd94333e4ec283d38614f92996de615859a952da082890d/preshed-3.0.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8e9196e2ea704243a69df203e0c9185eb7c5c58c3632ba1c1e2e2e0aa3aae3b4", size = 123311 }, + { url = "https://files.pythonhosted.org/packages/4b/6f/f3772f6eaad1eae787f82ffb65a81a4a1993277eacf5a78a29da34608323/preshed-3.0.12-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ffa644e1730012ed435fb9d0c3031ea19a06b11136eff5e9b96b2aa25ec7a5f5", size = 831683 }, + { url = "https://files.pythonhosted.org/packages/1a/93/997d39ca61202486dd06c669b4707a5b8e5d0c2c922db9f7744fd6a12096/preshed-3.0.12-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:39e83a16ce53e4a3c41c091fe4fe1c3d28604e63928040da09ba0c5d5a7ca41e", size = 830035 }, + { url = "https://files.pythonhosted.org/packages/0a/f2/51bf44e3fdbef08d40a832181842cd9b21b11c3f930989f4ff17e9201e12/preshed-3.0.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2ec9bc0baee426303a644c7bf531333d4e7fd06fedf07f62ee09969c208d578d", size = 841728 }, + { url = "https://files.pythonhosted.org/packages/d3/b1/2d0e3d23d9f885f7647654d770227eb13e4d892deb9b0ed50b993d63fb18/preshed-3.0.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7db058f1b4a3d4d51c4c05b379c6cc9c36fcad00160923cb20ca1c7030581ea4", size = 858860 }, + { url = "https://files.pythonhosted.org/packages/e7/57/7c28c7f6f9bfce02796b54f1f6acd2cebb3fa3f14a2dce6fb3c686e3c3a8/preshed-3.0.12-cp314-cp314-win_amd64.whl", hash = "sha256:c87a54a55a2ba98d0c3fd7886295f2825397aff5a7157dcfb89124f6aa2dca41", size = 120325 }, + { url = "https://files.pythonhosted.org/packages/33/c3/df235ca679a08e09103983ec17c668f96abe897eadbe18d635972b43d8a9/preshed-3.0.12-cp314-cp314-win_arm64.whl", hash = "sha256:d9c5f10b4b971d71d163c2416b91b7136eae54ef3183b1742bb5993269af1b18", size = 107393 }, + { url = "https://files.pythonhosted.org/packages/7e/f1/51a2a72381c8aa3aeb8305d88e720c745048527107e649c01b8d49d6b5bf/preshed-3.0.12-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2739a9c57efcfa16466fa6e0257d67f0075a9979dc729585fbadaed7383ab449", size = 137703 }, + { url = "https://files.pythonhosted.org/packages/3f/ab/f3c3d50647f3af6ce6441c596a4f6fb0216d549432ef51f61c0c1744c9b9/preshed-3.0.12-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:364249656bfbf98b4008fac707f35835580ec56207f7cbecdafef6ebb6a595a6", size = 134889 }, + { url = "https://files.pythonhosted.org/packages/54/9a/012dbae28a0b88cd98eae99f87701ffbe3a7d2ea3de345cb8a6a6e1b16cd/preshed-3.0.12-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f933d509ee762a90f62573aaf189eba94dfee478fca13ea2183b2f8a1bb8f7e", size = 911078 }, + { url = "https://files.pythonhosted.org/packages/88/c1/0cd0f8cdb91f63c298320cf946c4b97adfb8e8d3a5d454267410c90fcfaa/preshed-3.0.12-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f73f4e29bf90e58034e6f5fa55e6029f3f2d7c042a7151ed487b49898b0ce887", size = 930506 }, + { url = "https://files.pythonhosted.org/packages/20/1a/cab79b3181b2150eeeb0e2541c2bd4e0830e1e068b8836b24ea23610cec3/preshed-3.0.12-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a61ede0c3d18f1ae128113f785a396351a46f4634beccfdf617b0a86008b154d", size = 900009 }, + { url = "https://files.pythonhosted.org/packages/31/9a/5ea9d6d95d5c07ba70166330a43bff7f0a074d0134eb7984eca6551e8c70/preshed-3.0.12-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eafc08a86f77be78e722d96aa8a3a0aef0e3c7ac2f2ada22186a138e63d4033c", size = 910826 }, + { url = "https://files.pythonhosted.org/packages/92/71/39024f9873ff317eac724b2759e94d013703800d970d51de77ccc6afff7e/preshed-3.0.12-cp314-cp314t-win_amd64.whl", hash = "sha256:fadaad54973b8697d5ef008735e150bd729a127b6497fd2cb068842074a6f3a7", size = 141358 }, + { url = "https://files.pythonhosted.org/packages/9d/0d/431bb85252119f5d2260417fa7d164619b31eed8f1725b364dc0ade43a8e/preshed-3.0.12-cp314-cp314t-win_arm64.whl", hash = "sha256:c0c0d3b66b4c1e40aa6042721492f7b07fc9679ab6c361bc121aa54a1c3ef63f", size = 114839 }, +] + [[package]] name = "propcache" version = "0.3.2" @@ -1403,6 +1636,84 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/67/e60968d3b0e077495a8fee89cf3f2373db98e528288a48f1ee44967f6e8c/redis-6.2.0-py3-none-any.whl", hash = "sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e", size = 278659 }, ] +[[package]] +name = "regex" +version = "2025.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01", size = 414669 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/74/18f04cb53e58e3fb107439699bd8375cf5a835eec81084e0bddbd122e4c2/regex-2025.11.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc8ab71e2e31b16e40868a40a69007bc305e1109bd4658eb6cad007e0bf67c41", size = 489312 }, + { url = "https://files.pythonhosted.org/packages/78/3f/37fcdd0d2b1e78909108a876580485ea37c91e1acf66d3bb8e736348f441/regex-2025.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:22b29dda7e1f7062a52359fca6e58e548e28c6686f205e780b02ad8ef710de36", size = 291256 }, + { url = "https://files.pythonhosted.org/packages/bf/26/0a575f58eb23b7ebd67a45fccbc02ac030b737b896b7e7a909ffe43ffd6a/regex-2025.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a91e4a29938bc1a082cc28fdea44be420bf2bebe2665343029723892eb073e1", size = 288921 }, + { url = "https://files.pythonhosted.org/packages/ea/98/6a8dff667d1af907150432cf5abc05a17ccd32c72a3615410d5365ac167a/regex-2025.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b884f4226602ad40c5d55f52bf91a9df30f513864e0054bad40c0e9cf1afb7", size = 798568 }, + { url = "https://files.pythonhosted.org/packages/64/15/92c1db4fa4e12733dd5a526c2dd2b6edcbfe13257e135fc0f6c57f34c173/regex-2025.11.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e0b11b2b2433d1c39c7c7a30e3f3d0aeeea44c2a8d0bae28f6b95f639927a69", size = 864165 }, + { url = "https://files.pythonhosted.org/packages/f9/e7/3ad7da8cdee1ce66c7cd37ab5ab05c463a86ffeb52b1a25fe7bd9293b36c/regex-2025.11.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87eb52a81ef58c7ba4d45c3ca74e12aa4b4e77816f72ca25258a85b3ea96cb48", size = 912182 }, + { url = "https://files.pythonhosted.org/packages/84/bd/9ce9f629fcb714ffc2c3faf62b6766ecb7a585e1e885eb699bcf130a5209/regex-2025.11.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a12ab1f5c29b4e93db518f5e3872116b7e9b1646c9f9f426f777b50d44a09e8c", size = 803501 }, + { url = "https://files.pythonhosted.org/packages/7c/0f/8dc2e4349d8e877283e6edd6c12bdcebc20f03744e86f197ab6e4492bf08/regex-2025.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7521684c8c7c4f6e88e35ec89680ee1aa8358d3f09d27dfbdf62c446f5d4c695", size = 787842 }, + { url = "https://files.pythonhosted.org/packages/f9/73/cff02702960bc185164d5619c0c62a2f598a6abff6695d391b096237d4ab/regex-2025.11.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7fe6e5440584e94cc4b3f5f4d98a25e29ca12dccf8873679a635638349831b98", size = 858519 }, + { url = "https://files.pythonhosted.org/packages/61/83/0e8d1ae71e15bc1dc36231c90b46ee35f9d52fab2e226b0e039e7ea9c10a/regex-2025.11.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8e026094aa12b43f4fd74576714e987803a315c76edb6b098b9809db5de58f74", size = 850611 }, + { url = "https://files.pythonhosted.org/packages/c8/f5/70a5cdd781dcfaa12556f2955bf170cd603cb1c96a1827479f8faea2df97/regex-2025.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:435bbad13e57eb5606a68443af62bed3556de2f46deb9f7d4237bc2f1c9fb3a0", size = 789759 }, + { url = "https://files.pythonhosted.org/packages/59/9b/7c29be7903c318488983e7d97abcf8ebd3830e4c956c4c540005fcfb0462/regex-2025.11.3-cp312-cp312-win32.whl", hash = "sha256:3839967cf4dc4b985e1570fd8d91078f0c519f30491c60f9ac42a8db039be204", size = 266194 }, + { url = "https://files.pythonhosted.org/packages/1a/67/3b92df89f179d7c367be654ab5626ae311cb28f7d5c237b6bb976cd5fbbb/regex-2025.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:e721d1b46e25c481dc5ded6f4b3f66c897c58d2e8cfdf77bbced84339108b0b9", size = 277069 }, + { url = "https://files.pythonhosted.org/packages/d7/55/85ba4c066fe5094d35b249c3ce8df0ba623cfd35afb22d6764f23a52a1c5/regex-2025.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:64350685ff08b1d3a6fff33f45a9ca183dc1d58bbfe4981604e70ec9801bbc26", size = 270330 }, + { url = "https://files.pythonhosted.org/packages/e1/a7/dda24ebd49da46a197436ad96378f17df30ceb40e52e859fc42cac45b850/regex-2025.11.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c1e448051717a334891f2b9a620fe36776ebf3dd8ec46a0b877c8ae69575feb4", size = 489081 }, + { url = "https://files.pythonhosted.org/packages/19/22/af2dc751aacf88089836aa088a1a11c4f21a04707eb1b0478e8e8fb32847/regex-2025.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b5aca4d5dfd7fbfbfbdaf44850fcc7709a01146a797536a8f84952e940cca76", size = 291123 }, + { url = "https://files.pythonhosted.org/packages/a3/88/1a3ea5672f4b0a84802ee9891b86743438e7c04eb0b8f8c4e16a42375327/regex-2025.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:04d2765516395cf7dda331a244a3282c0f5ae96075f728629287dfa6f76ba70a", size = 288814 }, + { url = "https://files.pythonhosted.org/packages/fb/8c/f5987895bf42b8ddeea1b315c9fedcfe07cadee28b9c98cf50d00adcb14d/regex-2025.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d9903ca42bfeec4cebedba8022a7c97ad2aab22e09573ce9976ba01b65e4361", size = 798592 }, + { url = "https://files.pythonhosted.org/packages/99/2a/6591ebeede78203fa77ee46a1c36649e02df9eaa77a033d1ccdf2fcd5d4e/regex-2025.11.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:639431bdc89d6429f6721625e8129413980ccd62e9d3f496be618a41d205f160", size = 864122 }, + { url = "https://files.pythonhosted.org/packages/94/d6/be32a87cf28cf8ed064ff281cfbd49aefd90242a83e4b08b5a86b38e8eb4/regex-2025.11.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f117efad42068f9715677c8523ed2be1518116d1c49b1dd17987716695181efe", size = 912272 }, + { url = "https://files.pythonhosted.org/packages/62/11/9bcef2d1445665b180ac7f230406ad80671f0fc2a6ffb93493b5dd8cd64c/regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4aecb6f461316adf9f1f0f6a4a1a3d79e045f9b71ec76055a791affa3b285850", size = 803497 }, + { url = "https://files.pythonhosted.org/packages/e5/a7/da0dc273d57f560399aa16d8a68ae7f9b57679476fc7ace46501d455fe84/regex-2025.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b3a5f320136873cc5561098dfab677eea139521cb9a9e8db98b7e64aef44cbc", size = 787892 }, + { url = "https://files.pythonhosted.org/packages/da/4b/732a0c5a9736a0b8d6d720d4945a2f1e6f38f87f48f3173559f53e8d5d82/regex-2025.11.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75fa6f0056e7efb1f42a1c34e58be24072cb9e61a601340cc1196ae92326a4f9", size = 858462 }, + { url = "https://files.pythonhosted.org/packages/0c/f5/a2a03df27dc4c2d0c769220f5110ba8c4084b0bfa9ab0f9b4fcfa3d2b0fc/regex-2025.11.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:dbe6095001465294f13f1adcd3311e50dd84e5a71525f20a10bd16689c61ce0b", size = 850528 }, + { url = "https://files.pythonhosted.org/packages/d6/09/e1cd5bee3841c7f6eb37d95ca91cdee7100b8f88b81e41c2ef426910891a/regex-2025.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:454d9b4ae7881afbc25015b8627c16d88a597479b9dea82b8c6e7e2e07240dc7", size = 789866 }, + { url = "https://files.pythonhosted.org/packages/eb/51/702f5ea74e2a9c13d855a6a85b7f80c30f9e72a95493260193c07f3f8d74/regex-2025.11.3-cp313-cp313-win32.whl", hash = "sha256:28ba4d69171fc6e9896337d4fc63a43660002b7da53fc15ac992abcf3410917c", size = 266189 }, + { url = "https://files.pythonhosted.org/packages/8b/00/6e29bb314e271a743170e53649db0fdb8e8ff0b64b4f425f5602f4eb9014/regex-2025.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:bac4200befe50c670c405dc33af26dad5a3b6b255dd6c000d92fe4629f9ed6a5", size = 277054 }, + { url = "https://files.pythonhosted.org/packages/25/f1/b156ff9f2ec9ac441710764dda95e4edaf5f36aca48246d1eea3f1fd96ec/regex-2025.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:2292cd5a90dab247f9abe892ac584cb24f0f54680c73fcb4a7493c66c2bf2467", size = 270325 }, + { url = "https://files.pythonhosted.org/packages/20/28/fd0c63357caefe5680b8ea052131acbd7f456893b69cc2a90cc3e0dc90d4/regex-2025.11.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1eb1ebf6822b756c723e09f5186473d93236c06c579d2cc0671a722d2ab14281", size = 491984 }, + { url = "https://files.pythonhosted.org/packages/df/ec/7014c15626ab46b902b3bcc4b28a7bae46d8f281fc7ea9c95e22fcaaa917/regex-2025.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1e00ec2970aab10dc5db34af535f21fcf32b4a31d99e34963419636e2f85ae39", size = 292673 }, + { url = "https://files.pythonhosted.org/packages/23/ab/3b952ff7239f20d05f1f99e9e20188513905f218c81d52fb5e78d2bf7634/regex-2025.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a4cb042b615245d5ff9b3794f56be4138b5adc35a4166014d31d1814744148c7", size = 291029 }, + { url = "https://files.pythonhosted.org/packages/21/7e/3dc2749fc684f455f162dcafb8a187b559e2614f3826877d3844a131f37b/regex-2025.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44f264d4bf02f3176467d90b294d59bf1db9fe53c141ff772f27a8b456b2a9ed", size = 807437 }, + { url = "https://files.pythonhosted.org/packages/1b/0b/d529a85ab349c6a25d1ca783235b6e3eedf187247eab536797021f7126c6/regex-2025.11.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7be0277469bf3bd7a34a9c57c1b6a724532a0d235cd0dc4e7f4316f982c28b19", size = 873368 }, + { url = "https://files.pythonhosted.org/packages/7d/18/2d868155f8c9e3e9d8f9e10c64e9a9f496bb8f7e037a88a8bed26b435af6/regex-2025.11.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0d31e08426ff4b5b650f68839f5af51a92a5b51abd8554a60c2fbc7c71f25d0b", size = 914921 }, + { url = "https://files.pythonhosted.org/packages/2d/71/9d72ff0f354fa783fe2ba913c8734c3b433b86406117a8db4ea2bf1c7a2f/regex-2025.11.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e43586ce5bd28f9f285a6e729466841368c4a0353f6fd08d4ce4630843d3648a", size = 812708 }, + { url = "https://files.pythonhosted.org/packages/e7/19/ce4bf7f5575c97f82b6e804ffb5c4e940c62609ab2a0d9538d47a7fdf7d4/regex-2025.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0f9397d561a4c16829d4e6ff75202c1c08b68a3bdbfe29dbfcdb31c9830907c6", size = 795472 }, + { url = "https://files.pythonhosted.org/packages/03/86/fd1063a176ffb7b2315f9a1b08d17b18118b28d9df163132615b835a26ee/regex-2025.11.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:dd16e78eb18ffdb25ee33a0682d17912e8cc8a770e885aeee95020046128f1ce", size = 868341 }, + { url = "https://files.pythonhosted.org/packages/12/43/103fb2e9811205e7386366501bc866a164a0430c79dd59eac886a2822950/regex-2025.11.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:ffcca5b9efe948ba0661e9df0fa50d2bc4b097c70b9810212d6b62f05d83b2dd", size = 854666 }, + { url = "https://files.pythonhosted.org/packages/7d/22/e392e53f3869b75804762c7c848bd2dd2abf2b70fb0e526f58724638bd35/regex-2025.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c56b4d162ca2b43318ac671c65bd4d563e841a694ac70e1a976ac38fcf4ca1d2", size = 799473 }, + { url = "https://files.pythonhosted.org/packages/4f/f9/8bd6b656592f925b6845fcbb4d57603a3ac2fb2373344ffa1ed70aa6820a/regex-2025.11.3-cp313-cp313t-win32.whl", hash = "sha256:9ddc42e68114e161e51e272f667d640f97e84a2b9ef14b7477c53aac20c2d59a", size = 268792 }, + { url = "https://files.pythonhosted.org/packages/e5/87/0e7d603467775ff65cd2aeabf1b5b50cc1c3708556a8b849a2fa4dd1542b/regex-2025.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7a7c7fdf755032ffdd72c77e3d8096bdcb0eb92e89e17571a196f03d88b11b3c", size = 280214 }, + { url = "https://files.pythonhosted.org/packages/8d/d0/2afc6f8e94e2b64bfb738a7c2b6387ac1699f09f032d363ed9447fd2bb57/regex-2025.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:df9eb838c44f570283712e7cff14c16329a9f0fb19ca492d21d4b7528ee6821e", size = 271469 }, + { url = "https://files.pythonhosted.org/packages/31/e9/f6e13de7e0983837f7b6d238ad9458800a874bf37c264f7923e63409944c/regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9697a52e57576c83139d7c6f213d64485d3df5bf84807c35fa409e6c970801c6", size = 489089 }, + { url = "https://files.pythonhosted.org/packages/a3/5c/261f4a262f1fa65141c1b74b255988bd2fa020cc599e53b080667d591cfc/regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e18bc3f73bd41243c9b38a6d9f2366cd0e0137a9aebe2d8ff76c5b67d4c0a3f4", size = 291059 }, + { url = "https://files.pythonhosted.org/packages/8e/57/f14eeb7f072b0e9a5a090d1712741fd8f214ec193dba773cf5410108bb7d/regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:61a08bcb0ec14ff4e0ed2044aad948d0659604f824cbd50b55e30b0ec6f09c73", size = 288900 }, + { url = "https://files.pythonhosted.org/packages/3c/6b/1d650c45e99a9b327586739d926a1cd4e94666b1bd4af90428b36af66dc7/regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9c30003b9347c24bcc210958c5d167b9e4f9be786cb380a7d32f14f9b84674f", size = 799010 }, + { url = "https://files.pythonhosted.org/packages/99/ee/d66dcbc6b628ce4e3f7f0cbbb84603aa2fc0ffc878babc857726b8aab2e9/regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4e1e592789704459900728d88d41a46fe3969b82ab62945560a31732ffc19a6d", size = 864893 }, + { url = "https://files.pythonhosted.org/packages/bf/2d/f238229f1caba7ac87a6c4153d79947fb0261415827ae0f77c304260c7d3/regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6538241f45eb5a25aa575dbba1069ad786f68a4f2773a29a2bd3dd1f9de787be", size = 911522 }, + { url = "https://files.pythonhosted.org/packages/bd/3d/22a4eaba214a917c80e04f6025d26143690f0419511e0116508e24b11c9b/regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce22519c989bb72a7e6b36a199384c53db7722fe669ba891da75907fe3587db", size = 803272 }, + { url = "https://files.pythonhosted.org/packages/84/b1/03188f634a409353a84b5ef49754b97dbcc0c0f6fd6c8ede505a8960a0a4/regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:66d559b21d3640203ab9075797a55165d79017520685fb407b9234d72ab63c62", size = 787958 }, + { url = "https://files.pythonhosted.org/packages/99/6a/27d072f7fbf6fadd59c64d210305e1ff865cc3b78b526fd147db768c553b/regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:669dcfb2e38f9e8c69507bace46f4889e3abbfd9b0c29719202883c0a603598f", size = 859289 }, + { url = "https://files.pythonhosted.org/packages/9a/70/1b3878f648e0b6abe023172dacb02157e685564853cc363d9961bcccde4e/regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:32f74f35ff0f25a5021373ac61442edcb150731fbaa28286bbc8bb1582c89d02", size = 850026 }, + { url = "https://files.pythonhosted.org/packages/dd/d5/68e25559b526b8baab8e66839304ede68ff6727237a47727d240006bd0ff/regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e6c7a21dffba883234baefe91bc3388e629779582038f75d2a5be918e250f0ed", size = 789499 }, + { url = "https://files.pythonhosted.org/packages/fc/df/43971264857140a350910d4e33df725e8c94dd9dee8d2e4729fa0d63d49e/regex-2025.11.3-cp314-cp314-win32.whl", hash = "sha256:795ea137b1d809eb6836b43748b12634291c0ed55ad50a7d72d21edf1cd565c4", size = 271604 }, + { url = "https://files.pythonhosted.org/packages/01/6f/9711b57dc6894a55faf80a4c1b5aa4f8649805cb9c7aef46f7d27e2b9206/regex-2025.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f95fbaa0ee1610ec0fc6b26668e9917a582ba80c52cc6d9ada15e30aa9ab9ad", size = 280320 }, + { url = "https://files.pythonhosted.org/packages/f1/7e/f6eaa207d4377481f5e1775cdeb5a443b5a59b392d0065f3417d31d80f87/regex-2025.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:dfec44d532be4c07088c3de2876130ff0fbeeacaa89a137decbbb5f665855a0f", size = 273372 }, + { url = "https://files.pythonhosted.org/packages/c3/06/49b198550ee0f5e4184271cee87ba4dfd9692c91ec55289e6282f0f86ccf/regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ba0d8a5d7f04f73ee7d01d974d47c5834f8a1b0224390e4fe7c12a3a92a78ecc", size = 491985 }, + { url = "https://files.pythonhosted.org/packages/ce/bf/abdafade008f0b1c9da10d934034cb670432d6cf6cbe38bbb53a1cfd6cf8/regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:442d86cf1cfe4faabf97db7d901ef58347efd004934da045c745e7b5bd57ac49", size = 292669 }, + { url = "https://files.pythonhosted.org/packages/f9/ef/0c357bb8edbd2ad8e273fcb9e1761bc37b8acbc6e1be050bebd6475f19c1/regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fd0a5e563c756de210bb964789b5abe4f114dacae9104a47e1a649b910361536", size = 291030 }, + { url = "https://files.pythonhosted.org/packages/79/06/edbb67257596649b8fb088d6aeacbcb248ac195714b18a65e018bf4c0b50/regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf3490bcbb985a1ae97b2ce9ad1c0f06a852d5b19dde9b07bdf25bf224248c95", size = 807674 }, + { url = "https://files.pythonhosted.org/packages/f4/d9/ad4deccfce0ea336296bd087f1a191543bb99ee1c53093dcd4c64d951d00/regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3809988f0a8b8c9dcc0f92478d6501fac7200b9ec56aecf0ec21f4a2ec4b6009", size = 873451 }, + { url = "https://files.pythonhosted.org/packages/13/75/a55a4724c56ef13e3e04acaab29df26582f6978c000ac9cd6810ad1f341f/regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f4ff94e58e84aedb9c9fce66d4ef9f27a190285b451420f297c9a09f2b9abee9", size = 914980 }, + { url = "https://files.pythonhosted.org/packages/67/1e/a1657ee15bd9116f70d4a530c736983eed997b361e20ecd8f5ca3759d5c5/regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eb542fd347ce61e1321b0a6b945d5701528dca0cd9759c2e3bb8bd57e47964d", size = 812852 }, + { url = "https://files.pythonhosted.org/packages/b8/6f/f7516dde5506a588a561d296b2d0044839de06035bb486b326065b4c101e/regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2d5919075a1f2e413c00b056ea0c2f065b3f5fe83c3d07d325ab92dce51d6", size = 795566 }, + { url = "https://files.pythonhosted.org/packages/d9/dd/3d10b9e170cc16fb34cb2cef91513cf3df65f440b3366030631b2984a264/regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3f8bf11a4827cc7ce5a53d4ef6cddd5ad25595d3c1435ef08f76825851343154", size = 868463 }, + { url = "https://files.pythonhosted.org/packages/f5/8e/935e6beff1695aa9085ff83195daccd72acc82c81793df480f34569330de/regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:22c12d837298651e5550ac1d964e4ff57c3f56965fc1812c90c9fb2028eaf267", size = 854694 }, + { url = "https://files.pythonhosted.org/packages/92/12/10650181a040978b2f5720a6a74d44f841371a3d984c2083fc1752e4acf6/regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ba394a3dda9ad41c7c780f60f6e4a70988741415ae96f6d1bf6c239cf01379", size = 799691 }, + { url = "https://files.pythonhosted.org/packages/67/90/8f37138181c9a7690e7e4cb388debbd389342db3c7381d636d2875940752/regex-2025.11.3-cp314-cp314t-win32.whl", hash = "sha256:4bf146dca15cdd53224a1bf46d628bd7590e4a07fbb69e720d561aea43a32b38", size = 274583 }, + { url = "https://files.pythonhosted.org/packages/8f/cd/867f5ec442d56beb56f5f854f40abcfc75e11d10b11fdb1869dd39c63aaf/regex-2025.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:adad1a1bcf1c9e76346e091d22d23ac54ef28e1365117d99521631078dfec9de", size = 284286 }, + { url = "https://files.pythonhosted.org/packages/20/31/32c0c4610cbc070362bf1d2e4ea86d1ea29014d400a6d6c2486fcfd57766/regex-2025.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c54f768482cef41e219720013cd05933b6f971d9562544d691c68699bf2b6801", size = 274741 }, +] + [[package]] name = "requests" version = "2.32.4" @@ -1470,6 +1781,101 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/33/4d3e79e4a84533d6cd526bfb42c020a23256ae5e4265d858bd1287831f7d/ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b", size = 10724946 }, ] +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818 }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997 }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381 }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296 }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256 }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382 }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042 }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180 }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660 }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057 }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731 }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852 }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094 }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436 }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749 }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906 }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836 }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236 }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593 }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007 }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043 }, + { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986 }, + { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814 }, + { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795 }, + { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476 }, + { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692 }, + { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345 }, + { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975 }, + { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926 }, + { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014 }, + { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856 }, + { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306 }, + { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371 }, + { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877 }, + { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103 }, + { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756 }, + { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566 }, + { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877 }, + { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366 }, + { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931 }, + { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081 }, + { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244 }, + { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753 }, + { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912 }, + { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371 }, + { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477 }, + { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678 }, + { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178 }, + { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246 }, + { url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469 }, + { url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043 }, + { url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952 }, + { url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512 }, + { url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639 }, + { url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729 }, + { url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251 }, + { url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681 }, + { url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423 }, + { url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027 }, + { url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379 }, + { url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052 }, + { url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183 }, + { url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174 }, + { url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852 }, + { url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595 }, + { url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269 }, + { url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779 }, + { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128 }, + { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127 }, +] + [[package]] name = "sentry-sdk" version = "2.30.0" @@ -1483,6 +1889,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/99/31ac6faaae33ea698086692638f58d14f121162a8db0039e68e94135e7f1/sentry_sdk-2.30.0-py2.py3-none-any.whl", hash = "sha256:59391db1550662f746ea09b483806a631c3ae38d6340804a1a4c0605044f6877", size = 343149 }, ] +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -1513,6 +1928,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/bb/f71c4b7d7e7eb3fc1e8c0458a8979b912f40b58002b9fbf37729b8cb464b/slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36", size = 14670 }, ] +[[package]] +name = "smart-open" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/9a/0a7acb748b86e2922982366d780ca4b16c33f7246fa5860d26005c97e4f3/smart_open-7.5.0.tar.gz", hash = "sha256:f394b143851d8091011832ac8113ea4aba6b92e6c35f6e677ddaaccb169d7cb9", size = 53920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/95/bc978be7ea0babf2fb48a414b6afaad414c6a9e8b1eafc5b8a53c030381a/smart_open-7.5.0-py3-none-any.whl", hash = "sha256:87e695c5148bbb988f15cec00971602765874163be85acb1c9fb8abc012e6599", size = 63940 }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1531,6 +1958,76 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 }, ] +[[package]] +name = "spacy" +version = "3.8.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "catalogue" }, + { name = "cymem" }, + { name = "jinja2" }, + { name = "murmurhash" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "preshed" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "spacy-legacy" }, + { name = "spacy-loggers" }, + { name = "srsly" }, + { name = "thinc" }, + { name = "tqdm" }, + { name = "typer-slim" }, + { name = "wasabi" }, + { name = "weasel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/9f/424244b0e2656afc9ff82fb7a96931a47397bfce5ba382213827b198312a/spacy-3.8.11.tar.gz", hash = "sha256:54e1e87b74a2f9ea807ffd606166bf29ac45e2bd81ff7f608eadc7b05787d90d", size = 1326804 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/fb/01eadf4ba70606b3054702dc41fc2ccf7d70fb14514b3cd57f0ff78ebea8/spacy-3.8.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aa1ee8362074c30098feaaf2dd888c829a1a79c4311eec1b117a0a61f16fa6dd", size = 6073726 }, + { url = "https://files.pythonhosted.org/packages/3a/f8/07b03a2997fc2621aaeafae00af50f55522304a7da6926b07027bb6d0709/spacy-3.8.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:75a036d04c2cf11d6cb566c0a689860cc5a7a75b439e8fea1b3a6b673dabf25d", size = 5724702 }, + { url = "https://files.pythonhosted.org/packages/13/0c/c4fa0f379dbe3258c305d2e2df3760604a9fcd71b34f8f65c23e43f4cf55/spacy-3.8.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cb599d2747d4a59a5f90e8a453c149b13db382a8297925cf126333141dbc4f7", size = 32727774 }, + { url = "https://files.pythonhosted.org/packages/ce/8e/6a4ba82bed480211ebdf5341b0f89e7271b454307525ac91b5e447825914/spacy-3.8.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:94632e302ad2fb79dc285bf1e9e4d4a178904d5c67049e0e02b7fb4a77af85c4", size = 33215053 }, + { url = "https://files.pythonhosted.org/packages/a6/bc/44d863d248e9d7358c76a0aa8b3f196b8698df520650ed8de162e18fbffb/spacy-3.8.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aeca6cf34009d48cda9fb1bbfb532469e3d643817241a73e367b34ab99a5806f", size = 32074195 }, + { url = "https://files.pythonhosted.org/packages/6f/7d/0b115f3f16e1dd2d3f99b0f89497867fc11c41aed94f4b7a4367b4b54136/spacy-3.8.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:368a79b8df925b15d89dccb5e502039446fb2ce93cf3020e092d5b962c3349b9", size = 32996143 }, + { url = "https://files.pythonhosted.org/packages/7d/48/7e9581b476df76aaf9ee182888d15322e77c38b0bbbd5e80160ba0bddd4c/spacy-3.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:88d65941a87f58d75afca1785bd64d01183a92f7269dcbcf28bd9d6f6a77d1a7", size = 14217511 }, + { url = "https://files.pythonhosted.org/packages/7b/1f/307a16f32f90aa5ee7ad8d29ff8620a57132b80a4c8c536963d46d192e1a/spacy-3.8.11-cp312-cp312-win_arm64.whl", hash = "sha256:97b865d6d3658e2ab103a67d6c8a2d678e193e84a07f40d9938565b669ceee39", size = 13614446 }, + { url = "https://files.pythonhosted.org/packages/ed/5c/3f07cff8bc478fcf48a915ca9fe8637486a1ec676587ed3e6fd775423301/spacy-3.8.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea4adeb399636059925be085c5bb852c1f3a2ebe1c2060332cbad6257d223bbc", size = 6051355 }, + { url = "https://files.pythonhosted.org/packages/6d/44/4671e8098b62befec69c7848538a0824086559f74065284bbd57a5747781/spacy-3.8.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd785e6bd85a58fa037da0c18fcd7250e2daecdfc30464d3882912529d1ad588", size = 5700468 }, + { url = "https://files.pythonhosted.org/packages/0c/98/5708bdfb39f94af0655568e14d953886117e18bd04c3aa3ab5ff1a60ea89/spacy-3.8.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:598c177054eb6196deed03cac6fb7a3229f4789719ad0c9f7483f9491e375749", size = 32521877 }, + { url = "https://files.pythonhosted.org/packages/c6/1f/731beb48f2c7415a71e2f655876fea8a0b3a6798be3d4d51b794f939623d/spacy-3.8.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a5a449ed3f2d03399481870b776f3ec61f2b831812d63dc1acedf6da70e5ab03", size = 32848355 }, + { url = "https://files.pythonhosted.org/packages/47/6b/f3d131d3f9bb1c7de4f355a12adcd0a5fa77f9f624711ddd0f19c517e88b/spacy-3.8.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a6c35c2cb93bade9b7360d1f9db608a066246a41301bb579309efb50764ba55b", size = 31764944 }, + { url = "https://files.pythonhosted.org/packages/72/bf/37ea8134667a4f2787b5f0e0146f2e8df1fb36ab67d598ad06eb5ed2e7db/spacy-3.8.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0156ae575b20290021573faa1fed8a82b11314e9a1c28f034713359a5240a325", size = 32718517 }, + { url = "https://files.pythonhosted.org/packages/79/fe/436435dfa93cc355ed511f21cf3cda5302b7aa29716457317eb07f1cf2da/spacy-3.8.11-cp313-cp313-win_amd64.whl", hash = "sha256:6f39cf36f86bd6a8882076f86ca80f246c73aa41d7ebc8679fbbe41b6f8ec045", size = 14211913 }, + { url = "https://files.pythonhosted.org/packages/c8/23/f89cfa51f54aa5e9c6c7a37f8bf4952d678f0902a5e1d81dfda33a94bfb2/spacy-3.8.11-cp313-cp313-win_arm64.whl", hash = "sha256:9a7151eee0814a5ced36642b42b1ecc8f98ac7225f3e378fb9f862ffbe84b8bf", size = 13605169 }, + { url = "https://files.pythonhosted.org/packages/d7/78/ddeb09116b593f3cccc7eb489a713433076b11cf8cdfb98aec641b73a2c2/spacy-3.8.11-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:43c24d19a3f85bde0872935294a31fd9b3a6db3f92bb2b75074177cd3acec03f", size = 6067734 }, + { url = "https://files.pythonhosted.org/packages/65/bb/1bb630250dc70e00fa3821879c6e2cb65c19425aba38840d3484061285c1/spacy-3.8.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b6158c21da57b8373d2d1afb2b73977c4bc4235d2563e7788d44367fc384939a", size = 5732963 }, + { url = "https://files.pythonhosted.org/packages/7a/56/c58071b3db23932ab2b934af3462a958e7edf472da9668e4869fe2a2199e/spacy-3.8.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1c0bd1bde1d91f1d7a44774ca4ca3fcf064946b72599a8eb34c25e014362ace1", size = 32447290 }, + { url = "https://files.pythonhosted.org/packages/34/eb/d3947efa2b46848372e89ced8371671d77219612a3eebef15db5690aa4d2/spacy-3.8.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:99b767c41a772e544cf2d48e0808764f42f17eb2fd6188db4a729922ff7f0c1e", size = 32488011 }, + { url = "https://files.pythonhosted.org/packages/04/9e/8c6c01558b62388557247e553e48874f52637a5648b957ed01fbd628391d/spacy-3.8.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3c500f04c164e4366a1163a61bf39fd50f0c63abdb1fc17991281ec52a54ab4", size = 31731340 }, + { url = "https://files.pythonhosted.org/packages/23/1f/21812ec34b187ef6ba223389760dfea09bbe27d2b84b553c5205576b4ac2/spacy-3.8.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a2bfe45c0c1530eaabc68f5434c52b1be8df10d5c195c54d4dc2e70cea97dc65", size = 32478557 }, + { url = "https://files.pythonhosted.org/packages/f3/16/a0c9174a232dfe7b48281c05364957e2c6d0f80ef26b67ce8d28a49c2d91/spacy-3.8.11-cp314-cp314-win_amd64.whl", hash = "sha256:45d0bbc8442d18dcea9257be0d1ab26e884067e038b1fa133405bf2f20c74edf", size = 14396041 }, + { url = "https://files.pythonhosted.org/packages/aa/d0/a6aad5b73d523e4686474b0cfcf46f37f3d7a18765be5c1f56c1dcee4c18/spacy-3.8.11-cp314-cp314-win_arm64.whl", hash = "sha256:90a12961ecc44e0195fd42db9f0ce4aade17e6fe03f8ab98d4549911d9e6f992", size = 13823760 }, +] + +[[package]] +name = "spacy-legacy" +version = "3.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971 }, +] + +[[package]] +name = "spacy-loggers" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343 }, +] + [[package]] name = "sqlalchemy" version = "2.0.41" @@ -1560,6 +2057,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224 }, ] +[[package]] +name = "srsly" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "catalogue" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/77/5633c4ba65e3421b72b5b4bd93aa328360b351b3a1e5bf3c90eb224668e5/srsly-2.5.2.tar.gz", hash = "sha256:4092bc843c71b7595c6c90a0302a197858c5b9fe43067f62ae6a45bc3baa1c19", size = 492055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/1c/21f658d98d602a559491b7886c7ca30245c2cd8987ff1b7709437c0f74b1/srsly-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f92b4f883e6be4ca77f15980b45d394d310f24903e25e1b2c46df783c7edcce", size = 656161 }, + { url = "https://files.pythonhosted.org/packages/2f/a2/bc6fd484ed703857043ae9abd6c9aea9152f9480a6961186ee6c1e0c49e8/srsly-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ac4790a54b00203f1af5495b6b8ac214131139427f30fcf05cf971dde81930eb", size = 653237 }, + { url = "https://files.pythonhosted.org/packages/ab/ea/e3895da29a15c8d325e050ad68a0d1238eece1d2648305796adf98dcba66/srsly-2.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce5c6b016050857a7dd365c9dcdd00d96e7ac26317cfcb175db387e403de05bf", size = 1174418 }, + { url = "https://files.pythonhosted.org/packages/a6/a5/21996231f53ee97191d0746c3a672ba33a4d86a19ffad85a1c0096c91c5f/srsly-2.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:539c6d0016e91277b5e9be31ebed03f03c32580d49c960e4a92c9003baecf69e", size = 1183089 }, + { url = "https://files.pythonhosted.org/packages/7b/df/eb17aa8e4a828e8df7aa7dc471295529d9126e6b710f1833ebe0d8568a8e/srsly-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f24b2c4f4c29da04083f09158543eb3f8893ba0ac39818693b3b259ee8044f0", size = 1122594 }, + { url = "https://files.pythonhosted.org/packages/80/74/1654a80e6c8ec3ee32370ea08a78d3651e0ba1c4d6e6be31c9efdb9a2d10/srsly-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d34675047460a3f6999e43478f40d9b43917ea1e93a75c41d05bf7648f3e872d", size = 1139594 }, + { url = "https://files.pythonhosted.org/packages/73/aa/8393344ca7f0e81965febba07afc5cad68335ed0426408d480b861ab915b/srsly-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:81fd133ba3c66c07f0e3a889d2b4c852984d71ea833a665238a9d47d8e051ba5", size = 654750 }, + { url = "https://files.pythonhosted.org/packages/c2/c5/dc29e65419692444253ea549106be156c5911041f16791f3b62fb90c14f2/srsly-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d976d6ae8e66006797b919e3d58533dce64cd48a5447a8ff7277f9b0505b0185", size = 654723 }, + { url = "https://files.pythonhosted.org/packages/80/8c/8111e7e8c766b47b5a5f9864f27f532cf6bb92837a3e277eb297170bd6af/srsly-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:24f52ecd27409ea24ba116ee9f07a2bb1c4b9ba11284b32a0bf2ca364499d1c1", size = 651651 }, + { url = "https://files.pythonhosted.org/packages/45/de/3f99d4e44af427ee09004df6586d0746640536b382c948f456be027c599b/srsly-2.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b0667ce1effb32a57522db10705db7c78d144547fcacc8a06df62c4bb7f96e", size = 1158012 }, + { url = "https://files.pythonhosted.org/packages/c3/2f/66044ef5a10a487652913c1a7f32396cb0e9e32ecfc3fdc0a0bc0382e703/srsly-2.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60782f6f79c340cdaf1ba7cbaa1d354a0f7c8f86b285f1e14e75edb51452895a", size = 1163258 }, + { url = "https://files.pythonhosted.org/packages/74/6b/698834048672b52937e8cf09b554adb81b106c0492f9bc62e41e3b46a69b/srsly-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec51abb1b58e1e6c689714104aeeba6290c40c0bfad0243b9b594df89f05881", size = 1112214 }, + { url = "https://files.pythonhosted.org/packages/85/17/1efc70426be93d32a3c6c5c12d795eb266a9255d8b537fcb924a3de57fcb/srsly-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:76464e45f73afd20c2c34d2ef145bf788afc32e7d45f36f6393ed92a85189ed3", size = 1130687 }, + { url = "https://files.pythonhosted.org/packages/e2/25/07f8c8a778bc0447ee15e37089b08af81b24fcc1d4a2c09eff4c3a79b241/srsly-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:009424a96d763951e4872b36ba38823f973bef094a1adbc11102e23e8d1ef429", size = 653128 }, + { url = "https://files.pythonhosted.org/packages/39/03/3d248f538abc141d9c7ed1aa10e61506c0f95515a61066ee90e888f0cd8f/srsly-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a0911dcf1026f982bd8c5f73e1c43f1bc868416408fcbc1f3d99eb59475420c5", size = 659866 }, + { url = "https://files.pythonhosted.org/packages/43/22/0fcff4c977ddfb32a6b10f33d904868b16ce655323756281f973c5a3449e/srsly-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0ff3ac2942aee44235ca3c7712fcbd6e0d1a092e10ee16e07cef459ed6d7f65", size = 655868 }, + { url = "https://files.pythonhosted.org/packages/1b/c1/e158f26a5597ac31b0f306d2584411ec1f984058e8171d76c678bf439e96/srsly-2.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:78385fb75e1bf7b81ffde97555aee094d270a5e0ea66f8280f6e95f5bb508b3e", size = 1156753 }, + { url = "https://files.pythonhosted.org/packages/d9/bc/2001cd27fd6ecdae79050cf6b655ca646dedc0b69a756e6a87993cc47314/srsly-2.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2e9943b70bd7655b9eefca77aab838c3b7acea00c9dd244fd218a43dc61c518b", size = 1157916 }, + { url = "https://files.pythonhosted.org/packages/5c/dd/56f563c2d0cd76c8fd22fb9f1589f18af50b54d31dd3323ceb05fe7999b8/srsly-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7d235a2bb08f5240e47c6aba4d9688b228d830fbf4c858388d9c151a10039e6d", size = 1114582 }, + { url = "https://files.pythonhosted.org/packages/2e/e6/e155facc965a119e6f5d32b7e95082cadfb62cc5d97087d53db93f3a5a98/srsly-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ad94ee18b3042a6cdfdc022556e2ed9a7b52b876de86fe334c4d8ec58d59ecbc", size = 1129875 }, + { url = "https://files.pythonhosted.org/packages/b6/3a/c12a4d556349c9f491b0a9d27968483f22934d2a02dfb14fb1d3a7d9b837/srsly-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:6658467165d8fa4aec0f5f6e2da8fe977e087eaff13322b0ff20450f0d762cee", size = 658858 }, + { url = "https://files.pythonhosted.org/packages/70/db/52510cbf478ab3ae8cb6c95aff3a499f2ded69df6d84df8a293630e9f10a/srsly-2.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:517e907792acf574979752ce33e7b15985c95d4ed7d8e38ee47f36063dc985ac", size = 666843 }, + { url = "https://files.pythonhosted.org/packages/3d/da/4257b1d4c3eb005ecd135414398c033c13c4d3dffb715f63c3acd63d8d1a/srsly-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5602797e6f87bf030b11ad356828142367c5c81e923303b5ff2a88dfb12d1e4", size = 663981 }, + { url = "https://files.pythonhosted.org/packages/c6/f8/1ec5edd7299d8599def20fc3440372964f7c750022db8063e321747d1cf8/srsly-2.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3452306118f8604daaaac6d770ee8f910fca449e8f066dcc96a869b43ece5340", size = 1267808 }, + { url = "https://files.pythonhosted.org/packages/3e/5c/4ef9782c9a3f331ef80e1ea8fc6fab50fc3d32ae61a494625d2c5f30cc4c/srsly-2.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e2d59f1ce00d73397a7f5b9fc33e76d17816ce051abe4eb920cec879d2a9d4f4", size = 1252838 }, + { url = "https://files.pythonhosted.org/packages/39/da/d13cfc662d71eec3ccd4072433bf435bd2e11e1c5340150b4cc43fad46f4/srsly-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ebda3736651d33d92b17e26c525ba8d0b94d0ee379c9f92e8d937ba89dca8978", size = 1244558 }, + { url = "https://files.pythonhosted.org/packages/26/50/92bf62dfb19532b823ef52251bb7003149e1d4a89f50a63332c8ff5f894b/srsly-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:74a9338fcc044f4bdc7113b2d9db2db8e0a263c69f1cba965acf12c845d8b365", size = 1244935 }, + { url = "https://files.pythonhosted.org/packages/95/81/6ea10ef6228ce4438a240c803639f7ccf5eae3469fbc015f33bd84aa8df1/srsly-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:8e2b9058623c44b07441eb0d711dfdf6302f917f0634d0a294cae37578dcf899", size = 676105 }, +] + [[package]] name = "starlette" version = "0.46.2" @@ -1581,6 +2117,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248 }, ] +[[package]] +name = "thinc" +version = "8.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blis" }, + { name = "catalogue" }, + { name = "confection" }, + { name = "cymem" }, + { name = "murmurhash" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "preshed" }, + { name = "pydantic" }, + { name = "setuptools" }, + { name = "srsly" }, + { name = "wasabi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/3a/2d0f0be132b9faaa6d56f04565ae122684273e4bf4eab8dee5f48dc00f68/thinc-8.3.10.tar.gz", hash = "sha256:5a75109f4ee1c968fc055ce651a17cb44b23b000d9e95f04a4d047ab3cb3e34e", size = 194196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/34/ba3b386d92edf50784b60ee34318d47c7f49c198268746ef7851c5bbe8cf/thinc-8.3.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51bc6ef735bdbcab75ab2916731b8f61f94c66add6f9db213d900d3c6a244f95", size = 794509 }, + { url = "https://files.pythonhosted.org/packages/07/f3/9f52d18115cd9d8d7b2590d226cb2752d2a5ffec61576b19462b48410184/thinc-8.3.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4f48b4d346915f98e9722c0c50ef911cc16c6790a2b7afebc6e1a2c96a6ce6c6", size = 741084 }, + { url = "https://files.pythonhosted.org/packages/ad/9c/129c2b740c4e3d3624b6fb3dec1577ef27cb804bc1647f9bc3e1801ea20c/thinc-8.3.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5003f4db2db22cc8d686db8db83509acc3c50f4c55ebdcb2bbfcc1095096f7d2", size = 3846337 }, + { url = "https://files.pythonhosted.org/packages/22/d2/738cf188dea8240c2be081c83ea47270fea585eba446171757d2cdb9b675/thinc-8.3.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b12484c3ed0632331fada2c334680dd6bc35972d0717343432dfc701f04a9b4c", size = 3901216 }, + { url = "https://files.pythonhosted.org/packages/22/92/32f66eb9b1a29b797bf378a0874615d810d79eefca1d6c736c5ca3f8b918/thinc-8.3.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8677c446d3f9b97a465472c58683b785b25dfcf26c683e3f4e8f8c7c188e4362", size = 4827286 }, + { url = "https://files.pythonhosted.org/packages/c4/5f/7ceae1e1f2029efd67ed88e23cd6dc13a5ee647cdc2b35113101b2a62c10/thinc-8.3.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:759c385ac08dcf950238b60b96a28f9c04618861141766928dff4a51b1679b25", size = 5024421 }, + { url = "https://files.pythonhosted.org/packages/0b/66/30f9d8d41049b78bc614213d492792fbcfeb1b28642adf661c42110a7ebd/thinc-8.3.10-cp312-cp312-win_amd64.whl", hash = "sha256:bf3f188c3fa1fdcefd547d1f90a1245c29025d6d0e3f71d7fdf21dad210b990c", size = 1718631 }, + { url = "https://files.pythonhosted.org/packages/f8/44/32e2a5018a1165a304d25eb9b1c74e5310da19a533a35331e8d824dc6a88/thinc-8.3.10-cp312-cp312-win_arm64.whl", hash = "sha256:234b7e57a6ef4e0260d99f4e8fdc328ed12d0ba9bbd98fdaa567294a17700d1c", size = 1642224 }, + { url = "https://files.pythonhosted.org/packages/53/fc/17a2818d1f460b8c4f33b8bd3f21b19d263a647bfd23b572768d175e6b64/thinc-8.3.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c7c3a50ddd423d1c49419899acef4ac80d800af3b423593acb9e40578384b543", size = 789771 }, + { url = "https://files.pythonhosted.org/packages/8d/24/649f54774b1fbe791a1c2efd7d7f0a95cfd9244902553ca7dcf19daab1dd/thinc-8.3.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a1cb110398f51fc2b9a07a2a4daec6f91e166533a9c9f1c565225330f46569a", size = 737051 }, + { url = "https://files.pythonhosted.org/packages/b2/8c/5840c6c504c1fa9718e1c74d6e04d77a474f594888867dbba53f9317285f/thinc-8.3.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42318746a67403d04be57d862fe0c0015b58b6fb9bbbf7b6db01f3f103b73a99", size = 3839221 }, + { url = "https://files.pythonhosted.org/packages/45/ef/e7fca88074cb0aa1c1a23195470b4549492c2797fe7dc9ff79a85500153a/thinc-8.3.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6b0e41e79973f8828adead770f885db8d0f199bfbaa9591d1d896c385842e993", size = 3885024 }, + { url = "https://files.pythonhosted.org/packages/9a/eb/805e277aa019896009028d727460f071c6cf83843d70f6a69e58994d2203/thinc-8.3.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9ed982daa1eddbad813bfd079546483b849a68b98c01ad4a7e4efd125ddc5d7b", size = 4815939 }, + { url = "https://files.pythonhosted.org/packages/4f/f5/6425f12a60e3782091c9ec16394b9239f0c18c52c70218f3c8c047ff985c/thinc-8.3.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d22bd381410749dec5f629b3162b7d1f1e2d9b7364fd49a7ea555b61c93772b9", size = 5020260 }, + { url = "https://files.pythonhosted.org/packages/85/a2/ae98feffe0b161400e87b7bfc8859e6fa1e6023fa7bcfa0a8cacd83b39a1/thinc-8.3.10-cp313-cp313-win_amd64.whl", hash = "sha256:9c32830446a57da13b6856cacb0225bc2f2104f279d9928d40500081c13aa9ec", size = 1717562 }, + { url = "https://files.pythonhosted.org/packages/b8/e0/faa1d04a6890ea33b9541727d2a3ca88bad794a89f73b9111af6f9aefe10/thinc-8.3.10-cp313-cp313-win_arm64.whl", hash = "sha256:aa43f9af76781d32f5f9fe29299204c8841d71e64cbb56e0e4f3d1e0387c2783", size = 1641536 }, + { url = "https://files.pythonhosted.org/packages/b8/32/7a96e1f2cac159d778c6b0ab4ddd8a139bb57c602cef793b7606cd32428d/thinc-8.3.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:44d7038a5d28572105332b44ec9c4c3b6f7953b41d224588ad0473c9b79ccf9e", size = 793037 }, + { url = "https://files.pythonhosted.org/packages/12/d8/81e8495e8ef412767c09d1f9d0d86dc60cd22e6ed75e61b49fbf1dcfcd65/thinc-8.3.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:639f20952af722cb0ab4c3d8a00e661686b60c04f82ef48d12064ceda3b8cd0c", size = 740768 }, + { url = "https://files.pythonhosted.org/packages/c2/6d/716488a301d65c5463e92cb0eddae3672ca84f1d70937808cea9760f759c/thinc-8.3.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9306e62c7e7066c63b0c0ba1d164ae0c23bf38edf5a7df2e09cce69a2c290500", size = 3834983 }, + { url = "https://files.pythonhosted.org/packages/9c/a1/d28b21cab9b79e9c803671bebd14489e14c5226136fad6a1c44f96f8e4ef/thinc-8.3.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2982604c21096de1a87b04a781a645863eece71ec6ee9f139ac01b998fb5622d", size = 3845215 }, + { url = "https://files.pythonhosted.org/packages/93/9d/ff64ead5f1c2298d9e6a9ccc1c676b2347ac06162ad3c5e5d895c32a719e/thinc-8.3.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6b82698e27846004d4eafc38317ace482eced888d4445f7fb9c548fd36777af", size = 4826596 }, + { url = "https://files.pythonhosted.org/packages/4a/44/b80c863608d0fd31641a2d50658560c22d4841f1e445529201e22b3e1d0f/thinc-8.3.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2950acab8ae77427a86d11655ed0a161bc83a1edf9d31ba5c43deca6cd27ed4f", size = 4988146 }, + { url = "https://files.pythonhosted.org/packages/93/6d/1bdd9344b2e7299faa55129dda624d50c334eed16a3761eb8b1dacd8bfcd/thinc-8.3.10-cp314-cp314-win_amd64.whl", hash = "sha256:c253139a5c873edf75a3b17ec9d8b6caebee072fdb489594bc64e35115df7625", size = 1738054 }, + { url = "https://files.pythonhosted.org/packages/45/c4/44e3163d48e398efb3748481656963ac6265c14288012871c921dc81d004/thinc-8.3.10-cp314-cp314-win_arm64.whl", hash = "sha256:ad6da67f534995d6ec257f16665377d7ad95bef5c1b1c89618fd4528657a6f24", size = 1665001 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, +] + [[package]] name = "tqdm" version = "4.67.1" @@ -1608,6 +2199,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317 }, ] +[[package]] +name = "typer-slim" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/45/81b94a52caed434b94da65729c03ad0fb7665fab0f7db9ee54c94e541403/typer_slim-0.20.0.tar.gz", hash = "sha256:9fc6607b3c6c20f5c33ea9590cbeb17848667c51feee27d9e314a579ab07d1a3", size = 106561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/dd/5cbf31f402f1cc0ab087c94d4669cfa55bd1e818688b910631e131d74e75/typer_slim-0.20.0-py3-none-any.whl", hash = "sha256:f42a9b7571a12b97dddf364745d29f12221865acef7a2680065f9bb29c7dc89d", size = 47087 }, +] + [[package]] name = "types-passlib" version = "1.7.7.20250602" @@ -1714,6 +2318,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 }, ] +[[package]] +name = "wasabi" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880 }, +] + [[package]] name = "watchfiles" version = "1.1.0" @@ -1781,6 +2397,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315 }, ] +[[package]] +name = "weasel" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpathlib" }, + { name = "confection" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "smart-open" }, + { name = "srsly" }, + { name = "typer-slim" }, + { name = "wasabi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/d7/edd9c24e60cf8e5de130aa2e8af3b01521f4d0216c371d01212f580d0d8e/weasel-0.4.3.tar.gz", hash = "sha256:f293d6174398e8f478c78481e00c503ee4b82ea7a3e6d0d6a01e46a6b1396845", size = 38733 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/74/a148b41572656904a39dfcfed3f84dd1066014eed94e209223ae8e9d088d/weasel-0.4.3-py3-none-any.whl", hash = "sha256:08f65b5d0dbded4879e08a64882de9b9514753d9eaa4c4e2a576e33666ac12cf", size = 50757 }, +] + [[package]] name = "websockets" version = "15.0.1" diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 97f6ea6c..37ed6608 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -153,7 +153,7 @@ services: - .env environment: - GF_AUTH_DISABLE_LOGIN_FORM=true - - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - GF_AUTH_ANONYMOUS_ENABLED=true - NODE_ENV=development - GF_FEATURE_TOGGLES_ENABLE=accessControlOnCall