marintosti12 commited on
Commit
3b97d72
·
1 Parent(s): ef040c3

feat (api/orm) : first version api with orm

Browse files
.github/workflows/deploy.yaml CHANGED
@@ -21,6 +21,8 @@ jobs:
21
  HF_SPACE_URL: ${{ secrets.HF_SPACE_URL }}
22
  HF_GIT_EMAIL: ${{ secrets.HF_GIT_EMAIL || 'actions@github.com' }}
23
  HF_GIT_NAME: ${{ secrets.HF_GIT_NAME || 'github-actions' }}
 
 
24
 
25
  steps:
26
  - name: Checkout
@@ -55,6 +57,25 @@ jobs:
55
 
56
  git lfs status || true
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
  - name: Push to Space
60
  run: |
 
21
  HF_SPACE_URL: ${{ secrets.HF_SPACE_URL }}
22
  HF_GIT_EMAIL: ${{ secrets.HF_GIT_EMAIL || 'actions@github.com' }}
23
  HF_GIT_NAME: ${{ secrets.HF_GIT_NAME || 'github-actions' }}
24
+ DATABASE_URL: ${{ secrets.DATABASE_URL }}
25
+
26
 
27
  steps:
28
  - name: Checkout
 
57
 
58
  git lfs status || true
59
 
60
+ - name: Setup Python
61
+ uses: actions/setup-python@v5
62
+ with:
63
+ python-version: "3.12"
64
+
65
+ - name: Install Poetry
66
+ uses: abatilo/actions-poetry@v3
67
+ with:
68
+ poetry-version: "1.8.3"
69
+
70
+ - name: Install deps (with dev)
71
+ run: poetry install --no-interaction --no-root
72
+
73
+ - name: Run Alembic migrations
74
+ env:
75
+ DATABASE_URL: ${{ env.DATABASE_URL }}
76
+ run: |
77
+ echo "Migrating DB: $DATABASE_URL"
78
+ PYTHONPATH=./src poetry run alembic upgrade head
79
 
80
  - name: Push to Space
81
  run: |
.gitignore CHANGED
@@ -219,4 +219,6 @@ __marimo__/
219
  *.joblib
220
  *.pkl
221
  *.pt
222
- *.onnx
 
 
 
219
  *.joblib
220
  *.pkl
221
  *.pt
222
+ *.onnx
223
+
224
+ /artifacts
alembic.ini ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # A generic, single database configuration.
2
+
3
+ [alembic]
4
+ # path to migration scripts.
5
+ # this is typically a path given in POSIX (e.g. forward slashes)
6
+ # format, relative to the token %(here)s which refers to the location of this
7
+ # ini file
8
+ script_location = %(here)s/alembic
9
+
10
+ # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
11
+ # Uncomment the line below if you want the files to be prepended with date and time
12
+ # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
13
+ # for all available tokens
14
+ # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
15
+
16
+ # sys.path path, will be prepended to sys.path if present.
17
+ # defaults to the current working directory. for multiple paths, the path separator
18
+ # is defined by "path_separator" below.
19
+ prepend_sys_path = .
20
+
21
+
22
+ # timezone to use when rendering the date within the migration file
23
+ # as well as the filename.
24
+ # If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
25
+ # Any required deps can installed by adding `alembic[tz]` to the pip requirements
26
+ # string value is passed to ZoneInfo()
27
+ # leave blank for localtime
28
+ # timezone =
29
+
30
+ # max length of characters to apply to the "slug" field
31
+ # truncate_slug_length = 40
32
+
33
+ # set to 'true' to run the environment during
34
+ # the 'revision' command, regardless of autogenerate
35
+ # revision_environment = false
36
+
37
+ # set to 'true' to allow .pyc and .pyo files without
38
+ # a source .py file to be detected as revisions in the
39
+ # versions/ directory
40
+ # sourceless = false
41
+
42
+ # version location specification; This defaults
43
+ # to <script_location>/versions. When using multiple version
44
+ # directories, initial revisions must be specified with --version-path.
45
+ # The path separator used here should be the separator specified by "path_separator"
46
+ # below.
47
+ # version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
48
+
49
+ # path_separator; This indicates what character is used to split lists of file
50
+ # paths, including version_locations and prepend_sys_path within configparser
51
+ # files such as alembic.ini.
52
+ # The default rendered in new alembic.ini files is "os", which uses os.pathsep
53
+ # to provide os-dependent path splitting.
54
+ #
55
+ # Note that in order to support legacy alembic.ini files, this default does NOT
56
+ # take place if path_separator is not present in alembic.ini. If this
57
+ # option is omitted entirely, fallback logic is as follows:
58
+ #
59
+ # 1. Parsing of the version_locations option falls back to using the legacy
60
+ # "version_path_separator" key, which if absent then falls back to the legacy
61
+ # behavior of splitting on spaces and/or commas.
62
+ # 2. Parsing of the prepend_sys_path option falls back to the legacy
63
+ # behavior of splitting on spaces, commas, or colons.
64
+ #
65
+ # Valid values for path_separator are:
66
+ #
67
+ # path_separator = :
68
+ # path_separator = ;
69
+ # path_separator = space
70
+ # path_separator = newline
71
+ #
72
+ # Use os.pathsep. Default configuration used for new projects.
73
+ path_separator = os
74
+
75
+ # set to 'true' to search source files recursively
76
+ # in each "version_locations" directory
77
+ # new in Alembic version 1.10
78
+ # recursive_version_locations = false
79
+
80
+ # the output encoding used when revision files
81
+ # are written from script.py.mako
82
+ # output_encoding = utf-8
83
+
84
+ # database URL. This is consumed by the user-maintained env.py script only.
85
+ # other means of configuring database URLs may be customized within the env.py
86
+ # file.
87
+ sqlalchemy.url = driver://user:pass@localhost/dbname
88
+
89
+
90
+ [post_write_hooks]
91
+ # post_write_hooks defines scripts or Python functions that are run
92
+ # on newly generated revision scripts. See the documentation for further
93
+ # detail and examples
94
+
95
+ # format using "black" - use the console_scripts runner, against the "black" entrypoint
96
+ # hooks = black
97
+ # black.type = console_scripts
98
+ # black.entrypoint = black
99
+ # black.options = -l 79 REVISION_SCRIPT_FILENAME
100
+
101
+ # lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
102
+ # hooks = ruff
103
+ # ruff.type = module
104
+ # ruff.module = ruff
105
+ # ruff.options = check --fix REVISION_SCRIPT_FILENAME
106
+
107
+ # Alternatively, use the exec runner to execute a binary found on your PATH
108
+ # hooks = ruff
109
+ # ruff.type = exec
110
+ # ruff.executable = ruff
111
+ # ruff.options = check --fix REVISION_SCRIPT_FILENAME
112
+
113
+ # Logging configuration. This is also consumed by the user-maintained
114
+ # env.py script only.
115
+ [loggers]
116
+ keys = root,sqlalchemy,alembic
117
+
118
+ [handlers]
119
+ keys = console
120
+
121
+ [formatters]
122
+ keys = generic
123
+
124
+ [logger_root]
125
+ level = WARNING
126
+ handlers = console
127
+ qualname =
128
+
129
+ [logger_sqlalchemy]
130
+ level = WARNING
131
+ handlers =
132
+ qualname = sqlalchemy.engine
133
+
134
+ [logger_alembic]
135
+ level = INFO
136
+ handlers =
137
+ qualname = alembic
138
+
139
+ [handler_console]
140
+ class = StreamHandler
141
+ args = (sys.stderr,)
142
+ level = NOTSET
143
+ formatter = generic
144
+
145
+ [formatter_generic]
146
+ format = %(levelname)-5.5s [%(name)s] %(message)s
147
+ datefmt = %H:%M:%S
alembic/README ADDED
@@ -0,0 +1 @@
 
 
1
+ Generic single-database configuration.
alembic/env.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from logging.config import fileConfig
3
+
4
+ from alembic import context
5
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
6
+ from sqlalchemy import pool
7
+
8
+ from src.config.db import Base, settings
9
+
10
+ # Alembic Config
11
+ config = context.config
12
+ if config.config_file_name is not None:
13
+ fileConfig(config.config_file_name)
14
+
15
+ target_metadata = Base.metadata
16
+
17
+ def get_url() -> str:
18
+ return settings.DATABASE_URL
19
+
20
+ def run_migrations_offline() -> None:
21
+ """Mode offline : pas d'engine, juste l'URL."""
22
+ context.configure(
23
+ url=get_url(),
24
+ target_metadata=target_metadata,
25
+ literal_binds=True,
26
+ compare_type=True,
27
+ dialect_opts={"paramstyle": "named"},
28
+ )
29
+ with context.begin_transaction():
30
+ context.run_migrations()
31
+
32
+ def do_run_migrations(connection) -> None:
33
+ """Configuration commune (online) une fois connecté."""
34
+ context.configure(
35
+ connection=connection,
36
+ target_metadata=target_metadata,
37
+ compare_type=True,
38
+ )
39
+ with context.begin_transaction():
40
+ context.run_migrations()
41
+
42
+ async def run_migrations_online() -> None:
43
+ """Mode online : engine async."""
44
+ connectable: AsyncEngine = create_async_engine(
45
+ get_url(),
46
+ poolclass=pool.NullPool,
47
+ )
48
+ async with connectable.connect() as connection:
49
+ await connection.run_sync(do_run_migrations)
50
+ await connectable.dispose()
51
+
52
+ if context.is_offline_mode():
53
+ run_migrations_offline()
54
+ else:
55
+ import asyncio
56
+ asyncio.run(run_migrations_online())
alembic/script.py.mako ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ """Downgrade schema."""
28
+ ${downgrades if downgrades else "pass"}
alembic/versions/99e339b56253_initial_schema.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """initial schema
2
+
3
+ Revision ID: 99e339b56253
4
+ Revises:
5
+ Create Date: 2025-09-15 12:01:54.205742
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ from sqlalchemy.dialects import postgresql
13
+
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = '99e339b56253'
17
+ down_revision: Union[str, Sequence[str], None] = None
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ op.create_table(
24
+ "ml_models",
25
+ sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, nullable=False),
26
+ sa.Column("name", sa.String(length=100), nullable=False, unique=True),
27
+ sa.Column("description", sa.Text(), nullable=True),
28
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
29
+ sa.Column("is_active", sa.Boolean(), server_default=sa.text("TRUE"), nullable=False),
30
+ )
31
+
32
+
33
+ def downgrade() -> None:
34
+ op.drop_table("ml_models")
35
+
alembic/versions/ecd589af543e_ml_inputs.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ml inputs
2
+
3
+ Revision ID: ecd589af543e
4
+ Revises: 99e339b56253
5
+ Create Date: 2025-09-15 13:47:50.220857
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ from sqlalchemy.dialects import postgresql
13
+
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = 'ecd589af543e'
17
+ down_revision: Union[str, Sequence[str], None] = '99e339b56253'
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ op.create_table(
24
+ "ml_inputs",
25
+ sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, nullable=False),
26
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("NOW()"), nullable=False),
27
+
28
+ sa.Column("id_employee", sa.Integer(), nullable=False),
29
+ sa.Column("age", sa.Integer(), nullable=False),
30
+
31
+ sa.Column("genre", sa.String(length=20), nullable=False),
32
+ sa.Column("revenu_mensuel", sa.Integer(), nullable=False),
33
+ sa.Column("statut_marital", sa.String(length=50), nullable=False),
34
+ sa.Column("departement", sa.String(length=100), nullable=False),
35
+ sa.Column("poste", sa.String(length=100), nullable=False),
36
+
37
+ sa.Column("nombre_experiences_precedentes", sa.Integer(), nullable=False),
38
+ sa.Column("nombre_heures_travailless", sa.Integer(), nullable=False),
39
+ sa.Column("annee_experience_totale", sa.Integer(), nullable=False),
40
+ sa.Column("annees_dans_l_entreprise", sa.Integer(), nullable=False),
41
+ sa.Column("annees_dans_le_poste_actuel", sa.Integer(), nullable=False),
42
+
43
+ sa.Column("nombre_participation_pee", sa.Integer(), nullable=False),
44
+ sa.Column("nb_formations_suivies", sa.Integer(), nullable=False),
45
+ sa.Column("nombre_employee_sous_responsabilite", sa.Integer(), nullable=False),
46
+
47
+ sa.Column("code_sondage", sa.Integer(), nullable=False),
48
+ sa.Column("distance_domicile_travail", sa.Integer(), nullable=False),
49
+ sa.Column("niveau_education", sa.Integer(), nullable=False),
50
+ sa.Column("domaine_etude", sa.String(length=100), nullable=False),
51
+
52
+ sa.Column("ayant_enfants", sa.String(length=10), nullable=False),
53
+ sa.Column("frequence_deplacement", sa.String(length=50), nullable=False),
54
+
55
+ sa.Column("annees_depuis_la_derniere_promotion", sa.Integer(), nullable=False),
56
+ sa.Column("annes_sous_responsable_actuel", sa.Integer(), nullable=False),
57
+
58
+ sa.Column("satisfaction_employee_environnement", sa.Integer(), nullable=False),
59
+ sa.Column("note_evaluation_precedente", sa.Integer(), nullable=False),
60
+ sa.Column("niveau_hierarchique_poste", sa.Integer(), nullable=False),
61
+ sa.Column("satisfaction_employee_nature_travail", sa.Integer(), nullable=False),
62
+ sa.Column("satisfaction_employee_equipe", sa.Integer(), nullable=False),
63
+ sa.Column("satisfaction_employee_equilibre_pro_perso", sa.Integer(), nullable=False),
64
+
65
+ sa.Column("eval_number", sa.String(length=50), nullable=False),
66
+ sa.Column("note_evaluation_actuelle", sa.Integer(), nullable=False),
67
+ sa.Column("heure_supplementaires", sa.String(length=10), nullable=False),
68
+ sa.Column("augementation_salaire_precedente", sa.Integer(), nullable=False),
69
+ )
70
+
71
+
72
+ def downgrade() -> None:
73
+ op.drop_table("ml_inputs")
74
+
docker-compose.yml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ db:
3
+ image: postgres:16
4
+ container_name: futurisys-db
5
+ environment:
6
+ POSTGRES_USER: futu
7
+ POSTGRES_PASSWORD: futu_pass
8
+ POSTGRES_DB: futurisys
9
+ ports:
10
+ - "5432:5432"
11
+ volumes:
12
+ - pgdata:/var/lib/postgresql/data
13
+ healthcheck:
14
+ test: ["CMD-SHELL", "pg_isready -U futu -d futurisys"]
15
+ interval: 5s
16
+ timeout: 5s
17
+ retries: 10
18
+ volumes:
19
+ pgdata:
notebook/1_Analyse.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
notebook/2_Nettoyage_Standardisation.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
notebook/3_Feature_Engineering.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
notebook/4_Modelisation.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
poetry.lock CHANGED
@@ -1,5 +1,24 @@
1
  # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  [[package]]
4
  name = "annotated-types"
5
  version = "0.7.0"
@@ -30,6 +49,69 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
30
  [package.extras]
31
  trio = ["trio (>=0.26.1)"]
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  [[package]]
34
  name = "certifi"
35
  version = "2025.8.3"
@@ -41,6 +123,94 @@ files = [
41
  {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"},
42
  ]
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  [[package]]
45
  name = "click"
46
  version = "8.2.1"
@@ -289,6 +459,17 @@ uvicorn = {version = ">=0.15.0", extras = ["standard"]}
289
  [package.extras]
290
  standard = ["uvicorn[standard] (>=0.15.0)"]
291
 
 
 
 
 
 
 
 
 
 
 
 
292
  [[package]]
293
  name = "fonttools"
294
  version = "4.59.2"
@@ -369,6 +550,45 @@ type1 = ["xattr"]
369
  unicode = ["unicodedata2 (>=15.1.0)"]
370
  woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
371
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  [[package]]
373
  name = "greenlet"
374
  version = "3.2.4"
@@ -447,6 +667,26 @@ files = [
447
  {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
448
  ]
449
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  [[package]]
451
  name = "httpcore"
452
  version = "1.0.9"
@@ -547,6 +787,44 @@ http2 = ["h2 (>=3,<5)"]
547
  socks = ["socksio (==1.*)"]
548
  zstd = ["zstandard (>=0.18.0)"]
549
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  [[package]]
551
  name = "idna"
552
  version = "3.10"
@@ -767,6 +1045,25 @@ files = [
767
  {file = "llvmlite-0.45.0rc1.tar.gz", hash = "sha256:bec0a4c729848a4e7f6355fdbd98f2ee9471189d0a5aeb03a3cd19f672327fef"},
768
  ]
769
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
  [[package]]
771
  name = "markdown-it-py"
772
  version = "4.0.0"
@@ -1290,6 +1587,83 @@ files = [
1290
  dev = ["pre-commit", "tox"]
1291
  testing = ["coverage", "pytest", "pytest-benchmark"]
1292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1293
  [[package]]
1294
  name = "pydantic"
1295
  version = "2.11.7"
@@ -1423,6 +1797,29 @@ files = [
1423
  [package.dependencies]
1424
  typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
1425
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1426
  [[package]]
1427
  name = "pygments"
1428
  version = "2.19.2"
@@ -1584,6 +1981,27 @@ files = [
1584
  {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
1585
  ]
1586
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1587
  [[package]]
1588
  name = "rich"
1589
  version = "14.1.0"
@@ -2154,7 +2572,7 @@ files = [
2154
  ]
2155
 
2156
  [package.dependencies]
2157
- greenlet = {version = ">=1", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
2158
  typing-extensions = ">=4.6.0"
2159
 
2160
  [package.extras]
@@ -2577,4 +2995,4 @@ files = [
2577
  [metadata]
2578
  lock-version = "2.0"
2579
  python-versions = "^3.12"
2580
- content-hash = "7ca2b5831a4e584d1f5c1a59aeeaf40b6a9333bbc81ae9e8221549e8878660b3"
 
1
  # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
2
 
3
+ [[package]]
4
+ name = "alembic"
5
+ version = "1.16.5"
6
+ description = "A database migration tool for SQLAlchemy."
7
+ optional = false
8
+ python-versions = ">=3.9"
9
+ files = [
10
+ {file = "alembic-1.16.5-py3-none-any.whl", hash = "sha256:e845dfe090c5ffa7b92593ae6687c5cb1a101e91fa53868497dbd79847f9dbe3"},
11
+ {file = "alembic-1.16.5.tar.gz", hash = "sha256:a88bb7f6e513bd4301ecf4c7f2206fe93f9913f9b48dac3b78babde2d6fe765e"},
12
+ ]
13
+
14
+ [package.dependencies]
15
+ Mako = "*"
16
+ SQLAlchemy = ">=1.4.0"
17
+ typing-extensions = ">=4.12"
18
+
19
+ [package.extras]
20
+ tz = ["tzdata"]
21
+
22
  [[package]]
23
  name = "annotated-types"
24
  version = "0.7.0"
 
49
  [package.extras]
50
  trio = ["trio (>=0.26.1)"]
51
 
52
+ [[package]]
53
+ name = "asyncpg"
54
+ version = "0.30.0"
55
+ description = "An asyncio PostgreSQL driver"
56
+ optional = false
57
+ python-versions = ">=3.8.0"
58
+ files = [
59
+ {file = "asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e"},
60
+ {file = "asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0"},
61
+ {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f"},
62
+ {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af"},
63
+ {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75"},
64
+ {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f"},
65
+ {file = "asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf"},
66
+ {file = "asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50"},
67
+ {file = "asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a"},
68
+ {file = "asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed"},
69
+ {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a"},
70
+ {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956"},
71
+ {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056"},
72
+ {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454"},
73
+ {file = "asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d"},
74
+ {file = "asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f"},
75
+ {file = "asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e"},
76
+ {file = "asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a"},
77
+ {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3"},
78
+ {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737"},
79
+ {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a"},
80
+ {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af"},
81
+ {file = "asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e"},
82
+ {file = "asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305"},
83
+ {file = "asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70"},
84
+ {file = "asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3"},
85
+ {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33"},
86
+ {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4"},
87
+ {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4"},
88
+ {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba"},
89
+ {file = "asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590"},
90
+ {file = "asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e"},
91
+ {file = "asyncpg-0.30.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29ff1fc8b5bf724273782ff8b4f57b0f8220a1b2324184846b39d1ab4122031d"},
92
+ {file = "asyncpg-0.30.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64e899bce0600871b55368b8483e5e3e7f1860c9482e7f12e0a771e747988168"},
93
+ {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b290f4726a887f75dcd1b3006f484252db37602313f806e9ffc4e5996cfe5cb"},
94
+ {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f86b0e2cd3f1249d6fe6fd6cfe0cd4538ba994e2d8249c0491925629b9104d0f"},
95
+ {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:393af4e3214c8fa4c7b86da6364384c0d1b3298d45803375572f415b6f673f38"},
96
+ {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fd4406d09208d5b4a14db9a9dbb311b6d7aeeab57bded7ed2f8ea41aeef39b34"},
97
+ {file = "asyncpg-0.30.0-cp38-cp38-win32.whl", hash = "sha256:0b448f0150e1c3b96cb0438a0d0aa4871f1472e58de14a3ec320dbb2798fb0d4"},
98
+ {file = "asyncpg-0.30.0-cp38-cp38-win_amd64.whl", hash = "sha256:f23b836dd90bea21104f69547923a02b167d999ce053f3d502081acea2fba15b"},
99
+ {file = "asyncpg-0.30.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f4e83f067b35ab5e6371f8a4c93296e0439857b4569850b178a01385e82e9ad"},
100
+ {file = "asyncpg-0.30.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5df69d55add4efcd25ea2a3b02025b669a285b767bfbf06e356d68dbce4234ff"},
101
+ {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3479a0d9a852c7c84e822c073622baca862d1217b10a02dd57ee4a7a081f708"},
102
+ {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26683d3b9a62836fad771a18ecf4659a30f348a561279d6227dab96182f46144"},
103
+ {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1b982daf2441a0ed314bd10817f1606f1c28b1136abd9e4f11335358c2c631cb"},
104
+ {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c06a3a50d014b303e5f6fc1e5f95eb28d2cee89cf58384b700da621e5d5e547"},
105
+ {file = "asyncpg-0.30.0-cp39-cp39-win32.whl", hash = "sha256:1b11a555a198b08f5c4baa8f8231c74a366d190755aa4f99aacec5970afe929a"},
106
+ {file = "asyncpg-0.30.0-cp39-cp39-win_amd64.whl", hash = "sha256:8b684a3c858a83cd876f05958823b68e8d14ec01bb0c0d14a6704c5bf9711773"},
107
+ {file = "asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851"},
108
+ ]
109
+
110
+ [package.extras]
111
+ docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"]
112
+ gssauth = ["gssapi", "sspilib"]
113
+ test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"]
114
+
115
  [[package]]
116
  name = "certifi"
117
  version = "2025.8.3"
 
123
  {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"},
124
  ]
125
 
126
+ [[package]]
127
+ name = "charset-normalizer"
128
+ version = "3.4.3"
129
+ description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
130
+ optional = false
131
+ python-versions = ">=3.7"
132
+ files = [
133
+ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"},
134
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"},
135
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"},
136
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"},
137
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"},
138
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"},
139
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"},
140
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"},
141
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"},
142
+ {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"},
143
+ {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"},
144
+ {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"},
145
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"},
146
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"},
147
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"},
148
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"},
149
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"},
150
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"},
151
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"},
152
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"},
153
+ {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"},
154
+ {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"},
155
+ {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"},
156
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"},
157
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"},
158
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"},
159
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"},
160
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"},
161
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"},
162
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"},
163
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"},
164
+ {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"},
165
+ {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"},
166
+ {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"},
167
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"},
168
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"},
169
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"},
170
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"},
171
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"},
172
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"},
173
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"},
174
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"},
175
+ {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"},
176
+ {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"},
177
+ {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"},
178
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"},
179
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"},
180
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"},
181
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"},
182
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"},
183
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"},
184
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"},
185
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"},
186
+ {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"},
187
+ {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"},
188
+ {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"},
189
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"},
190
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"},
191
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"},
192
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"},
193
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"},
194
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"},
195
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"},
196
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"},
197
+ {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"},
198
+ {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"},
199
+ {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"},
200
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"},
201
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"},
202
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"},
203
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"},
204
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"},
205
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"},
206
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"},
207
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"},
208
+ {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"},
209
+ {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"},
210
+ {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"},
211
+ {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"},
212
+ ]
213
+
214
  [[package]]
215
  name = "click"
216
  version = "8.2.1"
 
459
  [package.extras]
460
  standard = ["uvicorn[standard] (>=0.15.0)"]
461
 
462
+ [[package]]
463
+ name = "filelock"
464
+ version = "3.19.1"
465
+ description = "A platform independent file lock."
466
+ optional = false
467
+ python-versions = ">=3.9"
468
+ files = [
469
+ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"},
470
+ {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"},
471
+ ]
472
+
473
  [[package]]
474
  name = "fonttools"
475
  version = "4.59.2"
 
550
  unicode = ["unicodedata2 (>=15.1.0)"]
551
  woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
552
 
553
+ [[package]]
554
+ name = "fsspec"
555
+ version = "2025.9.0"
556
+ description = "File-system specification"
557
+ optional = false
558
+ python-versions = ">=3.9"
559
+ files = [
560
+ {file = "fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7"},
561
+ {file = "fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19"},
562
+ ]
563
+
564
+ [package.extras]
565
+ abfs = ["adlfs"]
566
+ adl = ["adlfs"]
567
+ arrow = ["pyarrow (>=1)"]
568
+ dask = ["dask", "distributed"]
569
+ dev = ["pre-commit", "ruff (>=0.5)"]
570
+ doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"]
571
+ dropbox = ["dropbox", "dropboxdrivefs", "requests"]
572
+ full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"]
573
+ fuse = ["fusepy"]
574
+ gcs = ["gcsfs"]
575
+ git = ["pygit2"]
576
+ github = ["requests"]
577
+ gs = ["gcsfs"]
578
+ gui = ["panel"]
579
+ hdfs = ["pyarrow (>=1)"]
580
+ http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"]
581
+ libarchive = ["libarchive-c"]
582
+ oci = ["ocifs"]
583
+ s3 = ["s3fs"]
584
+ sftp = ["paramiko"]
585
+ smb = ["smbprotocol"]
586
+ ssh = ["paramiko"]
587
+ test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"]
588
+ test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"]
589
+ test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"]
590
+ tqdm = ["tqdm"]
591
+
592
  [[package]]
593
  name = "greenlet"
594
  version = "3.2.4"
 
667
  {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
668
  ]
669
 
670
+ [[package]]
671
+ name = "hf-xet"
672
+ version = "1.1.9"
673
+ description = "Fast transfer of large files with the Hugging Face Hub."
674
+ optional = false
675
+ python-versions = ">=3.8"
676
+ files = [
677
+ {file = "hf_xet-1.1.9-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a3b6215f88638dd7a6ff82cb4e738dcbf3d863bf667997c093a3c990337d1160"},
678
+ {file = "hf_xet-1.1.9-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9b486de7a64a66f9a172f4b3e0dfe79c9f0a93257c501296a2521a13495a698a"},
679
+ {file = "hf_xet-1.1.9-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c5a840c2c4e6ec875ed13703a60e3523bc7f48031dfd750923b2a4d1a5fc3c"},
680
+ {file = "hf_xet-1.1.9-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:96a6139c9e44dad1c52c52520db0fffe948f6bce487cfb9d69c125f254bb3790"},
681
+ {file = "hf_xet-1.1.9-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ad1022e9a998e784c97b2173965d07fe33ee26e4594770b7785a8cc8f922cd95"},
682
+ {file = "hf_xet-1.1.9-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86754c2d6d5afb11b0a435e6e18911a4199262fe77553f8c50d75e21242193ea"},
683
+ {file = "hf_xet-1.1.9-cp37-abi3-win_amd64.whl", hash = "sha256:5aad3933de6b725d61d51034e04174ed1dce7a57c63d530df0014dea15a40127"},
684
+ {file = "hf_xet-1.1.9.tar.gz", hash = "sha256:c99073ce404462e909f1d5839b2d14a3827b8fe75ed8aed551ba6609c026c803"},
685
+ ]
686
+
687
+ [package.extras]
688
+ tests = ["pytest"]
689
+
690
  [[package]]
691
  name = "httpcore"
692
  version = "1.0.9"
 
787
  socks = ["socksio (==1.*)"]
788
  zstd = ["zstandard (>=0.18.0)"]
789
 
790
+ [[package]]
791
+ name = "huggingface-hub"
792
+ version = "0.34.4"
793
+ description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
794
+ optional = false
795
+ python-versions = ">=3.8.0"
796
+ files = [
797
+ {file = "huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a"},
798
+ {file = "huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c"},
799
+ ]
800
+
801
+ [package.dependencies]
802
+ filelock = "*"
803
+ fsspec = ">=2023.5.0"
804
+ hf-xet = {version = ">=1.1.3,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""}
805
+ packaging = ">=20.9"
806
+ pyyaml = ">=5.1"
807
+ requests = "*"
808
+ tqdm = ">=4.42.1"
809
+ typing-extensions = ">=3.7.4.3"
810
+
811
+ [package.extras]
812
+ all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
813
+ cli = ["InquirerPy (==0.3.4)"]
814
+ dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
815
+ fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"]
816
+ hf-transfer = ["hf-transfer (>=0.1.4)"]
817
+ hf-xet = ["hf-xet (>=1.1.2,<2.0.0)"]
818
+ inference = ["aiohttp"]
819
+ mcp = ["aiohttp", "mcp (>=1.8.0)", "typer"]
820
+ oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"]
821
+ quality = ["libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "ruff (>=0.9.0)"]
822
+ tensorflow = ["graphviz", "pydot", "tensorflow"]
823
+ tensorflow-testing = ["keras (<3.0)", "tensorflow"]
824
+ testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"]
825
+ torch = ["safetensors[torch]", "torch"]
826
+ typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"]
827
+
828
  [[package]]
829
  name = "idna"
830
  version = "3.10"
 
1045
  {file = "llvmlite-0.45.0rc1.tar.gz", hash = "sha256:bec0a4c729848a4e7f6355fdbd98f2ee9471189d0a5aeb03a3cd19f672327fef"},
1046
  ]
1047
 
1048
+ [[package]]
1049
+ name = "mako"
1050
+ version = "1.3.10"
1051
+ description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
1052
+ optional = false
1053
+ python-versions = ">=3.8"
1054
+ files = [
1055
+ {file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"},
1056
+ {file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"},
1057
+ ]
1058
+
1059
+ [package.dependencies]
1060
+ MarkupSafe = ">=0.9.2"
1061
+
1062
+ [package.extras]
1063
+ babel = ["Babel"]
1064
+ lingua = ["lingua"]
1065
+ testing = ["pytest"]
1066
+
1067
  [[package]]
1068
  name = "markdown-it-py"
1069
  version = "4.0.0"
 
1587
  dev = ["pre-commit", "tox"]
1588
  testing = ["coverage", "pytest", "pytest-benchmark"]
1589
 
1590
+ [[package]]
1591
+ name = "psycopg2-binary"
1592
+ version = "2.9.10"
1593
+ description = "psycopg2 - Python-PostgreSQL Database Adapter"
1594
+ optional = false
1595
+ python-versions = ">=3.8"
1596
+ files = [
1597
+ {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"},
1598
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"},
1599
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"},
1600
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"},
1601
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"},
1602
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"},
1603
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"},
1604
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"},
1605
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"},
1606
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"},
1607
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"},
1608
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"},
1609
+ {file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"},
1610
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"},
1611
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"},
1612
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"},
1613
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"},
1614
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"},
1615
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"},
1616
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"},
1617
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"},
1618
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"},
1619
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"},
1620
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"},
1621
+ {file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"},
1622
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"},
1623
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"},
1624
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"},
1625
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"},
1626
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"},
1627
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"},
1628
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"},
1629
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"},
1630
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"},
1631
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"},
1632
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"},
1633
+ {file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"},
1634
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"},
1635
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"},
1636
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"},
1637
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"},
1638
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"},
1639
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"},
1640
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"},
1641
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"},
1642
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"},
1643
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"},
1644
+ {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"},
1645
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"},
1646
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"},
1647
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"},
1648
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"},
1649
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"},
1650
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"},
1651
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"},
1652
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"},
1653
+ {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"},
1654
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"},
1655
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"},
1656
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"},
1657
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"},
1658
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"},
1659
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"},
1660
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"},
1661
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"},
1662
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"},
1663
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"},
1664
+ {file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"},
1665
+ ]
1666
+
1667
  [[package]]
1668
  name = "pydantic"
1669
  version = "2.11.7"
 
1797
  [package.dependencies]
1798
  typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
1799
 
1800
+ [[package]]
1801
+ name = "pydantic-settings"
1802
+ version = "2.10.1"
1803
+ description = "Settings management using Pydantic"
1804
+ optional = false
1805
+ python-versions = ">=3.9"
1806
+ files = [
1807
+ {file = "pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796"},
1808
+ {file = "pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee"},
1809
+ ]
1810
+
1811
+ [package.dependencies]
1812
+ pydantic = ">=2.7.0"
1813
+ python-dotenv = ">=0.21.0"
1814
+ typing-inspection = ">=0.4.0"
1815
+
1816
+ [package.extras]
1817
+ aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"]
1818
+ azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"]
1819
+ gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"]
1820
+ toml = ["tomli (>=2.0.1)"]
1821
+ yaml = ["pyyaml (>=6.0.1)"]
1822
+
1823
  [[package]]
1824
  name = "pygments"
1825
  version = "2.19.2"
 
1981
  {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
1982
  ]
1983
 
1984
+ [[package]]
1985
+ name = "requests"
1986
+ version = "2.32.5"
1987
+ description = "Python HTTP for Humans."
1988
+ optional = false
1989
+ python-versions = ">=3.9"
1990
+ files = [
1991
+ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"},
1992
+ {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"},
1993
+ ]
1994
+
1995
+ [package.dependencies]
1996
+ certifi = ">=2017.4.17"
1997
+ charset_normalizer = ">=2,<4"
1998
+ idna = ">=2.5,<4"
1999
+ urllib3 = ">=1.21.1,<3"
2000
+
2001
+ [package.extras]
2002
+ socks = ["PySocks (>=1.5.6,!=1.5.7)"]
2003
+ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
2004
+
2005
  [[package]]
2006
  name = "rich"
2007
  version = "14.1.0"
 
2572
  ]
2573
 
2574
  [package.dependencies]
2575
+ greenlet = {version = ">=1", optional = true, markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""}
2576
  typing-extensions = ">=4.6.0"
2577
 
2578
  [package.extras]
 
2995
  [metadata]
2996
  lock-version = "2.0"
2997
  python-versions = "^3.12"
2998
+ content-hash = "ae6861740d43c577f92198e978bd401f46311b2faddb8841e5d26b8163e1a78c"
pyproject.toml CHANGED
@@ -17,7 +17,12 @@ scipy = "^1.16.1"
17
  scikit-learn = "^1.7.2"
18
  imbalanced-learn = "^0.14.0"
19
  shap = "^0.48.0"
20
- sqlalchemy = "^2.0.43"
 
 
 
 
 
21
 
22
 
23
  [tool.poetry.group.dev.dependencies]
 
17
  scikit-learn = "^1.7.2"
18
  imbalanced-learn = "^0.14.0"
19
  shap = "^0.48.0"
20
+ sqlalchemy = {extras = ["asyncio"], version = "^2.0.43"}
21
+ huggingface-hub = "^0.34.4"
22
+ alembic = "^1.16.5"
23
+ asyncpg = "^0.30.0"
24
+ psycopg2-binary = "^2.9.10"
25
+ pydantic-settings = "^2.10.1"
26
 
27
 
28
  [tool.poetry.group.dev.dependencies]
space.yaml DELETED
@@ -1,8 +0,0 @@
1
- # space.yaml
2
- title: "Deploy ML API"
3
- emoji: "🚀"
4
- colorFrom: "blue"
5
- colorTo: "green"
6
- sdk: "docker"
7
- pinned: false
8
- license: "apache-2.0"
 
 
 
 
 
 
 
 
 
src/config/db.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.orm import sessionmaker, DeclarativeBase
3
+ from pydantic_settings import BaseSettings, SettingsConfigDict
4
+
5
+ class Settings(BaseSettings):
6
+ DATABASE_URL: str
7
+ model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore",)
8
+
9
+ settings = Settings()
10
+
11
+ class Base(DeclarativeBase):
12
+ pass
13
+
14
+ engine = create_engine(settings.DATABASE_URL, echo=True, future=True)
15
+ SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
src/controllers/home_controller.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from sqlalchemy.orm import Session
3
+ from config.db import SessionLocal
4
+ from models.ml import MLModel
5
+
6
+ router = APIRouter()
7
+
8
+ @router.get("/", tags=["models"])
9
+ def list_ml_models():
10
+ try:
11
+ with SessionLocal() as s:
12
+ rows = (
13
+ s.query(MLModel)
14
+ .order_by(MLModel.created_at.desc())
15
+ .all()
16
+ )
17
+ return [
18
+ {
19
+ "id": str(r.id),
20
+ "name": r.name,
21
+ "description": r.description,
22
+ "created_at": r.created_at.isoformat() if r.created_at else None,
23
+ "is_active": r.is_active,
24
+ }
25
+ for r in rows
26
+ ]
27
+ except Exception as e:
28
+ raise HTTPException(status_code=500, detail=str(e))
src/controllers/predict_controller.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/controllers/predict_controller.py
2
+ from fastapi import APIRouter, HTTPException
3
+ from pydantic import BaseModel
4
+ from typing import Optional, List
5
+
6
+ from sqlalchemy.orm import Session
7
+ from config.db import SessionLocal
8
+ from models.ml import MLModel
9
+
10
+ # Schemas
11
+ from models.ml_inputs import MLInput
12
+ from schemas.ModelFeatures import ModelFeatures
13
+
14
+ import pandas as pd
15
+ from model_loader import load_model
16
+ from features import compute_features
17
+ from schemas.PredictItemResult import PredictItemResult
18
+ from schemas.PredictResponse import PredictResponse
19
+ from schemas.PredictRequest import PredictRequest
20
+
21
+ router = APIRouter(prefix="/predict", tags=["inference"])
22
+
23
+ # (optionnel) mapping lisible des classes
24
+ LABELS = {
25
+ "0": "reste_dans_l_entreprise",
26
+ "1": "parti_de_l_entreprise",
27
+ }
28
+
29
+ # --------- Route ----------
30
+ @router.post("/", response_model=PredictResponse)
31
+ def batch_predict(payload: PredictRequest):
32
+ with SessionLocal() as s:
33
+ row = (
34
+ s.query(MLModel)
35
+ .filter(MLModel.name == payload.model_name)
36
+ .first()
37
+ )
38
+
39
+ objs = [MLInput(**x.model_dump()) for x in payload.inputs]
40
+ s.add_all(objs)
41
+ s.commit()
42
+
43
+
44
+ if not row or getattr(row, "is_active", True) is False:
45
+ raise HTTPException(status_code=404, detail="Modèle introuvable ou inactif")
46
+
47
+ try:
48
+ m = load_model(payload.model_name)
49
+ except Exception as e:
50
+ raise HTTPException(status_code=500, detail=f"Chargement du modèle '{payload.model_name}' impossible: {e}")
51
+
52
+ try:
53
+ df = pd.DataFrame([x.model_dump() for x in payload.inputs])
54
+ X = compute_features(df)
55
+
56
+ results: list[PredictItemResult] = []
57
+
58
+ probas = m.predict_proba(X)
59
+ classes = getattr(m, "classes_", None)
60
+ for p in probas:
61
+ i = int(p.argmax())
62
+ key = str(classes[i]) if classes is not None else str(i)
63
+ label = LABELS.get(key, key)
64
+ results.append(PredictItemResult(label=label, proba=float(p[i])))
65
+
66
+ except Exception as e:
67
+ raise HTTPException(status_code=400, detail=f"Erreur pendant la prédiction: {e}")
68
+
69
+ return PredictResponse(
70
+ model_name=payload.model_name,
71
+ results=results,
72
+ )
src/features.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ def _safe_div(a, b):
5
+ a = pd.to_numeric(a, errors="coerce")
6
+ b = pd.to_numeric(b, errors="coerce").replace(0, np.nan)
7
+ return (a / b).fillna(0.0)
8
+
9
+ def compute_features(df: pd.DataFrame) -> pd.DataFrame:
10
+ SAT_COLS = [
11
+ "satisfaction_employee_environnement",
12
+ "satisfaction_employee_nature_travail",
13
+ "satisfaction_employee_equipe",
14
+ "satisfaction_employee_equilibre_pro_perso",
15
+ ]
16
+
17
+ X = df.copy()
18
+ X["sat_mean"] = X[SAT_COLS].astype(float).mean(axis=1)
19
+ X["sat_std"] = X[SAT_COLS].astype(float).std(axis=1, ddof=0)
20
+ X["delta_eval"] = (
21
+ X["note_evaluation_actuelle"].astype(float)
22
+ - X["note_evaluation_precedente"].astype(float)
23
+ )
24
+
25
+ X["ratio_post_stab"] = _safe_div(X["annes_sous_responsable_actuel"], X["annees_dans_le_poste_actuel"])
26
+ X["revenu_par_niveau"] = _safe_div(X["revenu_mensuel"], X["niveau_hierarchique_poste"])
27
+
28
+ age_bins = [-np.inf, 25, 35, 45, 60, np.inf]
29
+ dist_bins = [-np.inf, 5, 10, 20, np.inf]
30
+ revenu_bins = [-np.inf, 2500, 4000, 6000, np.inf]
31
+ sat_mean_bins = [-np.inf, 2.0, 3.0, 4.0, np.inf]
32
+
33
+ X["tranche_age"] = pd.cut(X["age"].astype(float), age_bins, labels=["<=25","26-35","36-45","46-60","60+"])
34
+ X["tranche_distance"] = pd.cut(X["distance_domicile_travail"].astype(float), dist_bins, labels=["<=5","6-10","11-20",">20"])
35
+ X["tranche_revenu"] = pd.cut(X["revenu_mensuel"].astype(float), revenu_bins, labels=["<=2.5k","2.5-4k","4-6k",">6k"])
36
+ X["tranche_sat_mean"] = pd.cut(X["sat_mean"], sat_mean_bins, labels=["basse","moyenne","bonne","excellente"])
37
+
38
+ return X
src/main.py CHANGED
@@ -1,81 +1,28 @@
1
 
2
- from typing import Any, Optional
3
- from fastapi import FastAPI, HTTPException
4
- import joblib
5
- import numpy as np
6
  import pandas as pd
 
7
 
8
- from shema.ModelFeatures import ModelFeatures
 
 
 
9
 
10
- _model: Optional[Any] = None
11
 
12
- app = FastAPI()
13
-
14
- LABELS = {
15
- "0": "reste_dans_l_entreprise",
16
- "1": "parti_de_l_entreprise",
17
- }
18
-
19
-
20
- def load_model():
21
- global _model
22
- if _model is None:
23
- _model = joblib.load('./artifacts/best_model.joblib')
24
- return _model
25
-
26
- def _safe_div(a, b):
27
- a = pd.to_numeric(a, errors="coerce")
28
- b = pd.to_numeric(b, errors="coerce").replace(0, np.nan)
29
- return (a / b).fillna(0.0)
30
-
31
-
32
- def compute_features(df: pd.DataFrame) -> pd.DataFrame:
33
- SAT_COLS = [
34
- "satisfaction_employee_environnement",
35
- "satisfaction_employee_nature_travail",
36
- "satisfaction_employee_equipe",
37
- "satisfaction_employee_equilibre_pro_perso",
38
- ]
39
-
40
- X = df.copy()
41
 
42
- X["sat_mean"] = X[SAT_COLS].astype(float).mean(axis=1)
43
- X["sat_std"] = X[SAT_COLS].astype(float).std(axis=1, ddof=0)
44
-
45
- X["delta_eval"] = X["note_evaluation_actuelle"].astype(float) - X["note_evaluation_precedente"].astype(float)
46
-
47
- X["ratio_post_stab"] = _safe_div(X["annes_sous_responsable_actuel"], X["annees_dans_le_poste_actuel"])
48
- X["revenu_par_niveau"] = _safe_div(X["revenu_mensuel"], X["niveau_hierarchique_poste"])
49
-
50
- age_bins = [-np.inf, 25, 35, 45, 60, np.inf]
51
- dist_bins = [-np.inf, 5, 10, 20, np.inf]
52
- revenu_bins = [-np.inf, 2500, 4000, 6000, np.inf]
53
- sat_mean_bins = [-np.inf, 2.0, 3.0, 4.0, np.inf]
54
-
55
-
56
- X["tranche_age"] = pd.cut(X["age"].astype(float), age_bins, labels=["<=25","26-35","36-45","46-60","60+"])
57
- X["tranche_distance"] = pd.cut(X["distance_domicile_travail"].astype(float), dist_bins, labels=["<=5","6-10","11-20",">20"])
58
- X["tranche_revenu"] = pd.cut(X["revenu_mensuel"].astype(float), revenu_bins, labels=["<=2.5k","2.5-4k","4-6k",">6k"])
59
- X["tranche_sat_mean"] = pd.cut(X["sat_mean"], sat_mean_bins, labels=["basse","moyenne","bonne","excellente"])
60
-
61
- return X
62
 
63
- @app.get("/")
64
- def read_root():
65
- return {"Hello": "World"}
66
 
67
- @app.post("/predict", tags=["inference"])
68
- def predict(body: ModelFeatures):
69
- try:
70
- m = load_model()
71
- raw = pd.DataFrame([body.model_dump()])
72
 
73
- X = compute_features(raw)
74
- proba = m.predict_proba(X)[0]
75
- i = int(proba.argmax())
76
- classes = getattr(m, "classes_", None)
77
- pred = str(classes[i]) if classes is not None else str(i)
78
- label = LABELS.get(pred, str(pred))
79
- return {"label": label, "proba": float(proba[i])}
80
- except Exception as e:
81
- raise HTTPException(status_code=400, detail=str(e))
 
1
 
2
+ from fastapi import FastAPI, Depends, HTTPException
 
 
 
3
  import pandas as pd
4
+ from contextlib import asynccontextmanager
5
 
6
+ from config.db import SessionLocal
7
+ from schemas.ModelFeatures import ModelFeatures
8
+ from model_loader import load_model
9
+ from features import compute_features
10
 
11
+ from sqlalchemy.orm import Session
12
 
13
+ from controllers.home_controller import router as ml_home_router
14
+ from controllers.predict_controller import router as predict_router
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ def get_db():
17
+ db = SessionLocal()
18
+ try:
19
+ yield db
20
+ finally:
21
+ db.close()
22
+
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ app = FastAPI()
 
 
25
 
26
+ app.include_router(ml_home_router)
 
 
 
 
27
 
28
+ app.include_router(predict_router)
 
 
 
 
 
 
 
 
src/model_loader.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from functools import lru_cache
3
+ from typing import Any
4
+ from huggingface_hub import hf_hub_download
5
+ import joblib
6
+
7
+ HF_REPO_ID = os.getenv("HF_REPO_ID", "Marintosti/attrition")
8
+ HF_TOKEN = os.getenv("HF_TOKEN")
9
+
10
+ @lru_cache(maxsize=1)
11
+ def load_model(name) -> Any:
12
+ local_path = hf_hub_download(
13
+ repo_id=HF_REPO_ID,
14
+ filename=f"{name}.joblib",
15
+ token=HF_TOKEN,
16
+ local_files_only=False,
17
+ )
18
+ return joblib.load(local_path)
src/models/base.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from sqlalchemy.orm import declarative_base
2
+
3
+ Base = declarative_base()
src/models/ml.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from datetime import datetime
3
+ from sqlalchemy import Column, String, Text, DateTime, Boolean
4
+ from sqlalchemy.dialects.postgresql import UUID
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+
8
+ from .base import Base
9
+
10
+
11
+ class MLModel(Base):
12
+ __tablename__ = "ml_models"
13
+
14
+ id: Mapped[uuid.UUID] = mapped_column(
15
+ UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
16
+ )
17
+ name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True)
18
+ description: Mapped[str | None] = mapped_column(Text, nullable=True)
19
+ created_at: Mapped[datetime] = mapped_column(
20
+ DateTime(timezone=True), default=datetime.utcnow, nullable=False
21
+ )
22
+ is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
23
+
src/models/ml_inputs.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from datetime import datetime, timezone
3
+ from sqlalchemy.orm import Mapped, mapped_column
4
+ from sqlalchemy import String, Integer, Boolean, DateTime
5
+ from sqlalchemy.dialects.postgresql import UUID
6
+ from .base import Base
7
+
8
+ class MLInput(Base):
9
+ __tablename__ = "ml_inputs"
10
+
11
+ id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
12
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True),
13
+ default=lambda: datetime.now(timezone.utc), nullable=False)
14
+
15
+ id_employee: Mapped[int] = mapped_column(Integer, index=True)
16
+ age: Mapped[int] = mapped_column(Integer)
17
+
18
+ genre: Mapped[str] = mapped_column(String(20))
19
+ revenu_mensuel: Mapped[int] = mapped_column(Integer)
20
+ statut_marital: Mapped[str] = mapped_column(String(50))
21
+ departement: Mapped[str] = mapped_column(String(100), index=True)
22
+ poste: Mapped[str] = mapped_column(String(100))
23
+
24
+ nombre_experiences_precedentes: Mapped[int] = mapped_column(Integer)
25
+ nombre_heures_travailless: Mapped[int] = mapped_column(Integer)
26
+ annee_experience_totale: Mapped[int] = mapped_column(Integer)
27
+ annees_dans_l_entreprise: Mapped[int] = mapped_column(Integer)
28
+ annees_dans_le_poste_actuel: Mapped[int] = mapped_column(Integer)
29
+
30
+ nombre_participation_pee: Mapped[int] = mapped_column(Integer)
31
+ nb_formations_suivies: Mapped[int] = mapped_column(Integer)
32
+ nombre_employee_sous_responsabilite: Mapped[int] = mapped_column(Integer)
33
+
34
+ code_sondage: Mapped[int] = mapped_column(Integer)
35
+ distance_domicile_travail: Mapped[int] = mapped_column(Integer)
36
+ niveau_education: Mapped[int] = mapped_column(Integer)
37
+ domaine_etude: Mapped[str] = mapped_column(String(100))
38
+
39
+ ayant_enfants: Mapped[str] = mapped_column(String(10))
40
+ frequence_deplacement: Mapped[str] = mapped_column(String(50))
41
+
42
+ annees_depuis_la_derniere_promotion: Mapped[int] = mapped_column(Integer)
43
+ annes_sous_responsable_actuel: Mapped[int] = mapped_column(Integer)
44
+ satisfaction_employee_environnement: Mapped[int] = mapped_column(Integer)
45
+ note_evaluation_precedente: Mapped[int] = mapped_column(Integer)
46
+ niveau_hierarchique_poste: Mapped[int] = mapped_column(Integer)
47
+ satisfaction_employee_nature_travail: Mapped[int] = mapped_column(Integer)
48
+ satisfaction_employee_equipe: Mapped[int] = mapped_column(Integer)
49
+ satisfaction_employee_equilibre_pro_perso: Mapped[int] = mapped_column(Integer)
50
+
51
+ eval_number: Mapped[str] = mapped_column(String(50))
52
+ note_evaluation_actuelle: Mapped[int] = mapped_column(Integer)
53
+ heure_supplementaires: Mapped[str] = mapped_column(String(10))
54
+ augementation_salaire_precedente: Mapped[int] = mapped_column(Integer)
src/{shema → schemas}/ModelFeatures.py RENAMED
File without changes
src/schemas/PredictItemResult.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class PredictItemResult(BaseModel):
6
+ label: str
7
+ proba: Optional[float] = None
src/schemas/PredictRequest.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from pydantic import BaseModel
3
+
4
+ from schemas.ModelFeatures import ModelFeatures
5
+
6
+ class PredictRequest(BaseModel):
7
+ model_name: str
8
+ inputs: List[ModelFeatures]
src/schemas/PredictResponse.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from pydantic import BaseModel
3
+
4
+ from schemas.PredictItemResult import PredictItemResult
5
+
6
+
7
+ class PredictResponse(BaseModel):
8
+ model_name: str
9
+ results: List[PredictItemResult]
src/{shema → schemas}/__init__.py RENAMED
File without changes
src/seeds/ml_models_seed.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/seeds/ml_models_seed.py
2
+ import os
3
+ from datetime import datetime, timezone
4
+ from sqlalchemy import create_engine, text
5
+ from sqlalchemy.orm import Session
6
+
7
+ # (optionnel) charge .env automatiquement
8
+ try:
9
+ from dotenv import load_dotenv
10
+ load_dotenv()
11
+ except Exception:
12
+ pass
13
+
14
+ DATABASE_URL = os.environ["DATABASE_URL"] # ex: postgresql+psycopg2://...
15
+ engine = create_engine(DATABASE_URL, future=True)
16
+
17
+ UPSERT = text("""
18
+ INSERT INTO ml_models (id, name, description, created_at, is_active)
19
+ VALUES (:id, :name, :description, :created_at, :is_active)
20
+ ON CONFLICT (name) DO UPDATE
21
+ SET description = EXCLUDED.description,
22
+ is_active = EXCLUDED.is_active
23
+ """)
24
+
25
+ def seed_ml_models(session: Session):
26
+ rows = [
27
+ {"id": "5b1c7b3a-0000-4000-8000-000000000001", "name": "baseline", "description": "Baseline model", "is_active": True},
28
+ {"id": "5b1c7b3a-0000-4000-8000-000000000002", "name": "xgboost_v1", "description": "XGB v1", "is_active": False},
29
+ ]
30
+ now = datetime.now(timezone.utc)
31
+ for r in rows:
32
+ session.execute(UPSERT, {**r, "created_at": now})
33
+
34
+ def main():
35
+ with Session(engine) as s:
36
+ seed_ml_models(s)
37
+ s.commit()
38
+
39
+ if __name__ == "__main__":
40
+ main()
utils/__pycache__/scoring.cpython-312.pyc CHANGED
Binary files a/utils/__pycache__/scoring.cpython-312.pyc and b/utils/__pycache__/scoring.cpython-312.pyc differ