pat/01-categorization-transactions

#2
by palexis3 - opened
This view is limited to 50 files because it contains too many changes. See the raw diff here.
Files changed (50) hide show
  1. .dockerignore +1 -3
  2. .env.example +0 -8
  3. .gitignore +1 -2
  4. .vscode/settings.json +0 -11
  5. Dockerfile +0 -9
  6. Dockerfile.local.postgres +0 -23
  7. Makefile +0 -14
  8. README.md +0 -30
  9. alembic.ini +0 -116
  10. app/api/routers/chat.py +10 -5
  11. app/api/routers/file_upload.py +0 -43
  12. app/api/routers/income_statement.py +0 -64
  13. app/api/routers/transaction.py +0 -30
  14. app/api/routers/user.py +0 -89
  15. app/categorization/__init__.py +0 -0
  16. app/categorization/categorizer.py +0 -147
  17. app/categorization/categorizer_list.py +0 -70
  18. app/categorization/config.py +0 -6
  19. app/categorization/file_processing.py +0 -128
  20. app/categorization/template.py +0 -56
  21. app/engine/postgresdb.py +0 -81
  22. app/model/base.py +0 -15
  23. app/model/income_statement.py +0 -46
  24. app/model/transaction.py +0 -62
  25. app/model/user.py +0 -110
  26. app/schema/base.py +0 -14
  27. app/schema/index.py +0 -85
  28. app/service/income_statement.py +0 -37
  29. app/service/llm.py +0 -55
  30. app/transactions_rag/__init__.py +0 -0
  31. app/transactions_rag/categorize_transactions.ipynb +0 -571
  32. app/transactions_rag/transactions_2024.csv +0 -29
  33. config/index.py +0 -34
  34. data/.gitkeep +0 -0
  35. data/tx_data/input/transactions_2024.csv +0 -28
  36. main.py +30 -53
  37. migration/README +0 -1
  38. migration/env.py +0 -91
  39. migration/script.py.mako +0 -26
  40. migration/versions/4e76691ab103_add_income_statement_table.py +0 -43
  41. migration/versions/7ea44cbc5b1f_default_timezone.py +0 -69
  42. migration/versions/8feaedca36f9_users_datatype_updates.py +0 -74
  43. migration/versions/cd515c44401d_add_users_transactions_tables.py +0 -55
  44. poetry.lock +91 -835
  45. pyproject.toml +1 -29
  46. requirements.txt +11 -0
  47. tests/conftest.py +0 -83
  48. tests/pytest.ini +0 -4
  49. tests/test_income_statement.py +0 -51
  50. tests/test_transactions.py +0 -25
.dockerignore CHANGED
@@ -1,5 +1,3 @@
1
  __pycache__
2
  storage
3
- data/*
4
- venv
5
- myvenv
 
1
  __pycache__
2
  storage
3
+ data/*
 
 
.env.example CHANGED
@@ -43,11 +43,3 @@ APP_HOST=0.0.0.0
43
 
44
  # The port to start the backend app.
45
  APP_PORT=8000
46
-
47
- # Postgres database configuration
48
- POSTGRES_USER=postgres
49
- POSTGRES_PASSWORD=postgres
50
- POSTGRES_DB_NAME=postgres
51
- POSTGRES_DB_HOST=localhost
52
- POSTGRES_DB_PORT=5432
53
- SQLALCHEMY_DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_DB_HOST}:${POSTGRES_DB_PORT}/${POSTGRES_DB_NAME}
 
43
 
44
  # The port to start the backend app.
45
  APP_PORT=8000
 
 
 
 
 
 
 
 
.gitignore CHANGED
@@ -1,5 +1,4 @@
1
  __pycache__
2
  storage
3
  .env
4
- venv
5
- myvenv
 
1
  __pycache__
2
  storage
3
  .env
4
+ data/*
 
.vscode/settings.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "jupyter.notebookFileRoot": "${workspaceFolder}",
3
- "[python]": {
4
- "editor.defaultFormatter": "ms-python.black-formatter",
5
- "editor.formatOnSave": true,
6
- },
7
- "ms-python.black-formatter.args": [
8
- "--line-length",
9
- "119"
10
- ]
11
- }
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile CHANGED
@@ -25,13 +25,4 @@ COPY . .
25
  # Make port 8000 available to the world outside this container
26
  EXPOSE 8000
27
 
28
- ENV POSTGRES_USER codepath_project_owner
29
- ENV POSTGRES_PASSWORD 03EdiworgCJz
30
- ENV POSTGRES_DB_NAME codepath_project
31
- ENV POSTGRES_DB_HOST ep-icy-cloud-a5m4mcgo.us-east-2.aws.neon.tech
32
- ENV POSTGRES_DB_PORT 5432
33
-
34
- # Run migrations
35
- RUN alembic upgrade head
36
-
37
  CMD ["python", "main.py"]
 
25
  # Make port 8000 available to the world outside this container
26
  EXPOSE 8000
27
 
 
 
 
 
 
 
 
 
 
28
  CMD ["python", "main.py"]
Dockerfile.local.postgres DELETED
@@ -1,23 +0,0 @@
1
- # Pull the official PostgreSQL 16 image
2
- FROM postgres:16
3
-
4
- # Create a directory for the database files
5
- RUN mkdir -p /var/lib/postgresql/data
6
-
7
- # Change the ownership of the data directory
8
- RUN chown -R postgres:postgres /var/lib/postgresql/data
9
-
10
- # Copy the configuration files into the data directory
11
- # NOTE: the [f] suffix is to ensure that the COPY command will not fail if the files don't exist
12
- COPY ./pg_hba.con[f] /var/lib/postgresql/data/pg_hba.conf
13
- COPY ./postgresql.con[f] /var/lib/postgresql/data/postgresql.conf
14
-
15
- # Include environment variables for the PostgreSQL user and password
16
- ENV POSTGRES_USER=${POSTGRES_USER}
17
- ENV POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
18
-
19
- # Expose the default PostgreSQL port
20
- EXPOSE 5432
21
-
22
- # Start the PostgreSQL server
23
- CMD ["postgres", "-c", "config_file=/var/lib/postgresql/data/postgresql.conf"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Makefile DELETED
@@ -1,14 +0,0 @@
1
-
2
- include .env
3
-
4
- build-postgres:
5
- docker build -t codepath_project_postgres -f Dockerfile.local.postgres .
6
-
7
- run-postgres:
8
- docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=${POSTGRES_PASSWORD} -e POSTGRES_USER=${POSTGRES_USER} --name ${POSTGRES_DB_NAME} codepath_project_postgres
9
-
10
- run-migrations:
11
- alembic upgrade head
12
-
13
- generate-migrations:
14
- alembic revision --autogenerate -m "$(migration_title)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -69,36 +69,6 @@ The API allows CORS for all origins to simplify development. You can change this
69
  ENVIRONMENT=prod python main.py
70
  ```
71
 
72
- ## Local Postgres database setup
73
-
74
- To setup a local postgres database, run:
75
-
76
- 1. Build the docker image:
77
-
78
- ```bash
79
- make build-postgres
80
- ```
81
-
82
- 2. Start the docker container:
83
-
84
- ```bash
85
- make run-postgres
86
- ```
87
-
88
- ## Running Migrations
89
-
90
- To generate new migrations, run:
91
-
92
- ```bash
93
- make generate-migrations migration_title="<name_for_migration>"
94
- ```
95
-
96
- To locally verify your changes, run:
97
-
98
- ```bash
99
- make run-migrations
100
- ```
101
-
102
  ## Using Docker
103
 
104
  1. Build an image for the FastAPI app:
 
69
  ENVIRONMENT=prod python main.py
70
  ```
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  ## Using Docker
73
 
74
  1. Build an image for the FastAPI app:
alembic.ini DELETED
@@ -1,116 +0,0 @@
1
- # A generic, single database configuration.
2
-
3
- [alembic]
4
- # path to migration scripts
5
- script_location = migration
6
-
7
- # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
8
- # Uncomment the line below if you want the files to be prepended with date and time
9
- # see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
10
- # for all available tokens
11
- # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
12
-
13
- # sys.path path, will be prepended to sys.path if present.
14
- # defaults to the current working directory.
15
- prepend_sys_path = .
16
-
17
- # timezone to use when rendering the date within the migration file
18
- # as well as the filename.
19
- # If specified, requires the python>=3.9 or backports.zoneinfo library.
20
- # Any required deps can installed by adding `alembic[tz]` to the pip requirements
21
- # string value is passed to ZoneInfo()
22
- # leave blank for localtime
23
- # timezone =
24
-
25
- # max length of characters to apply to the
26
- # "slug" field
27
- # truncate_slug_length = 40
28
-
29
- # set to 'true' to run the environment during
30
- # the 'revision' command, regardless of autogenerate
31
- # revision_environment = false
32
-
33
- # set to 'true' to allow .pyc and .pyo files without
34
- # a source .py file to be detected as revisions in the
35
- # versions/ directory
36
- # sourceless = false
37
-
38
- # version location specification; This defaults
39
- # to migration/versions. When using multiple version
40
- # directories, initial revisions must be specified with --version-path.
41
- # The path separator used here should be the separator specified by "version_path_separator" below.
42
- # version_locations = %(here)s/bar:%(here)s/bat:migration/versions
43
-
44
- # version path separator; As mentioned above, this is the character used to split
45
- # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
46
- # If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
47
- # Valid values for version_path_separator are:
48
- #
49
- # version_path_separator = :
50
- # version_path_separator = ;
51
- # version_path_separator = space
52
- version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
53
-
54
- # set to 'true' to search source files recursively
55
- # in each "version_locations" directory
56
- # new in Alembic version 1.10
57
- # recursive_version_locations = false
58
-
59
- # the output encoding used when revision files
60
- # are written from script.py.mako
61
- # output_encoding = utf-8
62
-
63
- sqlalchemy.url = driver://user:pass@localhost/dbname
64
-
65
-
66
- [post_write_hooks]
67
- # post_write_hooks defines scripts or Python functions that are run
68
- # on newly generated revision scripts. See the documentation for further
69
- # detail and examples
70
-
71
- # format using "black" - use the console_scripts runner, against the "black" entrypoint
72
- # hooks = black
73
- # black.type = console_scripts
74
- # black.entrypoint = black
75
- # black.options = -l 79 REVISION_SCRIPT_FILENAME
76
-
77
- # lint with attempts to fix using "ruff" - use the exec runner, execute a binary
78
- # hooks = ruff
79
- # ruff.type = exec
80
- # ruff.executable = %(here)s/.venv/bin/ruff
81
- # ruff.options = --fix REVISION_SCRIPT_FILENAME
82
-
83
- # Logging configuration
84
- [loggers]
85
- keys = root,sqlalchemy,alembic
86
-
87
- [handlers]
88
- keys = console
89
-
90
- [formatters]
91
- keys = generic
92
-
93
- [logger_root]
94
- level = WARN
95
- handlers = console
96
- qualname =
97
-
98
- [logger_sqlalchemy]
99
- level = WARN
100
- handlers =
101
- qualname = sqlalchemy.engine
102
-
103
- [logger_alembic]
104
- level = INFO
105
- handlers =
106
- qualname = alembic
107
-
108
- [handler_console]
109
- class = StreamHandler
110
- args = (sys.stderr,)
111
- level = NOTSET
112
- formatter = generic
113
-
114
- [formatter_generic]
115
- format = %(levelname)-5.5s [%(name)s] %(message)s
116
- datefmt = %H:%M:%S
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/routers/chat.py CHANGED
@@ -1,4 +1,4 @@
1
- from pydantic import BaseModel, ConfigDict
2
  from typing import List, Any, Optional, Dict, Tuple
3
  from fastapi import APIRouter, Depends, HTTPException, Request, status
4
  from llama_index.core.chat_engine.types import BaseChatEngine
@@ -19,8 +19,9 @@ class _Message(BaseModel):
19
 
20
  class _ChatData(BaseModel):
21
  messages: List[_Message]
22
- model_config = ConfigDict(
23
- json_schema_extra={
 
24
  "example": {
25
  "messages": [
26
  {
@@ -30,7 +31,6 @@ class _ChatData(BaseModel):
30
  ]
31
  }
32
  }
33
- )
34
 
35
 
36
  class _SourceNodes(BaseModel):
@@ -121,7 +121,12 @@ async def chat(
121
  yield VercelStreamResponse.convert_data(
122
  {
123
  "type": "sources",
124
- "data": {"nodes": [_SourceNodes.from_source_node(node).dict() for node in response.source_nodes]},
 
 
 
 
 
125
  }
126
  )
127
 
 
1
+ from pydantic import BaseModel
2
  from typing import List, Any, Optional, Dict, Tuple
3
  from fastapi import APIRouter, Depends, HTTPException, Request, status
4
  from llama_index.core.chat_engine.types import BaseChatEngine
 
19
 
20
  class _ChatData(BaseModel):
21
  messages: List[_Message]
22
+
23
+ class Config:
24
+ json_schema_extra = {
25
  "example": {
26
  "messages": [
27
  {
 
31
  ]
32
  }
33
  }
 
34
 
35
 
36
  class _SourceNodes(BaseModel):
 
121
  yield VercelStreamResponse.convert_data(
122
  {
123
  "type": "sources",
124
+ "data": {
125
+ "nodes": [
126
+ _SourceNodes.from_source_node(node).dict()
127
+ for node in response.source_nodes
128
+ ]
129
+ },
130
  }
131
  )
132
 
app/api/routers/file_upload.py DELETED
@@ -1,43 +0,0 @@
1
- from typing import Annotated
2
- from fastapi import APIRouter, UploadFile, Depends
3
- from app.categorization.file_processing import process_file, save_results
4
- from app.schema.index import FileUploadCreate
5
- import asyncio
6
- import os
7
- import csv
8
-
9
- from app.engine.postgresdb import get_db_session
10
- from sqlalchemy.ext.asyncio import AsyncSession
11
-
12
- file_upload_router = r = APIRouter(prefix="/api/v1/file_upload", tags=["file_upload"])
13
-
14
- @r.post(
15
- "/",
16
- responses={
17
- 200: {"description": "File successfully uploaded"},
18
- 400: {"description": "Bad request"},
19
- 500: {"description": "Internal server error"},
20
- },
21
- )
22
- async def create_file(input_file: UploadFile, db: AsyncSession = Depends(get_db_session)):
23
- try:
24
- # Create directory to store all uploaded .csv files
25
- file_upload_directory_path = "data/tx_data/input"
26
- if not os.path.exists(file_upload_directory_path):
27
- os.makedirs(file_upload_directory_path)
28
-
29
- # Write items of .csv filte to directory
30
- with open(os.path.join(file_upload_directory_path, input_file.filename)) as output_file:
31
- [output_file.write(" ".join(row)+'\n') for row in csv.reader(input_file)]
32
- output_file.close()
33
-
34
- # With the newly created file and it's path, process and save it for embedding
35
- processed_file = process_file(os.path.realpath(input_file.filename))
36
- result = await asyncio.gather(processed_file)
37
- await save_results(db, result)
38
-
39
- except Exception:
40
- return {"message": "There was an error uploading this file. Ensure you have a .csv file with the following columns:"
41
- "\n transaction_date, type, category, name_description, amount"}
42
-
43
- return {"message": f"Successfully uploaded {input_file.filename}"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/routers/income_statement.py DELETED
@@ -1,64 +0,0 @@
1
- from typing import List
2
- from fastapi import APIRouter, Depends, HTTPException, status
3
- from sqlalchemy.ext.asyncio import AsyncSession
4
- from app.model.transaction import Transaction as TransactionModel
5
- from app.model.income_statement import IncomeStatement as IncomeStatementModel
6
- from app.schema.index import IncomeStatementCreateRequest, IncomeStatementResponse
7
- from app.engine.postgresdb import get_db_session
8
- from app.service.income_statement import call_llm_to_create_income_statement
9
-
10
- income_statement_router = r = APIRouter(prefix="/api/v1/income_statement", tags=["income_statement"])
11
-
12
-
13
- @r.post(
14
- "/",
15
- responses={
16
- 200: {"description": "New transaction created"},
17
- 400: {"description": "Bad request"},
18
- 500: {"description": "Internal server error"},
19
- },
20
- )
21
- async def create_income_statement(payload: IncomeStatementCreateRequest, db: AsyncSession = Depends(get_db_session)) -> None:
22
- try:
23
- await call_llm_to_create_income_statement(payload, db)
24
-
25
- except Exception as e:
26
- raise HTTPException(status_code=500, detail=str(e))
27
-
28
-
29
- @r.get(
30
- "/user/{user_id}",
31
- response_model=List[IncomeStatementResponse],
32
- responses={
33
- 200: {"description": "New user created"},
34
- 400: {"description": "Bad request"},
35
- 204: {"description": "No content"},
36
- 500: {"description": "Internal server error"},
37
- },
38
- )
39
- async def get_income_statements(
40
- user_id: int, db: AsyncSession = Depends(get_db_session)
41
- ) -> List[IncomeStatementResponse]:
42
- """
43
- Retrieve all income statements.
44
- """
45
- result = await IncomeStatementModel.get_by_user(db, user_id)
46
- if len(result) == 0:
47
- raise HTTPException(status_code=status.HTTP_204_NO_CONTENT, detail="No income statements found for this user")
48
- return result
49
-
50
-
51
- @r.get(
52
- "/report/{report_id}",
53
- response_model=IncomeStatementResponse,
54
- responses={
55
- 200: {"description": "Income statement found"},
56
- 404: {"description": "Income statement not found"},
57
- 500: {"description": "Internal server error"},
58
- },
59
- )
60
- async def get_income_statement(report_id: int, db: AsyncSession = Depends(get_db_session)) -> IncomeStatementResponse:
61
- income_statement = await IncomeStatementModel.get(db, id=report_id)
62
- if not income_statement:
63
- raise HTTPException(status_code=404, detail="Income statement not found")
64
- return income_statement
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/routers/transaction.py DELETED
@@ -1,30 +0,0 @@
1
- import os
2
- from typing import List
3
- from fastapi import APIRouter, Depends, HTTPException, status
4
- from sqlalchemy.ext.asyncio import AsyncSession
5
- from app.model.transaction import Transaction as TransactionModel
6
- from app.schema.index import TransactionResponse
7
- from app.engine.postgresdb import get_db_session
8
-
9
- transaction_router = r = APIRouter(prefix="/api/v1/transactions", tags=["transactions"])
10
-
11
-
12
- @r.get(
13
- "/{user_id}",
14
- response_model=List[TransactionResponse],
15
- responses={
16
- 200: {"description": "New user created"},
17
- 400: {"description": "Bad request"},
18
- 204: {"description": "No content"},
19
- 500: {"description": "Internal server error"},
20
- },
21
- )
22
- async def get_transactions(user_id: int, db: AsyncSession = Depends(get_db_session)):
23
- """
24
- Retrieve all transactions.
25
- """
26
- result = await TransactionModel.get_by_user(db, user_id)
27
- all_rows = result.all()
28
- if len(all_rows) == 0:
29
- raise HTTPException(status_code=status.HTTP_204_NO_CONTENT, detail="No transactions found for this user")
30
- return all_rows
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/routers/user.py DELETED
@@ -1,89 +0,0 @@
1
- import logging
2
- from fastapi import APIRouter, Depends, HTTPException
3
- from sqlalchemy.ext.asyncio import AsyncSession
4
-
5
- from app.engine.postgresdb import get_db_session
6
- from app.schema.index import UserCreate, User as UserSchema, UserResponse, UserUpdate
7
- from app.model.user import User as UserModel
8
-
9
-
10
- user_router = r = APIRouter(prefix="/api/v1/users", tags=["users"])
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- @r.post(
15
- "/",
16
- response_model=UserResponse,
17
- responses={
18
- 200: {"description": "New user created"},
19
- 400: {"description": "Bad request"},
20
- 409: {"description": "Conflict"},
21
- 500: {"description": "Internal server error"},
22
- },
23
- )
24
- async def create_user(user: UserCreate, db: AsyncSession = Depends(get_db_session)):
25
- try:
26
- db_user = await UserModel.get(db, email=user.email)
27
- if db_user and not db_user.is_deleted:
28
- raise HTTPException(status_code=409, detail="User already exists")
29
-
30
- await UserModel.create(db, **user.model_dump())
31
- user = await UserModel.get(db, email=user.email)
32
- return user
33
- except Exception as e:
34
- raise HTTPException(status_code=500, detail=str(e))
35
-
36
-
37
- @r.get(
38
- "/{email}",
39
- response_model=UserResponse,
40
- responses={
41
- 200: {"description": "User found"},
42
- 404: {"description": "User not found"},
43
- 500: {"description": "Internal server error"},
44
- },
45
- )
46
- async def get_user(email: str, db: AsyncSession = Depends(get_db_session)):
47
- user = await UserModel.get(db, email=email)
48
- if not user:
49
- raise HTTPException(status_code=404, detail="User not found")
50
- return user
51
-
52
-
53
- @r.put(
54
- "/{email}",
55
- response_model=UserResponse,
56
- responses={
57
- 200: {"description": "User updated"},
58
- 404: {"description": "User not found"},
59
- 500: {"description": "Internal server error"},
60
- },
61
- )
62
- async def update_user(email: str, user_payload: UserUpdate, db: AsyncSession = Depends(get_db_session)):
63
- try:
64
- user = await UserModel.get(db, email=email)
65
- if not user:
66
- raise HTTPException(status_code=404, detail="User not found")
67
- await UserModel.update(db, id=user.id, **user_payload.model_dump())
68
- user = await UserModel.get(db, email=email)
69
- return user
70
- except Exception as e:
71
- raise HTTPException(status_code=500, detail=str(e))
72
-
73
-
74
- @r.delete(
75
- "/{email}",
76
- response_model=UserResponse,
77
- responses={
78
- 200: {"description": "User deleted"},
79
- 404: {"description": "User not found"},
80
- 500: {"description": "Internal server error"},
81
- },
82
- )
83
- async def delete_user(email: str, db: AsyncSession = Depends(get_db_session)):
84
- user = await UserModel.get(db, email=email)
85
- if not user:
86
- raise HTTPException(status_code=404, detail="User not found")
87
- await UserModel.delete(db, email=email)
88
- user = await UserModel.get(db, email=email)
89
- return user
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/categorization/__init__.py DELETED
File without changes
app/categorization/categorizer.py DELETED
@@ -1,147 +0,0 @@
1
- # Standard library imports
2
- import re
3
- import ast
4
- import json
5
- import logging
6
- import os
7
- from typing import Any, List, Tuple, Optional, Dict, Union
8
-
9
- # Third-party library imports
10
- import numpy as np
11
- import pandas as pd
12
- import asyncio
13
- from rapidfuzz import process
14
- from tenacity import retry, wait_random_exponential, stop_after_attempt
15
- from pydantic import ValidationError
16
-
17
- # Local application/library specific imports
18
- from langchain_openai import ChatOpenAI
19
- from langchain.chains import LLMChain
20
- from langchain.output_parsers import PydanticOutputParser, OutputFixingParser
21
- from langchain.prompts import PromptTemplate
22
- import app.categorization.template as CATEGORY_TEMPLATE
23
- from app.categorization.config import CATEGORY_REFERENCE_OUTPUT_FILE, TX_PER_LLM_RUN
24
-
25
-
26
- def fuzzy_match_list_categorizer(
27
- description: str,
28
- descriptions: np.ndarray,
29
- description_category_pairs: pd.DataFrame,
30
- threshold: int = 75,
31
- ) -> Optional[str]:
32
- """Find the most similar transaction description and return its category.
33
-
34
- This function uses fuzzy string matching to compare the input description
35
- against a list of known descriptions. If a sufficient match is found,
36
- the function returns the category associated with the matched description.
37
-
38
- Args:
39
- description (str): The transaction description to categorize.
40
- descriptions (np.ndarray): Known descriptions to compare against.
41
- description_category_pairs (pd.DataFrame): DataFrame mapping descriptions to categories.
42
- threshold (int): Minimum similarity score to consider a match.
43
-
44
- Returns:
45
- str or None: Category of the matched description, or None if no match found.
46
- """
47
-
48
- # Fuzzy-match this description against the reference descriptions
49
- match_results = process.extractOne(
50
- description, descriptions, score_cutoff=threshold)
51
-
52
- # If a match is found, return the category of the matched description
53
- if match_results:
54
- return description_category_pairs.at[match_results[2], 'category']
55
-
56
- return None
57
-
58
-
59
- async def llm_list_categorizer(tx_list: pd.DataFrame) -> pd.DataFrame:
60
- """Categorize a list of transactions using a language model.
61
-
62
- This function uses a Language Model (LLM) to categorize a list of transaction descriptions.
63
- It splits the input DataFrame into chunks and processes each chunk asynchronously to improve performance.
64
-
65
- Args:
66
- tx_list (pd.DataFrame): DataFrame containing the transaction descriptions to categorize.
67
-
68
- Returns:
69
- pd.DataFrame: DataFrame mapping transaction descriptions to their inferred categories.
70
- """
71
-
72
- # Initialize language model and prompt
73
- openai_api_key = os.environ['OPENAI_API_KEY']
74
- llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-0125",
75
- api_key=openai_api_key)
76
- prompt = PromptTemplate.from_template(template=CATEGORY_TEMPLATE)
77
- chain = LLMChain(llm=llm, prompt=prompt)
78
-
79
- # Iterate over the DataFrame in batches of TX_PER_LLM_RUN transactions
80
- tasks = [llm_sublist_categorizer(tx_list.attrs['file_name'], chain=chain, tx_descriptions="\n".join(chunk['name/description']).strip())
81
- for chunk in np.array_split(tx_list, tx_list.shape[0] // TX_PER_LLM_RUN + 1)]
82
-
83
- # Gather results and extract (valid) outputs
84
- # The results variable is a list of 'results', each 'result' being the output of a single LLM run
85
- results = await asyncio.gather(*tasks)
86
-
87
- # Extract valid results (each valid result is a list of description-category pairs)
88
- valid_results = [result['output'] for result in results if result['valid']]
89
-
90
- # Flatten the list of valid results to obtain a single list of description-category pairs
91
- valid_outputs = [
92
- output for valid_result in valid_results for output in valid_result]
93
-
94
- # Return a DataFrame with the valid outputs
95
- return pd.DataFrame(valid_outputs, columns=['name/description', 'category'])
96
-
97
-
98
- @retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6))
99
- async def llm_sublist_categorizer(
100
- file_name: str,
101
- chain: LLMChain,
102
- tx_descriptions: str,
103
- ) -> Dict[str, Union[bool, List[Tuple[str, str]]]]:
104
- """Categorize a batch of transactions using a language model.
105
-
106
- This function takes a batch of transaction descriptions and passes them to a language model
107
- for categorization. The function retries on failure, with an exponential backoff.
108
-
109
- Args:
110
- file_name (str): Name of the file the transaction descriptions were extracted from.
111
- chain (LLMChain): Language model chain to use for categorization.
112
- tx_descriptions (str): Concatenated transaction descriptions to categorize.
113
-
114
- Returns:
115
- dict: Dictionary containing a 'valid' flag and a list of categorized descriptions.
116
- """
117
-
118
- raw_result = await chain.arun(input_data=tx_descriptions)
119
-
120
- logger = logging.getLogger(__name__)
121
- result = {'valid': True, 'output': []}
122
- try:
123
- # Create a pattern to match a list Description-Category pairs (List[Tuple[str, str]])
124
- pattern = r"\['([^']+)', '([^']+)'\]"
125
-
126
- # Use it to extract all the correctly formatted pairs from the raw result
127
- matches = re.findall(pattern, raw_result.replace("\\'", "'"))
128
-
129
- # Loop over the matches, and try to parse them to ensure the content is valid
130
- valid_outputs = []
131
- for match in matches:
132
- try:
133
- parsed_pair = ast.literal_eval(str(list(match)))
134
- valid_outputs.append(parsed_pair)
135
- except Exception as e:
136
- logger.log(logging.ERROR,
137
- f"Parsing Error: {e}\nMatch: {match}\n")
138
- result['valid'] = False
139
-
140
- result['output'] = valid_outputs
141
-
142
- except Exception as e:
143
- logging.log(
144
- logging.ERROR, f"| File: {file_name} | Unexpected Error: {e}\nRaw Result: {raw_result}")
145
- result['valid'] = False
146
-
147
- return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/categorization/categorizer_list.py DELETED
@@ -1,70 +0,0 @@
1
- # Standard library imports
2
- import os
3
- from datetime import datetime
4
-
5
- # Third-party library imports
6
- import pandas as pd
7
-
8
- # Local application/library specific imports
9
- from app.categorization.config import CATEGORY_REFERENCE_OUTPUT_FILE
10
- from app.categorization.categorizer import llm_list_categorizer, fuzzy_match_list_categorizer
11
-
12
-
13
- async def categorize_list(tx_list: pd.DataFrame) -> pd.DataFrame:
14
- """Asynchronously categorize a list of transactions.
15
-
16
- This function categorizes a list of transactions using a combination of fuzzy matching
17
- and a language model. It looks up new transaction descriptions in the reference file
18
- (a combination of user input and previous executions) to minimize API calls.
19
- Any uncategorized transactions are sent to the language model, and new description-category
20
- pairs are added to the reference file.
21
-
22
- Args:
23
- tx_list (pd.DataFrame): The list of transactions to categorize.
24
-
25
- Returns:
26
- pd.DataFrame: The original DataFrame with an additional column for the category.
27
- """
28
-
29
- if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
30
- # Read description-category pairs from the reference file
31
- description_category_pairs = pd.read_csv(
32
- CATEGORY_REFERENCE_OUTPUT_FILE, header=None, names=['name/description', 'category']
33
- )
34
-
35
- # Extract only descriptions for faster matching
36
- descriptions = description_category_pairs['name/description'].values
37
-
38
- # Use fuzzy matching to find similar descriptions and assign the category
39
- tx_list['category'] = tx_list['name/description'].apply(
40
- fuzzy_match_list_categorizer,
41
- args=(descriptions, description_category_pairs),
42
- )
43
-
44
- # Filter out uncategorized transactions, deduplicate, and sort by description
45
- uncategorized_descriptions = (
46
- tx_list[tx_list['category'].isnull()]
47
- .drop_duplicates(subset=['name/description'])
48
- .sort_values(by=['name/description'])
49
- )
50
-
51
- # Ask the language model to categorize the remaining descriptions
52
- if not uncategorized_descriptions.empty:
53
- categorized_descriptions = await llm_list_categorizer(
54
- uncategorized_descriptions[['name/description', 'category']]
55
- )
56
-
57
- categorized_descriptions.dropna(inplace=True)
58
-
59
- # Update the category for uncategorized transactions based on the language model results
60
- if not categorized_descriptions.empty:
61
- tx_list['category'] = tx_list['category'].fillna(
62
- tx_list['name/description'].map(
63
- categorized_descriptions.set_index('name/description')['category']
64
- )
65
- )
66
-
67
- # Fill remaining NaN values in 'category' with 'Other'
68
- tx_list['category'] = tx_list['category'].fillna('Other')
69
-
70
- return tx_list
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/categorization/config.py DELETED
@@ -1,6 +0,0 @@
1
- # Data Folders
2
- RESULT_OUTPUT_FILE = 'data/tx_data/output/result_master_data.csv'
3
- CATEGORY_REFERENCE_OUTPUT_FILE = 'data/ref_data/category_ref_master_data.csv'
4
-
5
- # LLM CONFIG
6
- TX_PER_LLM_RUN = 10
 
 
 
 
 
 
 
app/categorization/file_processing.py DELETED
@@ -1,128 +0,0 @@
1
- import os
2
- import glob
3
- import shutil
4
- import logging
5
- import warnings
6
- from typing import Optional, Union, Dict, List
7
- from datetime import datetime
8
-
9
- import pandas as pd
10
- from dateparser import parse
11
-
12
- from app.categorization.categorizer_list import categorize_list
13
- from app.categorization.config import RESULT_OUTPUT_FILE, CATEGORY_REFERENCE_OUTPUT_FILE
14
- from app.model.transaction import Transaction
15
- from app.schema.index import TransactionCreate
16
-
17
- from sqlalchemy.ext.asyncio import AsyncSession
18
-
19
-
20
- # Read file and process it (e.g. categorize transactions)
21
- async def process_file(file_path: str) -> Dict[str, Union[str, pd.DataFrame]]:
22
- """
23
- Process the input file by reading, cleaning, standardizing, and categorizing the transactions.
24
-
25
- Args:
26
- file_path (str): Path to the input file.
27
-
28
- Returns:
29
- Dict[str, Union[str, pd.DataFrame]]: Dictionary containing the file name, processed output, and error information if any
30
- """
31
-
32
- file_name = os.path.basename(file_path)
33
- result = {"file_name": file_name, "output": pd.DataFrame(), "error": ""}
34
- try:
35
- # Read file into standardized tx format: source, date, type, category, description, amount
36
- tx_list = standardize_csv_file(file_path)
37
-
38
- # Categorize transactions
39
- result["output"] = await categorize_list(tx_list)
40
- print(f"File processed sucessfully: {file_name}")
41
-
42
- except Exception as e:
43
- # Return an error indicator and exception info
44
- logging.log(logging.ERROR, f"| File: {file_name} | Unexpected Error: {e}")
45
- print(f"ERROR processing file {file_name}: {e}")
46
- result["error"] = str(e)
47
-
48
- return result
49
-
50
-
51
- def standardize_csv_file(file_path: str) -> pd.DataFrame:
52
- """
53
- Read and prepare the data from the input file.
54
-
55
- Args:
56
- file_path (str): Path to the input file.
57
-
58
- Returns:
59
- pd.DataFrame: Prepared transaction data.
60
- """
61
-
62
- tx_list = pd.read_csv(file_path, index_col=False)
63
- tx_list.attrs["file_name"] = file_path
64
- tx_list.columns = tx_list.columns.str.lower().str.strip()
65
-
66
- # Standardize dates to YYYY/MM/DD format
67
- tx_list["date"] = pd.to_datetime(tx_list["date"]).dt.strftime("%Y/%m/%d")
68
-
69
- # Add source and reindex to desired tx format; category column is new and therefore empty
70
- tx_list.loc[:, "source"] = os.path.basename(file_path)
71
- tx_list = tx_list.reindex(columns=["date", "expense/income", "category", "name/description", "amount"])
72
-
73
- return tx_list
74
-
75
-
76
- async def save_results(db: AsyncSession, results: List) -> None:
77
- """
78
- Merge all interim results in the input folder and write the merged results to the output file.
79
-
80
- Args:
81
- in_folder (str): Path to the input folder containing interim results.
82
- out_file (str): Path to the output file.
83
-
84
- Returns:
85
- None
86
- """
87
-
88
- # Concatenate all (valid) results into a single DataFrame
89
- # Print errors to console
90
- ok_files = []
91
- ko_files = []
92
- error_messages = []
93
-
94
- col_list = ["transaction_date", "type", "category", "name_description", "amount"]
95
- tx_list = pd.DataFrame(columns=col_list)
96
- for result in results:
97
- if not result["error"]:
98
- ok_files.append(result["file_name"])
99
- result_df = result["output"]
100
- result_df.columns = col_list
101
- tx_list = pd.concat([tx_list, result_df], ignore_index=True)
102
- else:
103
- ko_files.append(result["file_name"])
104
- error_messages.append(f"{result['file_name']}: {result['error']}")
105
-
106
- # Save to database
107
- # FIXME: get user_id from session
108
- txn_list_to_save = [TransactionCreate(**row.to_dict(), user_id=1) for _, row in tx_list.iterrows()]
109
- await Transaction.bulk_create(db, txn_list_to_save)
110
-
111
- new_ref_data = tx_list[["name/description", "category"]]
112
- if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
113
- # If it exists, add master file to interim results
114
- old_ref_data = pd.read_csv(CATEGORY_REFERENCE_OUTPUT_FILE, names=["name/description", "category"], header=0)
115
- new_ref_data = pd.concat([old_ref_data, new_ref_data], ignore_index=True)
116
-
117
- # Drop duplicates, sort, and write to create new Master File
118
- new_ref_data.drop_duplicates(subset=["name/description"]).sort_values(by=["name/description"]).to_csv(
119
- CATEGORY_REFERENCE_OUTPUT_FILE, mode="w", index=False, header=True
120
- )
121
-
122
- # Summarize results
123
- print(f"\nProcessed {len(results)} files: {len(ok_files)} successful, {len(ko_files)} with errors\n")
124
- if len(ko_files):
125
- print(f"Errors in the following files:")
126
- for message in error_messages:
127
- print(f" {message}")
128
- print("\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/categorization/template.py DELETED
@@ -1,56 +0,0 @@
1
- CATEGORY_TEMPLATE = """
2
- <context>
3
- Your task is to create a list of [description, category] lists, where in each list item:
4
- - the description is exactly the same you receive as input
5
- - the category of the transaction (choose one from the list below based on the description)
6
- </context>
7
-
8
-
9
- <categories>
10
- The following list contains the categories and associated keywords you often see in transaction descriptions:
11
- -ATM: atm, cash, withdraw
12
- -Auto: auto body
13
- -Bars: bar, pubs, irish, brewery
14
- -Beauty: body
15
- -Cashback: cashback, reward, bonus, cash back
16
- -Clothing: clothing, shoes, accessories
17
- -Coffee Shops: coffee, cafe, tea, Starbucks, Dunkin
18
- -Credit Card Payment: card payment, autopay
19
- -Education: kindle, tuition
20
- -Entertainment: event, show, movies, cinema, theater
21
- -Fees: Fee
22
- -Food: snack, Donalds, Burger King, KFC, Subway, Pizza, Domino, Taco Bell, Wendy, Chick-fil-A, Popeyes, Arby's, Chipotle
23
- -Fuel: fuel, gas, petrol
24
- -Gifts: donation, gift
25
- -Groceries: groceries, supermarket, food, familia
26
- -Gym: gym, fitness, yoga, pilates, crossfit
27
- -Home: Ikea
28
- -Housing: rent, mortgage
29
- -Income: refund, deposit, paycheck
30
- -Insurance: insurance
31
- -Medical: medical, doctor, dentist, hospital, clinic
32
- -Pets: vet, veterinary, pet, dog, cat
33
- -Pharmacy: pharmacy, drugstore, cvs, walgreens, rite aid, duane
34
- -Restaurants: restaurant, lunch, dinner
35
- -Services: service, laundry, dry cleaning
36
- -Shopping: shopping, amazon, walmart, target, safeway
37
- -Streaming: Netflix, Spotify, Hulu, HBO
38
- -Taxes: tax, irs
39
- -Technology: technology, software, hardware, electronics
40
- -Transportation: bus, train, subway, metro, airline, uber, lyft, taxi
41
- -Travel: travel, holiday, trip, airbnb, kiwi, hotel, hostel, resort, kiwi, kayak, expedia, booking.com
42
- -Transfer: payment from
43
- -Utilities: electricity, water, gas, ting, verizon, comcast, sprint, t-mobile, at&t, mint
44
- -Other: use this when very uncertain about the category
45
- </categories>
46
-
47
- <formatting_instructions>
48
- Your output should be a valid list of lists (e.g. [[description1, category1], [description2, category2], ...]] parse-able by the command ast.literal_eval(output)
49
- Don't include any kind of commentary, return carriages, spaces or other characters in the output
50
- Fill all categories; if you don't know which one to choose, choose 'Other'
51
- </formatting_instructions>
52
-
53
-
54
- <financial_transactions>
55
- {input_data}
56
- </financial_transactions>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/engine/postgresdb.py DELETED
@@ -1,81 +0,0 @@
1
- import logging
2
- import contextlib
3
- from typing import Any, AsyncIterator
4
-
5
- from sqlalchemy.ext.asyncio import (
6
- AsyncConnection,
7
- AsyncSession,
8
- AsyncEngine,
9
- async_sessionmaker,
10
- create_async_engine,
11
- )
12
- from sqlalchemy.orm import declarative_base
13
-
14
- from config.index import config as env
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
- Base = declarative_base()
19
-
20
-
21
- class PostgresDatabase:
22
-
23
- def __init__(self):
24
- self._engine: AsyncEngine | None = None
25
- self._sessionmaker: async_sessionmaker | None = None
26
-
27
- def init(self, host: str, engine_kwargs: dict[str, Any] = {}):
28
- self._engine = create_async_engine(host, **engine_kwargs)
29
- self._sessionmaker = async_sessionmaker(autocommit=False, bind=self._engine)
30
-
31
- async def close(self):
32
- if self._engine is None:
33
- raise Exception("DatabaseSessionManager is not initialized")
34
- await self._engine.dispose()
35
-
36
- self._engine = None
37
- self._sessionmaker = None
38
-
39
- @contextlib.asynccontextmanager
40
- async def connect(self) -> AsyncIterator[AsyncConnection]:
41
- if self._engine is None:
42
- raise Exception("DatabaseSessionManager is not initialized")
43
-
44
- async with self._engine.begin() as connection:
45
- try:
46
- yield connection
47
- except Exception:
48
- await connection.rollback()
49
- raise
50
-
51
- @contextlib.asynccontextmanager
52
- async def session(self) -> AsyncIterator[AsyncSession]:
53
- if self._sessionmaker is None:
54
- raise Exception("DatabaseSessionManager is not initialized")
55
-
56
- session = self._sessionmaker()
57
- try:
58
- yield session
59
- except Exception:
60
- await session.rollback()
61
- raise
62
- finally:
63
- await session.close()
64
-
65
- def get_engine(self):
66
- return self._engine
67
-
68
- # Used for testing
69
- async def create_all(self, connection: AsyncConnection):
70
- await connection.run_sync(Base.metadata.create_all)
71
-
72
- async def drop_all(self, connection: AsyncConnection):
73
- await connection.run_sync(Base.metadata.drop_all)
74
-
75
-
76
- postgresdb = PostgresDatabase()
77
-
78
-
79
- async def get_db_session():
80
- async with postgresdb.session() as session:
81
- yield session
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/model/base.py DELETED
@@ -1,15 +0,0 @@
1
- from datetime import datetime, timezone
2
- from sqlalchemy import DateTime
3
- from sqlalchemy.orm import Mapped, mapped_column
4
-
5
- from app.engine.postgresdb import Base
6
-
7
-
8
- class BaseModel:
9
- id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True)
10
- created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now(timezone.utc))
11
- updated_at: Mapped[datetime] = mapped_column(
12
- DateTime(timezone=True),
13
- default=datetime.now(timezone.utc),
14
- onupdate=datetime.now(timezone.utc),
15
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/model/income_statement.py DELETED
@@ -1,46 +0,0 @@
1
- from datetime import datetime
2
- from typing import Any, Dict, List
3
- from sqlalchemy import ForeignKey
4
- from sqlalchemy.sql import expression as sql
5
- from sqlalchemy.orm import relationship, Mapped, mapped_column
6
- from sqlalchemy.ext.asyncio import AsyncSession
7
- from sqlalchemy.dialects.postgresql import JSON
8
-
9
- from app.model.base import BaseModel
10
- from app.engine.postgresdb import Base
11
- from app.schema.index import IncomeStatement as IncomeStatementSchema
12
-
13
-
14
- class IncomeStatement(Base, BaseModel):
15
- __tablename__ = "income_statement"
16
-
17
- date_from: Mapped[datetime]
18
- date_to: Mapped[datetime]
19
- income: Mapped[Dict] = mapped_column(JSON, nullable=True)
20
- expenses: Mapped[Dict] = mapped_column(JSON, nullable=True)
21
-
22
- user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
23
- user = relationship("User", back_populates="income_statements")
24
-
25
- def __str__(self) -> str:
26
- return f"IncomeStatement(id={self.id}, user_id={self.user_id}, date_from={self.date_from}, date_to={self.date_to}, income={self.income}, expenses={self.expenses})"
27
-
28
- @classmethod
29
- async def create(cls: "type[IncomeStatement]", db: AsyncSession, **kwargs: IncomeStatementSchema) -> "IncomeStatement":
30
- income_statement = cls(**kwargs)
31
- db.add(income_statement)
32
- await db.commit()
33
- await db.refresh(income_statement)
34
- return income_statement
35
-
36
- @classmethod
37
- async def get_by_user(cls: "type[IncomeStatement]", db: AsyncSession, user_id: int) -> "List[IncomeStatement]":
38
- query = sql.select(cls).where(cls.user_id == user_id)
39
- income_statements = await db.scalars(query)
40
- return income_statements.all()
41
-
42
- @classmethod
43
- async def get(cls: "type[IncomeStatement]", db: AsyncSession, id: int) -> "IncomeStatement":
44
- query = sql.select(cls).where(cls.id == id)
45
- income_statement = await db.scalar(query)
46
- return income_statement
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/model/transaction.py DELETED
@@ -1,62 +0,0 @@
1
- from datetime import datetime
2
- from typing import List
3
- from sqlalchemy import ForeignKey
4
- from sqlalchemy.orm import relationship, Mapped, mapped_column
5
- from sqlalchemy.sql import expression as sql
6
- from sqlalchemy.ext.asyncio import AsyncSession
7
-
8
- from app.model.base import BaseModel
9
- from app.engine.postgresdb import Base
10
- from app.schema.index import TransactionCreate
11
-
12
-
13
- class Transaction(Base, BaseModel):
14
- __tablename__ = "transactions"
15
- transaction_date: Mapped[datetime]
16
- category: Mapped[str]
17
- name_description: Mapped[str]
18
- amount: Mapped[float]
19
- type: Mapped[str]
20
-
21
- user_id = mapped_column(ForeignKey("users.id"))
22
- user = relationship("User", back_populates="transactions")
23
-
24
- def __str__(self) -> str:
25
- return f"{self.transaction_date}, {self.category}, {self.name_description}, {self.amount}, {self.type}"
26
-
27
- @classmethod
28
- async def create(cls: "type[Transaction]", db: AsyncSession, **kwargs) -> "Transaction":
29
- query = sql.insert(cls).values(**kwargs)
30
- transactions = await db.execute(query)
31
- transaction = transactions.first()
32
- await db.commit()
33
- return transaction
34
-
35
- @classmethod
36
- async def bulk_create(cls: "type[Transaction]", db: AsyncSession, transactions: List[TransactionCreate]) -> None:
37
- values = [transaction.model_dump() for transaction in transactions]
38
- query = sql.insert(cls).values(values)
39
- await db.execute(query)
40
- await db.commit()
41
-
42
- @classmethod
43
- async def update(cls: "type[Transaction]", db: AsyncSession, id: int, **kwargs) -> "Transaction":
44
- query = sql.update(cls).where(cls.id == id).values(**kwargs).execution_options(synchronize_session="fetch")
45
- transactions = await db.scalars(query)
46
- transaction = transactions.first()
47
- await db.commit()
48
- return transaction
49
-
50
- @classmethod
51
- async def get_by_user(cls: "type[Transaction]", db: AsyncSession, user_id: int) -> "List[Transaction]":
52
- query = sql.select(cls).where(cls.user_id == user_id)
53
- transactions = await db.scalars(query)
54
- return transactions
55
-
56
- @classmethod
57
- async def get_by_user_between_dates(
58
- cls: "type[Transaction]", db: AsyncSession, user_id: int, start_date: datetime, end_date: datetime
59
- ) -> "List[Transaction]":
60
- query = sql.select(cls).where(cls.user_id == user_id).where(cls.transaction_date.between(start_date, end_date))
61
- transactions = await db.scalars(query)
62
- return transactions.all()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/model/user.py DELETED
@@ -1,110 +0,0 @@
1
- import logging
2
- from sqlalchemy.orm import relationship, Mapped, mapped_column
3
- from sqlalchemy.sql import expression as sql
4
- from sqlalchemy.ext.asyncio import AsyncSession
5
-
6
- from app.model.base import BaseModel
7
- from app.engine.postgresdb import Base
8
- from app.schema.index import UserCreate, UserUpdate
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- class User(Base, BaseModel):
14
- __tablename__ = "users"
15
-
16
- name: Mapped[str]
17
- email: Mapped[str]
18
- hashed_password: Mapped[str]
19
- is_deleted: Mapped[bool] = mapped_column(default=False)
20
-
21
- transactions = relationship("Transaction", back_populates="user")
22
- income_statements = relationship("IncomeStatement", back_populates="user")
23
-
24
- @classmethod
25
- async def create(cls: "type[User]", db: AsyncSession, **kwargs: UserCreate) -> "User":
26
- """
27
- Creates a new user in the database.
28
-
29
- Args:
30
- cls (type[User]): The class object representing the User model.
31
- db (AsyncSession): The asynchronous session object for interacting with the database.
32
- **kwargs (UserCreate): The keyword arguments representing the user's attributes.
33
-
34
- Returns:
35
- User
36
- """
37
-
38
- print(f"Creating user: {kwargs}")
39
- # query = sql.insert(cls).values(**kwargs)
40
- user = cls(name=kwargs["name"], email=kwargs["email"], hashed_password=kwargs["hashed_password"])
41
- db.add(user)
42
- await db.commit()
43
- await db.refresh(user)
44
- return user
45
-
46
- @classmethod
47
- async def update(cls: "type[User]", db: AsyncSession, id: int, **kwargs: UserUpdate) -> "User":
48
- """
49
- Updates a user in the database with the given ID and keyword arguments.
50
-
51
- Args:
52
- cls (type[User]): The class object representing the User model.
53
- db (AsyncSession): The asynchronous session object for interacting with the database.
54
- id (int): The ID of the user to update.
55
- **kwargs (UserUpdate): The keyword arguments representing the user's attributes to update.
56
-
57
- Returns:
58
- User
59
- """
60
-
61
- query = sql.update(cls).where(cls.id == id).values(**kwargs).execution_options(synchronize_session="fetch")
62
- results = await db.execute(query)
63
- user = results.fetchone()
64
- await db.commit()
65
- return user
66
-
67
- @classmethod
68
- async def get(cls: "type[User]", db: AsyncSession, email: str) -> "User":
69
- """
70
- Retrieves a user from the database based on their email.
71
-
72
- Args:
73
- cls (type[User]): The class object representing the User model.
74
- db (AsyncSession): The asynchronous session object for interacting with the database.
75
- email (str): The email of the user to retrieve.
76
-
77
- Returns:
78
- User
79
- """
80
-
81
- logging.info(f"Getting user: {email}")
82
- query = sql.select(cls).where(cls.email == email)
83
- logging.info(f"Query: {query}")
84
- users = await db.scalars(query)
85
- logging.info(f"Users: {users}")
86
- return users.first()
87
-
88
- @classmethod
89
- async def delete(cls: "type[User]", db: AsyncSession, email: str) -> "User":
90
- """
91
- Deletes a user from the database based on their email.
92
-
93
- Args:
94
- cls (type[User]): The class object representing the User model.
95
- db (AsyncSession): The asynchronous session object for interacting with the database.
96
- email (str): The email of the user to delete.
97
-
98
- Returns:
99
- User
100
- """
101
- query = (
102
- sql.update(cls)
103
- .where(cls.email == email)
104
- .values(is_deleted=True)
105
- .execution_options(synchronize_session="fetch")
106
- )
107
- result = await db.execute(query)
108
- user = result.fetchone()
109
- await db.commit()
110
- return user
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/schema/base.py DELETED
@@ -1,14 +0,0 @@
1
- from datetime import datetime
2
- from pydantic import BaseModel, ConfigDict
3
-
4
-
5
- class PydanticBaseModel(BaseModel):
6
- pass
7
-
8
-
9
- class BaseModel(PydanticBaseModel):
10
- model_config = ConfigDict(from_attributes=True)
11
-
12
- id: int
13
- created_at: datetime
14
- updated_at: datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/schema/index.py DELETED
@@ -1,85 +0,0 @@
1
- from enum import Enum
2
- from datetime import datetime
3
- from typing import Dict, List
4
- from typing_extensions import TypedDict
5
-
6
- from app.schema.base import BaseModel, PydanticBaseModel
7
-
8
-
9
- class TransactionType(str, Enum):
10
- INCOME = "income"
11
- EXPENSE = "expense"
12
-
13
-
14
- class UserCreate(PydanticBaseModel):
15
- name: str
16
- email: str
17
- hashed_password: str
18
-
19
-
20
- class UserUpdate(PydanticBaseModel):
21
- name: str
22
- email: str
23
- hashed_password: str
24
-
25
-
26
- class UserResponse(PydanticBaseModel):
27
- id: int
28
- name: str
29
- email: str
30
- is_deleted: bool
31
-
32
-
33
- class User(BaseModel):
34
- name: str
35
- email: str
36
- hashed_password: str
37
- is_deleted: bool = False
38
- transactions: "List[Transaction]" = []
39
-
40
-
41
- class TransactionResponse(PydanticBaseModel):
42
- transaction_date: datetime
43
- category: str
44
- name_description: str
45
- amount: float
46
- type: TransactionType
47
-
48
-
49
- class TransactionCreate(TransactionResponse):
50
- user_id: int
51
-
52
-
53
- class Transaction(TransactionResponse):
54
- user: User
55
-
56
-
57
- class FileUploadCreate(PydanticBaseModel):
58
- source: str
59
- date: datetime
60
- category: str
61
- name_description: str
62
- amount: float
63
- type: str
64
-
65
-
66
- class IncomeStatementCreateRequest(PydanticBaseModel):
67
- user_id: int
68
- date_from: datetime
69
- date_to: datetime
70
-
71
-
72
- class IncomeStatementDetail(TypedDict):
73
- total: float
74
- category_totals: List[Dict[str, str | float]]
75
-
76
- class IncomeStatementLLMResponse(PydanticBaseModel):
77
- income: IncomeStatementDetail
78
- expenses: IncomeStatementDetail
79
-
80
-
81
- class IncomeStatement(IncomeStatementCreateRequest, IncomeStatementLLMResponse):
82
- pass
83
-
84
- class IncomeStatementResponse(IncomeStatement):
85
- id: int
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/service/income_statement.py DELETED
@@ -1,37 +0,0 @@
1
- import logging
2
- from app.schema.index import IncomeStatement, IncomeStatementCreateRequest
3
- from app.model.transaction import Transaction as TransactionModel
4
- from app.model.income_statement import IncomeStatement as IncomeStatementModel
5
- from sqlalchemy.ext.asyncio import AsyncSession
6
-
7
- from app.service.llm import call_llm
8
-
9
-
10
- async def call_llm_to_create_income_statement(payload: IncomeStatementCreateRequest, db: AsyncSession) -> None:
11
- transactions = await TransactionModel.get_by_user_between_dates(
12
- db, payload.user_id, payload.date_from, payload.date_to
13
- )
14
-
15
- if not transactions:
16
- print("No transactions found")
17
- return
18
-
19
- response = await call_llm(transactions)
20
-
21
- income = response.dict()['income']
22
- expenses = response.dict()['expenses']
23
-
24
- try:
25
- income_statement_create_payload = IncomeStatement(
26
- user_id=payload.user_id,
27
- date_from=payload.date_from,
28
- date_to=payload.date_to,
29
- income=income,
30
- expenses=expenses,
31
- )
32
-
33
- income_statement = await IncomeStatementModel.create(db, **income_statement_create_payload.model_dump())
34
- print(f"Income statement created: {income_statement}")
35
- except Exception as e:
36
- print(e)
37
- raise e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/service/llm.py DELETED
@@ -1,55 +0,0 @@
1
- from typing import List
2
- from langchain_core.prompts import ChatPromptTemplate
3
- from langchain_core.runnables.base import RunnableSequence
4
- from langchain_openai import OpenAI
5
- from langchain.globals import set_llm_cache
6
-
7
- from app.model.transaction import Transaction
8
- from app.schema.index import IncomeStatementLLMResponse
9
- from config.index import config as env
10
-
11
- from langchain_core.output_parsers import PydanticOutputParser
12
-
13
- set_llm_cache(None)
14
-
15
- def income_statement_prompt () -> ChatPromptTemplate:
16
- context_str = """
17
- You are an accountant skilled at organizing transactions from multiple different bank
18
- accounts and credit card statements to prepare an income statement.
19
-
20
- Input data is in the below csv format:
21
- transaction_date, category, name_description, amount, type\n
22
- {input_data_csv}
23
-
24
- Your task is to prepare an income statement. The output should be in the following format: {format_instructions}
25
-
26
- """
27
- prompt = ChatPromptTemplate.from_template(context_str)
28
- return prompt
29
-
30
- async def call_llm(inputData: List[Transaction]) -> str:
31
- input_data_csv = '\n'.join(str(x) for x in inputData)
32
-
33
- output_parser = PydanticOutputParser(pydantic_object=IncomeStatementLLMResponse)
34
-
35
- prompt = income_statement_prompt().partial(format_instructions=output_parser.get_format_instructions())
36
-
37
- llm = OpenAI(name='Income Statement Generation Bot',
38
- api_key=env.OPENAI_API_KEY,
39
- # cache=True,
40
- temperature=0.7,
41
- verbose=True)
42
-
43
- try:
44
- runnable_chain = RunnableSequence(prompt, llm, output_parser)
45
- except Exception as e:
46
- print(f"runnable_chain error: {str(e)}")
47
- raise e
48
-
49
- try:
50
- output_chunks = runnable_chain.invoke({"input_data_csv": input_data_csv})
51
- return output_chunks
52
-
53
- except Exception as e:
54
- print(f"runnable_chain.invoke error: {str(e)}")
55
- raise e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/transactions_rag/__init__.py DELETED
File without changes
app/transactions_rag/categorize_transactions.ipynb DELETED
@@ -1,571 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": 2,
6
- "metadata": {},
7
- "outputs": [
8
- {
9
- "name": "stdout",
10
- "output_type": "stream",
11
- "text": [
12
- "Defaulting to user installation because normal site-packages is not writeable\n",
13
- "Requirement already satisfied: pandas in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (2.0.3)\n",
14
- "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pandas) (2.9.0.post0)\n",
15
- "Requirement already satisfied: pytz>=2020.1 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pandas) (2024.1)\n",
16
- "Requirement already satisfied: tzdata>=2022.1 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pandas) (2024.1)\n",
17
- "Requirement already satisfied: numpy>=1.20.3 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pandas) (1.24.4)\n",
18
- "Requirement already satisfied: six>=1.5 in /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages (from python-dateutil>=2.8.2->pandas) (1.15.0)\n"
19
- ]
20
- },
21
- {
22
- "data": {
23
- "text/html": [
24
- "<div>\n",
25
- "<style scoped>\n",
26
- " .dataframe tbody tr th:only-of-type {\n",
27
- " vertical-align: middle;\n",
28
- " }\n",
29
- "\n",
30
- " .dataframe tbody tr th {\n",
31
- " vertical-align: top;\n",
32
- " }\n",
33
- "\n",
34
- " .dataframe thead th {\n",
35
- " text-align: right;\n",
36
- " }\n",
37
- "</style>\n",
38
- "<table border=\"1\" class=\"dataframe\">\n",
39
- " <thead>\n",
40
- " <tr style=\"text-align: right;\">\n",
41
- " <th></th>\n",
42
- " <th>Date</th>\n",
43
- " <th>Name / Description</th>\n",
44
- " <th>Expense/Income</th>\n",
45
- " <th>Amount</th>\n",
46
- " </tr>\n",
47
- " </thead>\n",
48
- " <tbody>\n",
49
- " <tr>\n",
50
- " <th>0</th>\n",
51
- " <td>2023-12-30</td>\n",
52
- " <td>Comcast Internet</td>\n",
53
- " <td>Expense</td>\n",
54
- " <td>9.96</td>\n",
55
- " </tr>\n",
56
- " <tr>\n",
57
- " <th>1</th>\n",
58
- " <td>2023-12-30</td>\n",
59
- " <td>Lemonade Home Insurance</td>\n",
60
- " <td>Expense</td>\n",
61
- " <td>17.53</td>\n",
62
- " </tr>\n",
63
- " <tr>\n",
64
- " <th>2</th>\n",
65
- " <td>2023-12-30</td>\n",
66
- " <td>Monthly Appartment Rent</td>\n",
67
- " <td>Expense</td>\n",
68
- " <td>2000.00</td>\n",
69
- " </tr>\n",
70
- " <tr>\n",
71
- " <th>3</th>\n",
72
- " <td>2023-12-30</td>\n",
73
- " <td>Staples Office Supplies</td>\n",
74
- " <td>Expense</td>\n",
75
- " <td>12.46</td>\n",
76
- " </tr>\n",
77
- " <tr>\n",
78
- " <th>4</th>\n",
79
- " <td>2023-12-29</td>\n",
80
- " <td>Selling Paintings</td>\n",
81
- " <td>Income</td>\n",
82
- " <td>13.63</td>\n",
83
- " </tr>\n",
84
- " </tbody>\n",
85
- "</table>\n",
86
- "</div>"
87
- ],
88
- "text/plain": [
89
- " Date Name / Description Expense/Income Amount\n",
90
- "0 2023-12-30 Comcast Internet Expense 9.96\n",
91
- "1 2023-12-30 Lemonade Home Insurance Expense 17.53\n",
92
- "2 2023-12-30 Monthly Appartment Rent Expense 2000.00\n",
93
- "3 2023-12-30 Staples Office Supplies Expense 12.46\n",
94
- "4 2023-12-29 Selling Paintings Income 13.63"
95
- ]
96
- },
97
- "execution_count": 2,
98
- "metadata": {},
99
- "output_type": "execute_result"
100
- }
101
- ],
102
- "source": [
103
- "# Read the transactions csv\n",
104
- "!pip3 install pandas\n",
105
- "\n",
106
- "import pandas as pd\n",
107
- "df = pd.read_csv(\"transactions_2024.csv\")\n",
108
- "df.head()"
109
- ]
110
- },
111
- {
112
- "cell_type": "code",
113
- "execution_count": 3,
114
- "metadata": {},
115
- "outputs": [
116
- {
117
- "data": {
118
- "text/plain": [
119
- "array(['Lemonade Home Insurance', 'Monthly Appartment Rent',\n",
120
- " 'Staples Office Supplies', 'Selling Paintings', 'Spotify',\n",
121
- " 'Target', 'IT Consulting', 'Phone', 'ML Consulting'], dtype=object)"
122
- ]
123
- },
124
- "execution_count": 3,
125
- "metadata": {},
126
- "output_type": "execute_result"
127
- }
128
- ],
129
- "source": [
130
- "# Get Unique transactions in the Name/Description column\n",
131
- "unique_transactions = df[\"Name / Description\"].unique()\n",
132
- "unique_transactions[1:10]"
133
- ]
134
- },
135
- {
136
- "cell_type": "code",
137
- "execution_count": 15,
138
- "metadata": {},
139
- "outputs": [],
140
- "source": [
141
- "!python3 -m venv venv"
142
- ]
143
- },
144
- {
145
- "cell_type": "code",
146
- "execution_count": 16,
147
- "metadata": {},
148
- "outputs": [],
149
- "source": [
150
- "!source venv/bin/activate"
151
- ]
152
- },
153
- {
154
- "cell_type": "code",
155
- "execution_count": 23,
156
- "metadata": {},
157
- "outputs": [
158
- {
159
- "name": "stdout",
160
- "output_type": "stream",
161
- "text": [
162
- "Defaulting to user installation because normal site-packages is not writeable\n",
163
- "Requirement already satisfied: setuptools in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (70.0.0)\n"
164
- ]
165
- }
166
- ],
167
- "source": [
168
- "!pip3 install --upgrade setuptools"
169
- ]
170
- },
171
- {
172
- "cell_type": "code",
173
- "execution_count": 2,
174
- "metadata": {},
175
- "outputs": [
176
- {
177
- "data": {
178
- "text/plain": [
179
- "['Defaulting to user installation because normal site-packages is not writeable',\n",
180
- " 'Requirement already satisfied: langchain in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 1)) (0.2.1)',\n",
181
- " 'Collecting langchain_openai (from -r requirements.txt (line 2))',\n",
182
- " ' Downloading langchain_openai-0.1.7-py3-none-any.whl.metadata (2.5 kB)',\n",
183
- " 'Collecting python-dotenv (from -r requirements.txt (line 3))',\n",
184
- " ' Using cached python_dotenv-1.0.1-py3-none-any.whl.metadata (23 kB)',\n",
185
- " 'Collecting openai (from -r requirements.txt (line 4))',\n",
186
- " ' Using cached openai-1.30.4-py3-none-any.whl.metadata (21 kB)',\n",
187
- " 'Requirement already satisfied: tenacity in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 5)) (8.3.0)',\n",
188
- " 'Requirement already satisfied: rapidfuzz in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 6)) (3.9.1)',\n",
189
- " 'Requirement already satisfied: pydantic in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 7)) (2.7.1)',\n",
190
- " 'Requirement already satisfied: dateparser in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 8)) (1.2.0)',\n",
191
- " 'Requirement already satisfied: pandas in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 9)) (2.0.3)',\n",
192
- " 'Collecting path (from -r requirements.txt (line 10))',\n",
193
- " ' Using cached path-16.14.0-py3-none-any.whl.metadata (6.3 kB)',\n",
194
- " 'Collecting aiostream==0.5.2 (from -r requirements.txt (line 12))',\n",
195
- " ' Using cached aiostream-0.5.2-py3-none-any.whl.metadata (9.9 kB)',\n",
196
- " 'Collecting cachetools==5.3.3 (from -r requirements.txt (line 13))',\n",
197
- " ' Using cached cachetools-5.3.3-py3-none-any.whl.metadata (5.3 kB)',\n",
198
- " 'Collecting docx2txt==0.8 (from -r requirements.txt (line 14))',\n",
199
- " ' Using cached docx2txt-0.8.tar.gz (2.8 kB)',\n",
200
- " ' Preparing metadata (setup.py): started',\n",
201
- " \" Preparing metadata (setup.py): finished with status 'done'\",\n",
202
- " 'Collecting fastapi==0.109.1 (from -r requirements.txt (line 15))',\n",
203
- " ' Using cached fastapi-0.109.1-py3-none-any.whl.metadata (25 kB)',\n",
204
- " 'Collecting llama-index-agent-openai==0.2.2 (from -r requirements.txt (line 16))',\n",
205
- " ' Using cached llama_index_agent_openai-0.2.2-py3-none-any.whl.metadata (677 bytes)',\n",
206
- " 'Collecting llama-index-core==0.10.28 (from -r requirements.txt (line 17))',\n",
207
- " ' Using cached llama_index_core-0.10.28-py3-none-any.whl.metadata (3.6 kB)',\n",
208
- " 'Collecting llama-index-vector-stores-pinecone==0.1.3 (from -r requirements.txt (line 18))',\n",
209
- " ' Using cached llama_index_vector_stores_pinecone-0.1.3-py3-none-any.whl.metadata (725 bytes)',\n",
210
- " 'Collecting llama-index==0.10.28 (from -r requirements.txt (line 19))',\n",
211
- " ' Using cached llama_index-0.10.28-py3-none-any.whl.metadata (11 kB)',\n",
212
- " 'Collecting python-dotenv (from -r requirements.txt (line 3))',\n",
213
- " ' Using cached python_dotenv-1.0.0-py3-none-any.whl.metadata (21 kB)',\n",
214
- " '\\x1b[31mERROR: Ignored the following versions that require a different python version: 0.0.10 Requires-Python >=3.9,<4.0; 0.0.11 Requires-Python >=3.9,<4.0; 0.0.12 Requires-Python >=3.9,<4.0; 0.0.13 Requires-Python >=3.9,<4.0; 0.0.14 Requires-Python >=3.9,<4.0; 0.0.15 Requires-Python >=3.9,<4.0; 0.0.16 Requires-Python >=3.9,<4.0; 0.0.17 Requires-Python >=3.9,<4.0; 0.0.18 Requires-Python >=3.9,<4.0; 0.0.19 Requires-Python >=3.9,<4.0; 0.0.20 Requires-Python >=3.9,<4.0; 0.0.21 Requires-Python >=3.9,<4.0; 0.0.22 Requires-Python >=3.9,<4.0; 0.0.23 Requires-Python >=3.9,<4.0; 0.0.24 Requires-Python >=3.9,<4.0; 0.0.25 Requires-Python >=3.9,<4.0; 0.14.3 Requires-Python >=3.9,<4; 0.14.4 Requires-Python <4,>=3.9; 0.14.5 Requires-Python <4,>=3.9; 0.15.0 Requires-Python <4,>=3.9; 0.15.1 Requires-Python <4,>=3.9; 0.15.10 Requires-Python <4,>=3.9; 0.15.11 Requires-Python <4,>=3.9; 0.15.12 Requires-Python <4,>=3.9; 0.15.13 Requires-Python <4,>=3.9; 0.15.2 Requires-Python <4,>=3.9; 0.15.3 Requires-Python <4,>=3.9; 0.15.4 Requires-Python <4,>=3.9; 0.15.5 Requires-Python <4,>=3.9; 0.15.6 Requires-Python <4,>=3.9; 0.15.7 Requires-Python <4,>=3.9; 0.15.8 Requires-Python <4,>=3.9; 0.15.9 Requires-Python <4,>=3.9; 0.16.0 Requires-Python <4,>=3.9; 0.16.1 Requires-Python <4,>=3.9; 0.16.2 Requires-Python <4,>=3.9; 0.16.3 Requires-Python <4,>=3.9; 0.16.4 Requires-Python <4,>=3.9; 0.16.5 Requires-Python <4,>=3.9; 0.16.6 Requires-Python <4,>=3.9; 0.16.7 Requires-Python <4,>=3.9; 0.16.8 Requires-Python <4,>=3.9; 0.16.9 Requires-Python <4,>=3.9; 0.17.0 Requires-Python <4,>=3.9; 0.17.1 Requires-Python <4,>=3.9; 0.17.2 Requires-Python <4,>=3.9; 0.17.3 Requires-Python <4,>=3.9; 0.17.4 Requires-Python <4,>=3.9; 0.17.5 Requires-Python <4,>=3.9; 0.17.6 Requires-Python <4,>=3.9; 0.17.7 Requires-Python <4,>=3.9; 0.18.0 Requires-Python <4,>=3.9; 0.18.1 Requires-Python <4,>=3.9; 0.18.2 Requires-Python <4,>=3.9; 0.19.0 Requires-Python <4,>=3.9; 0.20.0 Requires-Python <4,>=3.9; 0.21.0 Requires-Python <4,>=3.9\\x1b[0m\\x1b[31m',\n",
215
- " '\\x1b[0m\\x1b[31mERROR: Could not find a version that satisfies the requirement traceloop-sdk==0.15.11 (from versions: 0.0.26, 0.0.27b0, 0.0.27, 0.0.28, 0.0.29, 0.0.31, 0.0.32, 0.0.33, 0.0.34, 0.0.35, 0.0.36, 0.0.37, 0.0.38, 0.0.39, 0.0.40, 0.0.41, 0.0.42, 0.0.43b0, 0.0.43b2, 0.0.43b3, 0.0.43b4, 0.0.43, 0.0.44b0, 0.0.44, 0.0.46, 0.0.47, 0.0.48, 0.0.49, 0.0.50, 0.0.51, 0.0.52, 0.0.53, 0.0.54, 0.0.55, 0.0.56a0, 0.0.56a1, 0.0.56a2, 0.0.56a3, 0.0.56a4, 0.0.56a5, 0.0.56a6, 0.0.56, 0.0.57, 0.0.58, 0.0.59, 0.0.60a0, 0.0.60a1, 0.0.60, 0.0.61a0, 0.0.61, 0.0.62, 0.0.63, 0.0.64a0, 0.0.64, 0.0.65, 0.0.66, 0.0.67, 0.0.68, 0.0.69, 0.0.70, 0.1.3, 0.1.5, 0.1.6, 0.1.7, 0.1.8, 0.1.9, 0.1.10, 0.1.11, 0.1.12, 0.2.0, 0.2.1, 0.3.0, 0.3.1, 0.3.2, 0.3.3, 0.3.4, 0.3.5, 0.3.6, 0.4.0, 0.4.1, 0.4.2, 0.5.0, 0.5.1, 0.5.2, 0.5.3, 0.6.0, 0.7.0, 0.8.0, 0.8.2, 0.9.0, 0.9.1, 0.9.2, 0.9.3, 0.9.4, 0.10.0, 0.10.1, 0.10.2, 0.10.3, 0.10.4, 0.10.5, 0.11.0, 0.11.1, 0.11.2, 0.11.3, 0.12.0, 0.12.1, 0.12.2, 0.12.3, 0.12.4, 0.12.5, 0.13.0, 0.13.1, 0.13.2, 0.13.3, 0.14.0, 0.14.1, 0.14.2)\\x1b[0m\\x1b[31m',\n",
216
- " '\\x1b[0m\\x1b[31mERROR: No matching distribution found for traceloop-sdk==0.15.11\\x1b[0m\\x1b[31m',\n",
217
- " '\\x1b[0m']"
218
- ]
219
- },
220
- "execution_count": 2,
221
- "metadata": {},
222
- "output_type": "execute_result"
223
- }
224
- ],
225
- "source": [
226
- "!!pip3 install -r requirements.txt"
227
- ]
228
- },
229
- {
230
- "cell_type": "code",
231
- "execution_count": 28,
232
- "metadata": {},
233
- "outputs": [
234
- {
235
- "name": "stdout",
236
- "output_type": "stream",
237
- "text": [
238
- "Defaulting to user installation because normal site-packages is not writeable\n",
239
- "Collecting dateparser\n",
240
- " Using cached dateparser-1.2.0-py2.py3-none-any.whl.metadata (28 kB)\n",
241
- "Requirement already satisfied: python-dateutil in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from dateparser) (2.9.0.post0)\n",
242
- "Requirement already satisfied: pytz in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from dateparser) (2024.1)\n",
243
- "Collecting regex!=2019.02.19,!=2021.8.27 (from dateparser)\n",
244
- " Downloading regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl.metadata (40 kB)\n",
245
- "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m40.9/40.9 kB\u001b[0m \u001b[31m95.7 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n",
246
- "\u001b[?25hCollecting tzlocal (from dateparser)\n",
247
- " Downloading tzlocal-5.2-py3-none-any.whl.metadata (7.8 kB)\n",
248
- "Requirement already satisfied: six>=1.5 in /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages (from python-dateutil->dateparser) (1.15.0)\n",
249
- "Collecting backports.zoneinfo (from tzlocal->dateparser)\n",
250
- " Downloading backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl.metadata (4.7 kB)\n",
251
- "Downloading dateparser-1.2.0-py2.py3-none-any.whl (294 kB)\n",
252
- "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m295.0/295.0 kB\u001b[0m \u001b[31m510.0 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n",
253
- "\u001b[?25hDownloading regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl (281 kB)\n",
254
- "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m281.8/281.8 kB\u001b[0m \u001b[31m2.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n",
255
- "\u001b[?25hDownloading tzlocal-5.2-py3-none-any.whl (17 kB)\n",
256
- "Downloading backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl (35 kB)\n",
257
- "Installing collected packages: regex, backports.zoneinfo, tzlocal, dateparser\n",
258
- "\u001b[33m WARNING: The script dateparser-download is installed in '/Users/patrickalexis/Library/Python/3.8/bin' which is not on PATH.\n",
259
- " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n",
260
- "\u001b[0mSuccessfully installed backports.zoneinfo-0.2.1 dateparser-1.2.0 regex-2024.5.15 tzlocal-5.2\n"
261
- ]
262
- }
263
- ],
264
- "source": [
265
- "!pip3 install dateparser"
266
- ]
267
- },
268
- {
269
- "cell_type": "code",
270
- "execution_count": 2,
271
- "metadata": {},
272
- "outputs": [
273
- {
274
- "name": "stdout",
275
- "output_type": "stream",
276
- "text": [
277
- "Defaulting to user installation because normal site-packages is not writeable\n",
278
- "Collecting rapidfuzz\n",
279
- " Using cached rapidfuzz-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl.metadata (11 kB)\n",
280
- "Using cached rapidfuzz-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl (2.1 MB)\n",
281
- "Installing collected packages: rapidfuzz\n",
282
- "Successfully installed rapidfuzz-3.9.1\n"
283
- ]
284
- }
285
- ],
286
- "source": [
287
- "!pip3 install rapidfuzz"
288
- ]
289
- },
290
- {
291
- "cell_type": "code",
292
- "execution_count": 2,
293
- "metadata": {},
294
- "outputs": [
295
- {
296
- "name": "stdout",
297
- "output_type": "stream",
298
- "text": [
299
- "Defaulting to user installation because normal site-packages is not writeable\n",
300
- "Collecting dotenv\n",
301
- " Downloading dotenv-0.0.5.tar.gz (2.4 kB)\n",
302
- " Preparing metadata (setup.py) ... \u001b[?25lerror\n",
303
- " \u001b[1;31merror\u001b[0m: \u001b[1msubprocess-exited-with-error\u001b[0m\n",
304
- " \n",
305
- " \u001b[31m×\u001b[0m \u001b[32mpython setup.py egg_info\u001b[0m did not run successfully.\n",
306
- " \u001b[31m│\u001b[0m exit code: \u001b[1;36m1\u001b[0m\n",
307
- " \u001b[31m╰─>\u001b[0m \u001b[31m[76 lines of output]\u001b[0m\n",
308
- " \u001b[31m \u001b[0m /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/__init__.py:80: _DeprecatedInstaller: setuptools.installer and fetch_build_eggs are deprecated.\n",
309
- " \u001b[31m \u001b[0m !!\n",
310
- " \u001b[31m \u001b[0m \n",
311
- " \u001b[31m \u001b[0m ********************************************************************************\n",
312
- " \u001b[31m \u001b[0m Requirements should be satisfied by a PEP 517 installer.\n",
313
- " \u001b[31m \u001b[0m If you are using pip, you can try `pip install --use-pep517`.\n",
314
- " \u001b[31m \u001b[0m ********************************************************************************\n",
315
- " \u001b[31m \u001b[0m \n",
316
- " \u001b[31m \u001b[0m !!\n",
317
- " \u001b[31m \u001b[0m dist.fetch_build_eggs(dist.setup_requires)\n",
318
- " \u001b[31m \u001b[0m \u001b[1;31merror\u001b[0m: \u001b[1msubprocess-exited-with-error\u001b[0m\n",
319
- " \u001b[31m \u001b[0m \n",
320
- " \u001b[31m \u001b[0m \u001b[31m×\u001b[0m \u001b[32mpython setup.py egg_info\u001b[0m did not run successfully.\n",
321
- " \u001b[31m \u001b[0m \u001b[31m│\u001b[0m exit code: \u001b[1;36m1\u001b[0m\n",
322
- " \u001b[31m \u001b[0m \u001b[31m╰─>\u001b[0m \u001b[31m[16 lines of output]\u001b[0m\n",
323
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m Traceback (most recent call last):\n",
324
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"<string>\", line 2, in <module>\n",
325
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"<pip-setuptools-caller>\", line 14, in <module>\n",
326
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/__init__.py\", line 2, in <module>\n",
327
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from setuptools.extension import Extension, Library\n",
328
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/extension.py\", line 5, in <module>\n",
329
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from setuptools.dist import _get_unpatched\n",
330
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/dist.py\", line 7, in <module>\n",
331
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from setuptools.command.install import install\n",
332
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/command/__init__.py\", line 8, in <module>\n",
333
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from setuptools.command import install_scripts\n",
334
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/command/install_scripts.py\", line 3, in <module>\n",
335
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from pkg_resources import Distribution, PathMetadata, ensure_directory\n",
336
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/pkg_resources.py\", line 1518, in <module>\n",
337
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m register_loader_type(importlib_bootstrap.SourceFileLoader, DefaultProvider)\n",
338
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m AttributeError: module 'importlib._bootstrap' has no attribute 'SourceFileLoader'\n",
339
- " \u001b[31m \u001b[0m \u001b[31m \u001b[0m \u001b[31m[end of output]\u001b[0m\n",
340
- " \u001b[31m \u001b[0m \n",
341
- " \u001b[31m \u001b[0m \u001b[1;35mnote\u001b[0m: This error originates from a subprocess, and is likely not a problem with pip.\n",
342
- " \u001b[31m \u001b[0m \u001b[1;31merror\u001b[0m: \u001b[1mmetadata-generation-failed\u001b[0m\n",
343
- " \u001b[31m \u001b[0m \n",
344
- " \u001b[31m \u001b[0m \u001b[31m×\u001b[0m Encountered error while generating package metadata.\n",
345
- " \u001b[31m \u001b[0m \u001b[31m╰─>\u001b[0m See above for output.\n",
346
- " \u001b[31m \u001b[0m \n",
347
- " \u001b[31m \u001b[0m \u001b[1;35mnote\u001b[0m: This is an issue with the package mentioned above, not pip.\n",
348
- " \u001b[31m \u001b[0m \u001b[1;36mhint\u001b[0m: See above for details.\n",
349
- " \u001b[31m \u001b[0m Traceback (most recent call last):\n",
350
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/installer.py\", line 101, in _fetch_build_egg_no_warn\n",
351
- " \u001b[31m \u001b[0m subprocess.check_call(cmd)\n",
352
- " \u001b[31m \u001b[0m File \"/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/subprocess.py\", line 364, in check_call\n",
353
- " \u001b[31m \u001b[0m raise CalledProcessError(retcode, cmd)\n",
354
- " \u001b[31m \u001b[0m subprocess.CalledProcessError: Command '['/Applications/Xcode.app/Contents/Developer/usr/bin/python3', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/tmpnw2yadh3', '--quiet', 'distribute']' returned non-zero exit status 1.\n",
355
- " \u001b[31m \u001b[0m \n",
356
- " \u001b[31m \u001b[0m The above exception was the direct cause of the following exception:\n",
357
- " \u001b[31m \u001b[0m \n",
358
- " \u001b[31m \u001b[0m Traceback (most recent call last):\n",
359
- " \u001b[31m \u001b[0m File \"<string>\", line 2, in <module>\n",
360
- " \u001b[31m \u001b[0m File \"<pip-setuptools-caller>\", line 34, in <module>\n",
361
- " \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-install-ac3_ml88/dotenv_7766945cfd7d4e62965375b30e9a0c21/setup.py\", line 13, in <module>\n",
362
- " \u001b[31m \u001b[0m setup(name='dotenv',\n",
363
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/__init__.py\", line 102, in setup\n",
364
- " \u001b[31m \u001b[0m _install_setup_requires(attrs)\n",
365
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/__init__.py\", line 75, in _install_setup_requires\n",
366
- " \u001b[31m \u001b[0m _fetch_build_eggs(dist)\n",
367
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/__init__.py\", line 80, in _fetch_build_eggs\n",
368
- " \u001b[31m \u001b[0m dist.fetch_build_eggs(dist.setup_requires)\n",
369
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/dist.py\", line 641, in fetch_build_eggs\n",
370
- " \u001b[31m \u001b[0m return _fetch_build_eggs(self, requires)\n",
371
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/installer.py\", line 38, in _fetch_build_eggs\n",
372
- " \u001b[31m \u001b[0m resolved_dists = pkg_resources.working_set.resolve(\n",
373
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/pkg_resources/__init__.py\", line 787, in resolve\n",
374
- " \u001b[31m \u001b[0m dist = self._resolve_dist(\n",
375
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/pkg_resources/__init__.py\", line 823, in _resolve_dist\n",
376
- " \u001b[31m \u001b[0m dist = best[req.key] = env.best_match(\n",
377
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/pkg_resources/__init__.py\", line 1093, in best_match\n",
378
- " \u001b[31m \u001b[0m return self.obtain(req, installer)\n",
379
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/pkg_resources/__init__.py\", line 1104, in obtain\n",
380
- " \u001b[31m \u001b[0m return installer(requirement) if installer else None\n",
381
- " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/installer.py\", line 103, in _fetch_build_egg_no_warn\n",
382
- " \u001b[31m \u001b[0m raise DistutilsError(str(e)) from e\n",
383
- " \u001b[31m \u001b[0m distutils.errors.DistutilsError: Command '['/Applications/Xcode.app/Contents/Developer/usr/bin/python3', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/tmpnw2yadh3', '--quiet', 'distribute']' returned non-zero exit status 1.\n",
384
- " \u001b[31m \u001b[0m \u001b[31m[end of output]\u001b[0m\n",
385
- " \n",
386
- " \u001b[1;35mnote\u001b[0m: This error originates from a subprocess, and is likely not a problem with pip.\n",
387
- "\u001b[?25h\u001b[1;31merror\u001b[0m: \u001b[1mmetadata-generation-failed\u001b[0m\n",
388
- "\n",
389
- "\u001b[31m×\u001b[0m Encountered error while generating package metadata.\n",
390
- "\u001b[31m╰─>\u001b[0m See above for output.\n",
391
- "\n",
392
- "\u001b[1;35mnote\u001b[0m: This is an issue with the package mentioned above, not pip.\n",
393
- "\u001b[1;36mhint\u001b[0m: See above for details.\n"
394
- ]
395
- }
396
- ],
397
- "source": [
398
- "!pip3 install dotenv"
399
- ]
400
- },
401
- {
402
- "cell_type": "code",
403
- "execution_count": 10,
404
- "metadata": {},
405
- "outputs": [],
406
- "source": [
407
- "# Create data folder\n",
408
- "!mkdir -p data/tx_data/output\n",
409
- "!mkdir -p data/ref_data"
410
- ]
411
- },
412
- {
413
- "cell_type": "code",
414
- "execution_count": 4,
415
- "metadata": {},
416
- "outputs": [
417
- {
418
- "name": "stdout",
419
- "output_type": "stream",
420
- "text": [
421
- "Defaulting to user installation because normal site-packages is not writeable\n",
422
- "Collecting openai\n",
423
- " Using cached openai-1.30.4-py3-none-any.whl.metadata (21 kB)\n",
424
- "Collecting anyio<5,>=3.5.0 (from openai)\n",
425
- " Downloading anyio-4.4.0-py3-none-any.whl.metadata (4.6 kB)\n",
426
- "Collecting distro<2,>=1.7.0 (from openai)\n",
427
- " Downloading distro-1.9.0-py3-none-any.whl.metadata (6.8 kB)\n",
428
- "Collecting httpx<1,>=0.23.0 (from openai)\n",
429
- " Downloading httpx-0.27.0-py3-none-any.whl.metadata (7.2 kB)\n",
430
- "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from openai) (2.7.1)\n",
431
- "Collecting sniffio (from openai)\n",
432
- " Downloading sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)\n",
433
- "Requirement already satisfied: tqdm>4 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from openai) (4.66.4)\n",
434
- "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from openai) (4.12.0)\n",
435
- "Requirement already satisfied: idna>=2.8 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from anyio<5,>=3.5.0->openai) (3.7)\n",
436
- "Collecting exceptiongroup>=1.0.2 (from anyio<5,>=3.5.0->openai)\n",
437
- " Downloading exceptiongroup-1.2.1-py3-none-any.whl.metadata (6.6 kB)\n",
438
- "Requirement already satisfied: certifi in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from httpx<1,>=0.23.0->openai) (2024.2.2)\n",
439
- "Collecting httpcore==1.* (from httpx<1,>=0.23.0->openai)\n",
440
- " Downloading httpcore-1.0.5-py3-none-any.whl.metadata (20 kB)\n",
441
- "Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx<1,>=0.23.0->openai)\n",
442
- " Downloading h11-0.14.0-py3-none-any.whl.metadata (8.2 kB)\n",
443
- "Requirement already satisfied: annotated-types>=0.4.0 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pydantic<3,>=1.9.0->openai) (0.7.0)\n",
444
- "Requirement already satisfied: pydantic-core==2.18.2 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pydantic<3,>=1.9.0->openai) (2.18.2)\n",
445
- "Downloading openai-1.30.4-py3-none-any.whl (320 kB)\n",
446
- "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m320.6/320.6 kB\u001b[0m \u001b[31m5.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m\n",
447
- "\u001b[?25hDownloading anyio-4.4.0-py3-none-any.whl (86 kB)\n",
448
- "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
449
- "\u001b[?25hDownloading distro-1.9.0-py3-none-any.whl (20 kB)\n",
450
- "Downloading httpx-0.27.0-py3-none-any.whl (75 kB)\n",
451
- "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m75.6/75.6 kB\u001b[0m \u001b[31m2.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
452
- "\u001b[?25hDownloading httpcore-1.0.5-py3-none-any.whl (77 kB)\n",
453
- "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.9/77.9 kB\u001b[0m \u001b[31m4.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
454
- "\u001b[?25hDownloading sniffio-1.3.1-py3-none-any.whl (10 kB)\n",
455
- "Downloading exceptiongroup-1.2.1-py3-none-any.whl (16 kB)\n",
456
- "Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n",
457
- "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m2.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
458
- "\u001b[?25hInstalling collected packages: sniffio, h11, exceptiongroup, distro, httpcore, anyio, httpx, openai\n",
459
- "\u001b[33m WARNING: The script distro is installed in '/Users/patrickalexis/Library/Python/3.8/bin' which is not on PATH.\n",
460
- " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n",
461
- "\u001b[0m\u001b[33m WARNING: The script httpx is installed in '/Users/patrickalexis/Library/Python/3.8/bin' which is not on PATH.\n",
462
- " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n",
463
- "\u001b[0m\u001b[33m WARNING: The script openai is installed in '/Users/patrickalexis/Library/Python/3.8/bin' which is not on PATH.\n",
464
- " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n",
465
- "\u001b[0mSuccessfully installed anyio-4.4.0 distro-1.9.0 exceptiongroup-1.2.1 h11-0.14.0 httpcore-1.0.5 httpx-0.27.0 openai-1.30.4 sniffio-1.3.1\n"
466
- ]
467
- }
468
- ],
469
- "source": [
470
- "!pip3 install openai"
471
- ]
472
- },
473
- {
474
- "cell_type": "code",
475
- "execution_count": 5,
476
- "metadata": {},
477
- "outputs": [
478
- {
479
- "name": "stdout",
480
- "output_type": "stream",
481
- "text": [
482
- "\n",
483
- "Processing file\n"
484
- ]
485
- },
486
- {
487
- "name": "stderr",
488
- "output_type": "stream",
489
- "text": [
490
- "ERROR:root:| File: transactions_2024.csv | Unexpected Error: expected str, got module\n"
491
- ]
492
- },
493
- {
494
- "name": "stdout",
495
- "output_type": "stream",
496
- "text": [
497
- "ERROR processing file transactions_2024.csv: expected str, got module\n",
498
- "\n",
499
- "Processed 1 files: 0 successful, 1 with errors\n",
500
- "\n",
501
- "Errors in the following files:\n",
502
- " transactions_2024.csv: expected str, got module\n",
503
- "\n",
504
- "\n",
505
- "[{'file_name': 'transactions_2024.csv', 'output': Empty DataFrame\n",
506
- "Columns: []\n",
507
- "Index: [], 'error': 'expected str, got module'}]\n",
508
- "['name/description,category\\n']\n",
509
- "None\n"
510
- ]
511
- }
512
- ],
513
- "source": [
514
- "# Process the transactions csv and get the results from the categorization llm utility\n",
515
- "from app.categorization.file_processing import process_file, save_results\n",
516
- "from app.categorization.config import CATEGORY_REFERENCE_OUTPUT_FILE\n",
517
- "# from dotenv import load_dotenv\n",
518
- "import asyncio\n",
519
- "import os.path\n",
520
- "\n",
521
- "# load_dotenv()\n",
522
- "\n",
523
- "# relative_dir = os.path.dirname(__file__)\n",
524
- "# abs_file_path = os.path.join(relative_dir, CATEGORY_REFERENCE_OUTPUT_FILE)\n",
525
- "\n",
526
- "async def apply_categorization():\n",
527
- " processed_file = process_file(\"app/transactions_rag/transactions_2024.csv\")\n",
528
- "\n",
529
- " print(\"\\nProcessing file\")\n",
530
- " result = await asyncio.gather(processed_file)\n",
531
- "\n",
532
- " await save_results(result)\n",
533
- " print(result)\n",
534
- "\n",
535
- " output_file = open(CATEGORY_REFERENCE_OUTPUT_FILE, \"r+\")\n",
536
- " print(output_file.readlines())\n",
537
- "\n",
538
- "\n",
539
- "result = await apply_categorization()\n",
540
- "print(result)\n"
541
- ]
542
- }
543
- ],
544
- "metadata": {
545
- "kernelspec": {
546
- "display_name": "Python 3.8.9 64-bit",
547
- "language": "python",
548
- "name": "python3"
549
- },
550
- "language_info": {
551
- "codemirror_mode": {
552
- "name": "ipython",
553
- "version": 3
554
- },
555
- "file_extension": ".py",
556
- "mimetype": "text/x-python",
557
- "name": "python",
558
- "nbconvert_exporter": "python",
559
- "pygments_lexer": "ipython3",
560
- "version": "3.8.9"
561
- },
562
- "orig_nbformat": 4,
563
- "vscode": {
564
- "interpreter": {
565
- "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
566
- }
567
- }
568
- },
569
- "nbformat": 4,
570
- "nbformat_minor": 2
571
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/transactions_rag/transactions_2024.csv DELETED
@@ -1,29 +0,0 @@
1
- Date,Name / Description,Expense/Income,Amount
2
- 2023-12-30,Comcast Internet,Expense,9.96
3
- 2023-12-30,Lemonade Home Insurance,Expense,17.53
4
- 2023-12-30,Monthly Appartment Rent,Expense,2000.0
5
- 2023-12-30,Staples Office Supplies,Expense,12.46
6
- 2023-12-29,Selling Paintings,Income,13.63
7
- 2023-12-29,Spotify,Expense,12.19
8
- 2023-12-23,Target,Expense,27.08
9
- 2023-12-22,IT Consulting,Income,541.57
10
- 2023-12-22,Phone,Expense,10.7
11
- 2023-12-20,ML Consulting,Income,2641.93
12
- 2023-12-19,Chipotle,Expense,18.9
13
- 2023-12-18,Cold Brew Coffee,Expense,17.67
14
- 2023-12-18,Gas,Expense,8.80
15
- 2023-12-18,CA Property Tax,Expense,1670.34
16
- 2022-11-26,Salary,Income,4000.36
17
- 2022-11-26,Cellphone,Expense,19.27
18
- 2022-11-26,Gym Membership,Expense,24.71
19
- 2022-11-25,Wholefoods,Expense,17.35
20
- 2022-11-24,Freelancing,Income,2409.55
21
- 2022-11-19,Spotify,Expense,20.76
22
- 2022-10-25,Blogging,Income,4044.27
23
- 2022-10-24,Uber Taxi,Expense,18.9
24
- 2022-10-23,Uber Taxi,Expense,27.54
25
- 2022-10-22,Apple Services,Expense,41.25
26
- 2022-10-21,Netflix,Expense,22.8
27
- 2022-01-16,Amazon Lux,Expense,24.11
28
- 2022-01-15,Burger King,Expense,30.08
29
- 2022-01-14,Amazon,Expense,11.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
config/index.py DELETED
@@ -1,34 +0,0 @@
1
- import os
2
- from dotenv import load_dotenv
3
-
4
- load_dotenv()
5
-
6
-
7
- class Config:
8
- DEBUG = os.getenv("DEBUG", False)
9
- SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI")
10
- SQLALCHEMY_TRACK_MODIFICATIONS = os.getenv("SQLALCHEMY_TRACK_MODIFICATIONS", False)
11
- ENVIRONMENT = os.getenv("ENVIRONMENT", "dev")
12
- APP_HOST = os.getenv("APP_HOST", "0.0.0.0")
13
- APP_PORT = int(os.getenv("APP_PORT", "8000"))
14
- PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
15
- PINECONE_INDEX_NAME = os.getenv("PINECONE_INDEX_NAME")
16
- PINECONE_ENVIRONMENT = os.getenv("PINECONE_ENVIRONMENT")
17
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
18
- OPENAI_ORGANIZATION = os.getenv("OPENAI_ORGANIZATION")
19
- MODEL = os.getenv("MODEL", "gpt-3.5-turbo")
20
- EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-ada-002")
21
- EMBEDDING_DIM = os.getenv("EMBEDDING_DIM", "1536")
22
- CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "1024"))
23
- CHUNK_OVERLAP = int(os.getenv("CHUNK_OVERLAP", "20"))
24
- MAX_TOKENS = int(os.getenv("MAX_TOKENS", "4000"))
25
-
26
- POSTGRES_USER = os.getenv("POSTGRES_USER")
27
- POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD")
28
- POSTGRES_DB_NAME = os.getenv("POSTGRES_DB_NAME")
29
- POSTGRES_DB_HOST = os.getenv("POSTGRES_DB_HOST")
30
- POSTGRES_DB_PORT = int(os.getenv("POSTGRES_DB_PORT", 5432))
31
- SQLALCHEMY_DATABASE_URL = f"postgresql+asyncpg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_DB_HOST}:{POSTGRES_DB_PORT}/{POSTGRES_DB_NAME}"
32
-
33
-
34
- config = Config
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/.gitkeep DELETED
File without changes
data/tx_data/input/transactions_2024.csv DELETED
@@ -1,28 +0,0 @@
1
- 2023-12-30,Comcast Internet,Expense,9.96
2
- 2023-12-30,Lemonade Home Insurance,Expense,17.53
3
- 2023-12-30,Monthly Appartment Rent,Expense,2000.0
4
- 2023-12-30,Staples Office Supplies,Expense,12.46
5
- 2023-12-29,Selling Paintings,Income,13.63
6
- 2023-12-29,Spotify,Expense,12.19
7
- 2023-12-23,Target,Expense,27.08
8
- 2023-12-22,IT Consulting,Income,541.57
9
- 2023-12-22,Phone,Expense,10.7
10
- 2023-12-20,ML Consulting,Income,2641.93
11
- 2023-12-19,Chipotle,Expense,18.9
12
- 2023-12-18,Cold Brew Coffee,Expense,17.67
13
- 2023-12-18,Gas,Expense,8.80
14
- 2023-12-18,CA Property Tax,Expense,1670.34
15
- 2022-11-26,Salary,Income,4000.36
16
- 2022-11-26,Cellphone,Expense,19.27
17
- 2022-11-26,Gym Membership,Expense,24.71
18
- 2022-11-25,Wholefoods,Expense,17.35
19
- 2022-11-24,Freelancing,Income,2409.55
20
- 2022-11-19,Spotify,Expense,20.76
21
- 2022-10-25,Blogging,Income,4044.27
22
- 2022-10-24,Uber Taxi,Expense,18.9
23
- 2022-10-23,Uber Taxi,Expense,27.54
24
- 2022-10-22,Apple Services,Expense,41.25
25
- 2022-10-21,Netflix,Expense,22.8
26
- 2022-01-16,Amazon Lux,Expense,24.11
27
- 2022-01-15,Burger King,Expense,30.08
28
- 2022-01-14,Amazon,Expense,11.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -1,74 +1,51 @@
 
 
 
 
1
  import logging
2
  import os
3
  import uvicorn
4
- from contextlib import asynccontextmanager
5
  from fastapi import FastAPI
6
  from fastapi.middleware.cors import CORSMiddleware
7
  from fastapi.responses import RedirectResponse
8
  from app.api.routers.chat import chat_router
9
- from app.api.routers.user import user_router
10
- from app.api.routers.transaction import transaction_router
11
- from app.api.routers.file_upload import file_upload_router
12
- from app.api.routers.income_statement import income_statement_router
13
  from app.settings import init_settings
 
14
  from fastapi.staticfiles import StaticFiles
15
- from alembic.config import Config
16
- from alembic import command
17
-
18
 
19
- from app.engine.postgresdb import postgresdb
20
- from config.index import config as env
21
-
22
- logger = logging.getLogger("uvicorn")
23
 
 
24
 
25
  init_settings()
26
- # init_observability()
27
-
28
-
29
- def init_app(init_db: bool = True) -> FastAPI:
30
- lifespan = None
31
 
32
- if init_db:
33
- postgresdb.init(env.SQLALCHEMY_DATABASE_URL, {"echo": True, "future": True})
34
 
35
- @asynccontextmanager
36
- async def lifespan(app: FastAPI):
37
- yield
38
- if postgresdb.get_engine() is not None:
39
- await postgresdb.close()
 
 
 
 
 
40
 
41
- app = FastAPI(lifespan=lifespan)
 
 
 
42
 
43
- if env.ENVIRONMENT == "dev":
44
- logger.warning("Running in development mode - allowing CORS for all origins")
45
- app.add_middleware(
46
- CORSMiddleware,
47
- allow_origins=["*"],
48
- allow_credentials=True,
49
- allow_methods=["*"],
50
- allow_headers=["*"],
51
- )
52
 
53
- if os.path.exists("data"):
54
- app.mount("/api/data", StaticFiles(directory="data"), name="static")
55
- app.include_router(chat_router, prefix="/api/chat")
56
- app.include_router(user_router)
57
- app.include_router(transaction_router)
58
- app.include_router(file_upload_router)
59
- app.include_router(income_statement_router)
60
-
61
- return app
62
-
63
-
64
- app = init_app()
65
-
66
-
67
- # Redirect to documentation page when accessing base URL
68
- @app.get("/")
69
- async def redirect_to_docs():
70
- return RedirectResponse(url="/docs")
71
 
72
 
73
  if __name__ == "__main__":
74
- uvicorn.run(app="main:app", host=env.APP_HOST, port=env.APP_PORT, reload=(env.ENVIRONMENT == "dev"))
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+
3
+ load_dotenv()
4
+
5
  import logging
6
  import os
7
  import uvicorn
 
8
  from fastapi import FastAPI
9
  from fastapi.middleware.cors import CORSMiddleware
10
  from fastapi.responses import RedirectResponse
11
  from app.api.routers.chat import chat_router
 
 
 
 
12
  from app.settings import init_settings
13
+ from app.observability import init_observability
14
  from fastapi.staticfiles import StaticFiles
 
 
 
15
 
 
 
 
 
16
 
17
+ app = FastAPI()
18
 
19
  init_settings()
20
+ init_observability()
 
 
 
 
21
 
22
+ environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set
 
23
 
24
+ if environment == "dev":
25
+ logger = logging.getLogger("uvicorn")
26
+ logger.warning("Running in development mode - allowing CORS for all origins")
27
+ app.add_middleware(
28
+ CORSMiddleware,
29
+ allow_origins=["*"],
30
+ allow_credentials=True,
31
+ allow_methods=["*"],
32
+ allow_headers=["*"],
33
+ )
34
 
35
+ # Redirect to documentation page when accessing base URL
36
+ @app.get("/")
37
+ async def redirect_to_docs():
38
+ return RedirectResponse(url="/docs")
39
 
 
 
 
 
 
 
 
 
 
40
 
41
+ if os.path.exists("data"):
42
+ app.mount("/api/data", StaticFiles(directory="data"), name="static")
43
+ app.include_router(chat_router, prefix="/api/chat")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
 
46
  if __name__ == "__main__":
47
+ app_host = os.getenv("APP_HOST", "0.0.0.0")
48
+ app_port = int(os.getenv("APP_PORT", "8000"))
49
+ reload = True if environment == "dev" else False
50
+
51
+ uvicorn.run(app="main:app", host=app_host, port=app_port, reload=reload)
migration/README DELETED
@@ -1 +0,0 @@
1
- Generic single-database configuration.
 
 
migration/env.py DELETED
@@ -1,91 +0,0 @@
1
- import asyncio
2
- from logging.config import fileConfig
3
-
4
- from app.engine.postgresdb import Base
5
- from sqlalchemy import Connection
6
- from sqlalchemy import pool
7
- from sqlalchemy.ext.asyncio import async_engine_from_config
8
-
9
- from config.index import config as env
10
-
11
- from alembic import context
12
-
13
- # this is the Alembic Config object, which provides
14
- # access to the values within the .ini file in use.
15
- config = context.config
16
-
17
- config.set_main_option("sqlalchemy.url", env.SQLALCHEMY_DATABASE_URL)
18
-
19
- # Interpret the config file for Python logging.
20
- # This line sets up loggers basically.
21
- if config.config_file_name is not None:
22
- fileConfig(config.config_file_name)
23
-
24
- # Import all models so they're registered with SQLAlchemy.
25
- import app.model.user
26
- import app.model.transaction
27
- import app.model.income_statement
28
-
29
- # add your model's MetaData object here
30
- # for 'autogenerate' support
31
- # from myapp import mymodel
32
- # target_metadata = mymodel.Base.metadata
33
- target_metadata = Base.metadata
34
-
35
- # other values from the config, defined by the needs of env.py,
36
- # can be acquired:
37
- # my_important_option = config.get_main_option("my_important_option")
38
- # ... etc.
39
-
40
-
41
- def run_migrations_offline():
42
- """Run migrations in 'offline' mode.
43
- This configures the context with just a URL
44
- and not an Engine, though an Engine is acceptable
45
- here as well. By skipping the Engine creation
46
- we don't even need a DBAPI to be available.
47
- Calls to context.execute() here emit the given string to the
48
- script output.
49
- """
50
- url = config.get_main_option("sqlalchemy.url")
51
- context.configure(
52
- url=url,
53
- target_metadata=target_metadata,
54
- literal_binds=True,
55
- dialect_opts={"paramstyle": "named"},
56
- )
57
-
58
- with context.begin_transaction():
59
- context.run_migrations()
60
-
61
-
62
- def do_run_migrations(connection: Connection) -> None:
63
- context.configure(connection=connection, target_metadata=target_metadata)
64
-
65
- with context.begin_transaction():
66
- context.run_migrations()
67
-
68
-
69
- async def run_migrations_online():
70
- """Run migrations in 'online' mode.
71
- In this scenario we need to create an Engine
72
- and associate a connection with the context.
73
- """
74
- configuration = config.get_section(config.config_ini_section)
75
- configuration["sqlalchemy.url"] = env.SQLALCHEMY_DATABASE_URL
76
- connectable = async_engine_from_config(
77
- configuration,
78
- prefix="sqlalchemy.",
79
- poolclass=pool.NullPool,
80
- )
81
-
82
- async with connectable.connect() as connection:
83
- await connection.run_sync(do_run_migrations)
84
-
85
- await connectable.dispose()
86
-
87
-
88
- if context.is_offline_mode():
89
- run_migrations_offline()
90
- else:
91
- asyncio.run(run_migrations_online())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
migration/script.py.mako DELETED
@@ -1,26 +0,0 @@
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, 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
- ${upgrades if upgrades else "pass"}
23
-
24
-
25
- def downgrade() -> None:
26
- ${downgrades if downgrades else "pass"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
migration/versions/4e76691ab103_add_income_statement_table.py DELETED
@@ -1,43 +0,0 @@
1
- """add_income_statement_table
2
-
3
- Revision ID: 4e76691ab103
4
- Revises: 7ea44cbc5b1f
5
- Create Date: 2024-06-03 23:59:03.717778
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
- # revision identifiers, used by Alembic.
15
- revision: str = '4e76691ab103'
16
- down_revision: Union[str, None] = '7ea44cbc5b1f'
17
- branch_labels: Union[str, Sequence[str], None] = None
18
- depends_on: Union[str, Sequence[str], None] = None
19
-
20
-
21
- def upgrade() -> None:
22
- # ### commands auto generated by Alembic - please adjust! ###
23
- op.create_table('income_statement',
24
- sa.Column('income', postgresql.JSON(astext_type=sa.Text()), nullable=True),
25
- sa.Column('expenses', postgresql.JSON(astext_type=sa.Text()), nullable=True),
26
- sa.Column('date_from', sa.DateTime(), nullable=False),
27
- sa.Column('date_to', sa.DateTime(), nullable=False),
28
- sa.Column('user_id', sa.Integer(), nullable=False),
29
- sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
30
- sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
31
- sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
32
- sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
33
- sa.PrimaryKeyConstraint('id')
34
- )
35
- op.create_index(op.f('ix_income_statement_id'), 'income_statement', ['id'], unique=False)
36
- # ### end Alembic commands ###
37
-
38
-
39
- def downgrade() -> None:
40
- # ### commands auto generated by Alembic - please adjust! ###
41
- op.drop_index(op.f('ix_income_statement_id'), table_name='income_statement')
42
- op.drop_table('income_statement')
43
- # ### end Alembic commands ###
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
migration/versions/7ea44cbc5b1f_default_timezone.py DELETED
@@ -1,69 +0,0 @@
1
- """default_timezone
2
-
3
- Revision ID: 7ea44cbc5b1f
4
- Revises: 8feaedca36f9
5
- Create Date: 2024-06-02 14:36:01.552518
6
-
7
- """
8
-
9
- from typing import Sequence, Union
10
-
11
- from alembic import op
12
- import sqlalchemy as sa
13
- from sqlalchemy.dialects import postgresql
14
-
15
- # revision identifiers, used by Alembic.
16
- revision: str = "7ea44cbc5b1f"
17
- down_revision: Union[str, None] = "8feaedca36f9"
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
- # ### commands auto generated by Alembic - please adjust! ###
24
- op.alter_column(
25
- "transactions",
26
- "created_at",
27
- existing_type=postgresql.TIMESTAMP(),
28
- type_=sa.DateTime(timezone=True),
29
- nullable=False,
30
- )
31
- op.alter_column(
32
- "transactions",
33
- "updated_at",
34
- existing_type=postgresql.TIMESTAMP(),
35
- type_=sa.DateTime(timezone=True),
36
- nullable=False,
37
- )
38
- op.alter_column(
39
- "users", "created_at", existing_type=postgresql.TIMESTAMP(), type_=sa.DateTime(timezone=True), nullable=False
40
- )
41
- op.alter_column(
42
- "users", "updated_at", existing_type=postgresql.TIMESTAMP(), type_=sa.DateTime(timezone=True), nullable=False
43
- )
44
- # ### end Alembic commands ###
45
-
46
-
47
- def downgrade() -> None:
48
- # ### commands auto generated by Alembic - please adjust! ###
49
- op.alter_column(
50
- "users", "updated_at", existing_type=sa.DateTime(timezone=True), type_=postgresql.TIMESTAMP(), nullable=True
51
- )
52
- op.alter_column(
53
- "users", "created_at", existing_type=sa.DateTime(timezone=True), type_=postgresql.TIMESTAMP(), nullable=True
54
- )
55
- op.alter_column(
56
- "transactions",
57
- "updated_at",
58
- existing_type=sa.DateTime(timezone=True),
59
- type_=postgresql.TIMESTAMP(),
60
- nullable=True,
61
- )
62
- op.alter_column(
63
- "transactions",
64
- "created_at",
65
- existing_type=sa.DateTime(timezone=True),
66
- type_=postgresql.TIMESTAMP(),
67
- nullable=True,
68
- )
69
- # ### end Alembic commands ###
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
migration/versions/8feaedca36f9_users_datatype_updates.py DELETED
@@ -1,74 +0,0 @@
1
- """Remove_Transactions_From_Users
2
-
3
- Revision ID: 8feaedca36f9
4
- Revises: cd515c44401d
5
- Create Date: 2024-06-02 01:26:54.731002
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
- # revision identifiers, used by Alembic.
15
- revision: str = '8feaedca36f9'
16
- down_revision: Union[str, None] = 'cd515c44401d'
17
- branch_labels: Union[str, Sequence[str], None] = None
18
- depends_on: Union[str, Sequence[str], None] = None
19
-
20
-
21
- def upgrade() -> None:
22
- # ### commands auto generated by Alembic - please adjust! ###
23
- op.alter_column('transactions', 'transaction_date',
24
- existing_type=postgresql.TIMESTAMP(),
25
- nullable=False)
26
- op.alter_column('transactions', 'category',
27
- existing_type=sa.VARCHAR(),
28
- nullable=False)
29
- op.alter_column('transactions', 'name_description',
30
- existing_type=sa.VARCHAR(),
31
- nullable=False)
32
- op.alter_column('transactions', 'amount',
33
- existing_type=sa.DOUBLE_PRECISION(precision=53),
34
- nullable=False)
35
- op.alter_column('transactions', 'type',
36
- existing_type=sa.VARCHAR(),
37
- nullable=False)
38
- op.add_column('users', sa.Column('hashed_password', sa.String(), nullable=False))
39
- op.add_column('users', sa.Column('is_deleted', sa.Boolean(), nullable=False))
40
- op.alter_column('users', 'name',
41
- existing_type=sa.VARCHAR(),
42
- nullable=False)
43
- op.alter_column('users', 'email',
44
- existing_type=sa.VARCHAR(),
45
- nullable=False)
46
- # ### end Alembic commands ###
47
-
48
-
49
- def downgrade() -> None:
50
- # ### commands auto generated by Alembic - please adjust! ###
51
- op.alter_column('users', 'email',
52
- existing_type=sa.VARCHAR(),
53
- nullable=True)
54
- op.alter_column('users', 'name',
55
- existing_type=sa.VARCHAR(),
56
- nullable=True)
57
- op.drop_column('users', 'is_deleted')
58
- op.drop_column('users', 'hashed_password')
59
- op.alter_column('transactions', 'type',
60
- existing_type=sa.VARCHAR(),
61
- nullable=True)
62
- op.alter_column('transactions', 'amount',
63
- existing_type=sa.DOUBLE_PRECISION(precision=53),
64
- nullable=True)
65
- op.alter_column('transactions', 'name_description',
66
- existing_type=sa.VARCHAR(),
67
- nullable=True)
68
- op.alter_column('transactions', 'category',
69
- existing_type=sa.VARCHAR(),
70
- nullable=True)
71
- op.alter_column('transactions', 'transaction_date',
72
- existing_type=postgresql.TIMESTAMP(),
73
- nullable=True)
74
- # ### end Alembic commands ###
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
migration/versions/cd515c44401d_add_users_transactions_tables.py DELETED
@@ -1,55 +0,0 @@
1
- """Add users, transactions tables
2
-
3
- Revision ID: cd515c44401d
4
- Revises:
5
- Create Date: 2024-06-01 01:32:42.500524
6
-
7
- """
8
- from typing import Sequence, Union
9
-
10
- from alembic import op
11
- import sqlalchemy as sa
12
-
13
-
14
- # revision identifiers, used by Alembic.
15
- revision: str = 'cd515c44401d'
16
- down_revision: Union[str, None] = None
17
- branch_labels: Union[str, Sequence[str], None] = None
18
- depends_on: Union[str, Sequence[str], None] = None
19
-
20
-
21
- def upgrade() -> None:
22
- # ### commands auto generated by Alembic - please adjust! ###
23
- op.create_table('users',
24
- sa.Column('name', sa.String(), nullable=True),
25
- sa.Column('email', sa.String(), nullable=True),
26
- sa.Column('id', sa.Integer(), nullable=False),
27
- sa.Column('created_at', sa.DateTime(), nullable=True),
28
- sa.Column('updated_at', sa.DateTime(), nullable=True),
29
- sa.PrimaryKeyConstraint('id')
30
- )
31
- op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
32
- op.create_table('transactions',
33
- sa.Column('transaction_date', sa.DateTime(), nullable=True),
34
- sa.Column('category', sa.String(), nullable=True),
35
- sa.Column('name_description', sa.String(), nullable=True),
36
- sa.Column('amount', sa.Float(), nullable=True),
37
- sa.Column('type', sa.String(), nullable=True),
38
- sa.Column('user_id', sa.Integer(), nullable=True),
39
- sa.Column('id', sa.Integer(), nullable=False),
40
- sa.Column('created_at', sa.DateTime(), nullable=True),
41
- sa.Column('updated_at', sa.DateTime(), nullable=True),
42
- sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
43
- sa.PrimaryKeyConstraint('id')
44
- )
45
- op.create_index(op.f('ix_transactions_id'), 'transactions', ['id'], unique=False)
46
- # ### end Alembic commands ###
47
-
48
-
49
- def downgrade() -> None:
50
- # ### commands auto generated by Alembic - please adjust! ###
51
- op.drop_index(op.f('ix_transactions_id'), table_name='transactions')
52
- op.drop_table('transactions')
53
- op.drop_index(op.f('ix_users_id'), table_name='users')
54
- op.drop_table('users')
55
- # ### end Alembic commands ###
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
poetry.lock CHANGED
@@ -123,25 +123,6 @@ files = [
123
  [package.dependencies]
124
  typing-extensions = "*"
125
 
126
- [[package]]
127
- name = "alembic"
128
- version = "1.13.1"
129
- description = "A database migration tool for SQLAlchemy."
130
- optional = false
131
- python-versions = ">=3.8"
132
- files = [
133
- {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"},
134
- {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"},
135
- ]
136
-
137
- [package.dependencies]
138
- Mako = "*"
139
- SQLAlchemy = ">=1.3.0"
140
- typing-extensions = ">=4"
141
-
142
- [package.extras]
143
- tz = ["backports.zoneinfo"]
144
-
145
  [[package]]
146
  name = "annotated-types"
147
  version = "0.7.0"
@@ -198,87 +179,6 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin
198
  test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
199
  trio = ["trio (>=0.23)"]
200
 
201
- [[package]]
202
- name = "async-timeout"
203
- version = "4.0.3"
204
- description = "Timeout context manager for asyncio programs"
205
- optional = false
206
- python-versions = ">=3.7"
207
- files = [
208
- {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
209
- {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
210
- ]
211
-
212
- [[package]]
213
- name = "asyncio"
214
- version = "3.4.3"
215
- description = "reference implementation of PEP 3156"
216
- optional = false
217
- python-versions = "*"
218
- files = [
219
- {file = "asyncio-3.4.3-cp33-none-win32.whl", hash = "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de"},
220
- {file = "asyncio-3.4.3-cp33-none-win_amd64.whl", hash = "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c"},
221
- {file = "asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"},
222
- {file = "asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41"},
223
- ]
224
-
225
- [[package]]
226
- name = "asyncpg"
227
- version = "0.29.0"
228
- description = "An asyncio PostgreSQL driver"
229
- optional = false
230
- python-versions = ">=3.8.0"
231
- files = [
232
- {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"},
233
- {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"},
234
- {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"},
235
- {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"},
236
- {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"},
237
- {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"},
238
- {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"},
239
- {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"},
240
- {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"},
241
- {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"},
242
- {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"},
243
- {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"},
244
- {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"},
245
- {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"},
246
- {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"},
247
- {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"},
248
- {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"},
249
- {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"},
250
- {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"},
251
- {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"},
252
- {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"},
253
- {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"},
254
- {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"},
255
- {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"},
256
- {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"},
257
- {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"},
258
- {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"},
259
- {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"},
260
- {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"},
261
- {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"},
262
- {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"},
263
- {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"},
264
- {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"},
265
- {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"},
266
- {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"},
267
- {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"},
268
- {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"},
269
- {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"},
270
- {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"},
271
- {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"},
272
- {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"},
273
- ]
274
-
275
- [package.dependencies]
276
- async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""}
277
-
278
- [package.extras]
279
- docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
280
- test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"]
281
-
282
  [[package]]
283
  name = "attrs"
284
  version = "23.2.0"
@@ -480,28 +380,6 @@ files = [
480
  marshmallow = ">=3.18.0,<4.0.0"
481
  typing-inspect = ">=0.4.0,<1"
482
 
483
- [[package]]
484
- name = "dateparser"
485
- version = "1.2.0"
486
- description = "Date parsing library designed to parse dates from HTML pages"
487
- optional = false
488
- python-versions = ">=3.7"
489
- files = [
490
- {file = "dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830"},
491
- {file = "dateparser-1.2.0.tar.gz", hash = "sha256:7975b43a4222283e0ae15be7b4999d08c9a70e2d378ac87385b1ccf2cffbbb30"},
492
- ]
493
-
494
- [package.dependencies]
495
- python-dateutil = "*"
496
- pytz = "*"
497
- regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27"
498
- tzlocal = "*"
499
-
500
- [package.extras]
501
- calendars = ["convertdate", "hijri-converter"]
502
- fasttext = ["fasttext"]
503
- langdetect = ["langdetect"]
504
-
505
  [[package]]
506
  name = "deprecated"
507
  version = "1.2.14"
@@ -541,20 +419,6 @@ files = [
541
  {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
542
  ]
543
 
544
- [[package]]
545
- name = "execnet"
546
- version = "2.1.1"
547
- description = "execnet: rapid multi-Python deployment"
548
- optional = false
549
- python-versions = ">=3.8"
550
- files = [
551
- {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
552
- {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
553
- ]
554
-
555
- [package.extras]
556
- testing = ["hatch", "pre-commit", "pytest", "tox"]
557
-
558
  [[package]]
559
  name = "fastapi"
560
  version = "0.109.2"
@@ -1039,17 +903,6 @@ files = [
1039
  {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
1040
  ]
1041
 
1042
- [[package]]
1043
- name = "iniconfig"
1044
- version = "2.0.0"
1045
- description = "brain-dead simple config-ini parsing"
1046
- optional = false
1047
- python-versions = ">=3.7"
1048
- files = [
1049
- {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
1050
- {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
1051
- ]
1052
-
1053
  [[package]]
1054
  name = "jinja2"
1055
  version = "3.1.4"
@@ -1148,139 +1001,6 @@ files = [
1148
  {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"},
1149
  ]
1150
 
1151
- [[package]]
1152
- name = "jsonpatch"
1153
- version = "1.33"
1154
- description = "Apply JSON-Patches (RFC 6902)"
1155
- optional = false
1156
- python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
1157
- files = [
1158
- {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"},
1159
- {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"},
1160
- ]
1161
-
1162
- [package.dependencies]
1163
- jsonpointer = ">=1.9"
1164
-
1165
- [[package]]
1166
- name = "jsonpointer"
1167
- version = "2.4"
1168
- description = "Identify specific nodes in a JSON document (RFC 6901)"
1169
- optional = false
1170
- python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
1171
- files = [
1172
- {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"},
1173
- {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"},
1174
- ]
1175
-
1176
- [[package]]
1177
- name = "langchain"
1178
- version = "0.2.1"
1179
- description = "Building applications with LLMs through composability"
1180
- optional = false
1181
- python-versions = "<4.0,>=3.8.1"
1182
- files = [
1183
- {file = "langchain-0.2.1-py3-none-any.whl", hash = "sha256:3e13bf97c5717bce2c281f5117e8778823e8ccf62d949e73d3869448962b1c97"},
1184
- {file = "langchain-0.2.1.tar.gz", hash = "sha256:5758a315e1ac92eb26dafec5ad0fafa03cafa686aba197d5bb0b1dd28cc03ebe"},
1185
- ]
1186
-
1187
- [package.dependencies]
1188
- aiohttp = ">=3.8.3,<4.0.0"
1189
- langchain-core = ">=0.2.0,<0.3.0"
1190
- langchain-text-splitters = ">=0.2.0,<0.3.0"
1191
- langsmith = ">=0.1.17,<0.2.0"
1192
- numpy = ">=1,<2"
1193
- pydantic = ">=1,<3"
1194
- PyYAML = ">=5.3"
1195
- requests = ">=2,<3"
1196
- SQLAlchemy = ">=1.4,<3"
1197
- tenacity = ">=8.1.0,<9.0.0"
1198
-
1199
- [package.extras]
1200
- azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-textanalytics (>=5.3.0,<6.0.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (<2)"]
1201
- clarifai = ["clarifai (>=9.1.0)"]
1202
- cli = ["typer (>=0.9.0,<0.10.0)"]
1203
- cohere = ["cohere (>=4,<6)"]
1204
- docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"]
1205
- embeddings = ["sentence-transformers (>=2,<3)"]
1206
- extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<6)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.1,<0.2)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
1207
- javascript = ["esprima (>=4.0.1,<5.0.0)"]
1208
- llms = ["clarifai (>=9.1.0)", "cohere (>=4,<6)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
1209
- openai = ["openai (<2)", "tiktoken (>=0.7,<1.0)"]
1210
- qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"]
1211
- text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
1212
-
1213
- [[package]]
1214
- name = "langchain-core"
1215
- version = "0.2.3"
1216
- description = "Building applications with LLMs through composability"
1217
- optional = false
1218
- python-versions = "<4.0,>=3.8.1"
1219
- files = [
1220
- {file = "langchain_core-0.2.3-py3-none-any.whl", hash = "sha256:22189b5a3a30bfd65eb995f95e627f7c2c3acb322feb89f5f5f2fb7df21833a7"},
1221
- {file = "langchain_core-0.2.3.tar.gz", hash = "sha256:fbc75a64b9c0b7655d96ca57a707df1e6c09efc1539c36adbd73260612549810"},
1222
- ]
1223
-
1224
- [package.dependencies]
1225
- jsonpatch = ">=1.33,<2.0"
1226
- langsmith = ">=0.1.65,<0.2.0"
1227
- packaging = ">=23.2,<24.0"
1228
- pydantic = ">=1,<3"
1229
- PyYAML = ">=5.3"
1230
- tenacity = ">=8.1.0,<9.0.0"
1231
-
1232
- [package.extras]
1233
- extended-testing = ["jinja2 (>=3,<4)"]
1234
-
1235
- [[package]]
1236
- name = "langchain-openai"
1237
- version = "0.1.8"
1238
- description = "An integration package connecting OpenAI and LangChain"
1239
- optional = false
1240
- python-versions = "<4.0,>=3.8.1"
1241
- files = [
1242
- {file = "langchain_openai-0.1.8-py3-none-any.whl", hash = "sha256:8125c84223e9f43b05defbca64eedbcf362fd78a680de6c25e64f973b34a8063"},
1243
- {file = "langchain_openai-0.1.8.tar.gz", hash = "sha256:a11fcce15def7917c44232abda6baaa63dfc79fe44be1531eea650d39a44cd95"},
1244
- ]
1245
-
1246
- [package.dependencies]
1247
- langchain-core = ">=0.2.2,<0.3"
1248
- openai = ">=1.26.0,<2.0.0"
1249
- tiktoken = ">=0.7,<1"
1250
-
1251
- [[package]]
1252
- name = "langchain-text-splitters"
1253
- version = "0.2.0"
1254
- description = "LangChain text splitting utilities"
1255
- optional = false
1256
- python-versions = "<4.0,>=3.8.1"
1257
- files = [
1258
- {file = "langchain_text_splitters-0.2.0-py3-none-any.whl", hash = "sha256:7b4c6a45f8471630a882b321e138329b6897102a5bc62f4c12be1c0b05bb9199"},
1259
- {file = "langchain_text_splitters-0.2.0.tar.gz", hash = "sha256:b32ab4f7397f7d42c1fa3283fefc2547ba356bd63a68ee9092865e5ad83c82f9"},
1260
- ]
1261
-
1262
- [package.dependencies]
1263
- langchain-core = ">=0.2.0,<0.3.0"
1264
-
1265
- [package.extras]
1266
- extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"]
1267
-
1268
- [[package]]
1269
- name = "langsmith"
1270
- version = "0.1.65"
1271
- description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
1272
- optional = false
1273
- python-versions = "<4.0,>=3.8.1"
1274
- files = [
1275
- {file = "langsmith-0.1.65-py3-none-any.whl", hash = "sha256:ab4487029240e69cca30da1065f1e9138e5a7ca2bbe8c697f0bd7d5839f71cf7"},
1276
- {file = "langsmith-0.1.65.tar.gz", hash = "sha256:d3c2eb2391478bd79989f02652cf66e29a7959d677614b6993a47cef43f7f43b"},
1277
- ]
1278
-
1279
- [package.dependencies]
1280
- orjson = ">=3.9.14,<4.0.0"
1281
- pydantic = ">=1,<3"
1282
- requests = ">=2,<3"
1283
-
1284
  [[package]]
1285
  name = "llama-index"
1286
  version = "0.10.28"
@@ -1590,25 +1310,6 @@ files = [
1590
  httpx = ">=0.20.0"
1591
  pydantic = ">=1.10"
1592
 
1593
- [[package]]
1594
- name = "mako"
1595
- version = "1.3.5"
1596
- description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
1597
- optional = false
1598
- python-versions = ">=3.8"
1599
- files = [
1600
- {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"},
1601
- {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"},
1602
- ]
1603
-
1604
- [package.dependencies]
1605
- MarkupSafe = ">=0.9.2"
1606
-
1607
- [package.extras]
1608
- babel = ["Babel"]
1609
- lingua = ["lingua"]
1610
- testing = ["pytest"]
1611
-
1612
  [[package]]
1613
  name = "markupsafe"
1614
  version = "2.1.5"
@@ -1697,20 +1398,6 @@ dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"]
1697
  docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"]
1698
  tests = ["pytest", "pytz", "simplejson"]
1699
 
1700
- [[package]]
1701
- name = "mirakuru"
1702
- version = "2.5.2"
1703
- description = "Process executor (not only) for tests."
1704
- optional = false
1705
- python-versions = ">=3.8"
1706
- files = [
1707
- {file = "mirakuru-2.5.2-py3-none-any.whl", hash = "sha256:90c2d90a8cf14349b2f33e6db30a16acd855499811e0312e56cf80ceacf2d3e5"},
1708
- {file = "mirakuru-2.5.2.tar.gz", hash = "sha256:41ca583d355eb7a6cfdc21c1aea549979d685c27b57239b88725434f115a7132"},
1709
- ]
1710
-
1711
- [package.dependencies]
1712
- psutil = {version = ">=4.0.0", markers = "sys_platform != \"cygwin\""}
1713
-
1714
  [[package]]
1715
  name = "monotonic"
1716
  version = "1.6"
@@ -1933,13 +1620,13 @@ files = [
1933
 
1934
  [[package]]
1935
  name = "openai"
1936
- version = "1.30.5"
1937
  description = "The official Python library for the openai API"
1938
  optional = false
1939
  python-versions = ">=3.7.1"
1940
  files = [
1941
- {file = "openai-1.30.5-py3-none-any.whl", hash = "sha256:2ad95e926de0d2e09cde632a9204b0a6dca4a03c2cdcc84329b01f355784355a"},
1942
- {file = "openai-1.30.5.tar.gz", hash = "sha256:5366562eb2c5917e6116ae0391b7ae6e3acd62b0ae3f565ada32b35d8fcfa106"},
1943
  ]
1944
 
1945
  [package.dependencies]
@@ -2442,70 +2129,15 @@ files = [
2442
  {file = "opentelemetry_util_http-0.45b0.tar.gz", hash = "sha256:4ce08b6a7d52dd7c96b7705b5b4f06fdb6aa3eac1233b3b0bfef8a0cab9a92cd"},
2443
  ]
2444
 
2445
- [[package]]
2446
- name = "orjson"
2447
- version = "3.10.3"
2448
- description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
2449
- optional = false
2450
- python-versions = ">=3.8"
2451
- files = [
2452
- {file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"},
2453
- {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"},
2454
- {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"},
2455
- {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"},
2456
- {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"},
2457
- {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"},
2458
- {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"},
2459
- {file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"},
2460
- {file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"},
2461
- {file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"},
2462
- {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"},
2463
- {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"},
2464
- {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"},
2465
- {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"},
2466
- {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"},
2467
- {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"},
2468
- {file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"},
2469
- {file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"},
2470
- {file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"},
2471
- {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"},
2472
- {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"},
2473
- {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"},
2474
- {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"},
2475
- {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"},
2476
- {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"},
2477
- {file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"},
2478
- {file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"},
2479
- {file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"},
2480
- {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"},
2481
- {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"},
2482
- {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"},
2483
- {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"},
2484
- {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"},
2485
- {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"},
2486
- {file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"},
2487
- {file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"},
2488
- {file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"},
2489
- {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"},
2490
- {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"},
2491
- {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"},
2492
- {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"},
2493
- {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"},
2494
- {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"},
2495
- {file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"},
2496
- {file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"},
2497
- {file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"},
2498
- ]
2499
-
2500
  [[package]]
2501
  name = "packaging"
2502
- version = "23.2"
2503
  description = "Core utilities for Python packages"
2504
  optional = false
2505
  python-versions = ">=3.7"
2506
  files = [
2507
- {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
2508
- {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
2509
  ]
2510
 
2511
  [[package]]
@@ -2577,21 +2209,6 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d
2577
  test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
2578
  xml = ["lxml (>=4.9.2)"]
2579
 
2580
- [[package]]
2581
- name = "path"
2582
- version = "16.14.0"
2583
- description = "A module wrapper for os.path"
2584
- optional = false
2585
- python-versions = ">=3.8"
2586
- files = [
2587
- {file = "path-16.14.0-py3-none-any.whl", hash = "sha256:8ee37703cbdc7cc83835ed4ecc6b638226fb2b43b7b45f26b620589981a109a5"},
2588
- {file = "path-16.14.0.tar.gz", hash = "sha256:dbaaa7efd4602fd6ba8d82890dc7823d69e5de740a6e842d9919b0faaf2b6a8e"},
2589
- ]
2590
-
2591
- [package.extras]
2592
- docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
2593
- testing = ["appdirs", "more-itertools", "packaging", "pygments", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "pywin32"]
2594
-
2595
  [[package]]
2596
  name = "pillow"
2597
  version = "10.3.0"
@@ -2698,32 +2315,6 @@ urllib3 = {version = ">=1.26.0", markers = "python_version >= \"3.8\" and python
2698
  [package.extras]
2699
  grpc = ["googleapis-common-protos (>=1.53.0)", "grpc-gateway-protoc-gen-openapiv2 (==0.1.0)", "grpcio (>=1.44.0)", "grpcio (>=1.59.0)", "lz4 (>=3.1.3)", "protobuf (>=3.20.0,<3.21.0)"]
2700
 
2701
- [[package]]
2702
- name = "pluggy"
2703
- version = "1.5.0"
2704
- description = "plugin and hook calling mechanisms for python"
2705
- optional = false
2706
- python-versions = ">=3.8"
2707
- files = [
2708
- {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
2709
- {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
2710
- ]
2711
-
2712
- [package.extras]
2713
- dev = ["pre-commit", "tox"]
2714
- testing = ["pytest", "pytest-benchmark"]
2715
-
2716
- [[package]]
2717
- name = "port-for"
2718
- version = "0.7.2"
2719
- description = "Utility that helps with local TCP ports management. It can find an unused TCP localhost port and remember the association."
2720
- optional = false
2721
- python-versions = ">=3.8"
2722
- files = [
2723
- {file = "port-for-0.7.2.tar.gz", hash = "sha256:074f29335130578aa42fef3726985e57d01c15189e509633a8a1b0b7f9226349"},
2724
- {file = "port_for-0.7.2-py3-none-any.whl", hash = "sha256:16b279ab4f210bad33515c45bd9af0c6e048ab24c3b6bbd9cfc7e451782617df"},
2725
- ]
2726
-
2727
  [[package]]
2728
  name = "posthog"
2729
  version = "3.5.0"
@@ -2767,143 +2358,20 @@ files = [
2767
  {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
2768
  ]
2769
 
2770
- [[package]]
2771
- name = "psutil"
2772
- version = "5.9.8"
2773
- description = "Cross-platform lib for process and system monitoring in Python."
2774
- optional = false
2775
- python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
2776
- files = [
2777
- {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},
2778
- {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},
2779
- {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},
2780
- {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},
2781
- {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},
2782
- {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},
2783
- {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},
2784
- {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},
2785
- {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},
2786
- {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},
2787
- {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},
2788
- {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},
2789
- {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},
2790
- {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},
2791
- {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},
2792
- {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"},
2793
- ]
2794
-
2795
- [package.extras]
2796
- test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
2797
-
2798
- [[package]]
2799
- name = "psycopg"
2800
- version = "3.1.19"
2801
- description = "PostgreSQL database adapter for Python"
2802
- optional = false
2803
- python-versions = ">=3.7"
2804
- files = [
2805
- {file = "psycopg-3.1.19-py3-none-any.whl", hash = "sha256:dca5e5521c859f6606686432ae1c94e8766d29cc91f2ee595378c510cc5b0731"},
2806
- {file = "psycopg-3.1.19.tar.gz", hash = "sha256:92d7b78ad82426cdcf1a0440678209faa890c6e1721361c2f8901f0dccd62961"},
2807
- ]
2808
-
2809
- [package.dependencies]
2810
- typing-extensions = ">=4.1"
2811
- tzdata = {version = "*", markers = "sys_platform == \"win32\""}
2812
-
2813
- [package.extras]
2814
- binary = ["psycopg-binary (==3.1.19)"]
2815
- c = ["psycopg-c (==3.1.19)"]
2816
- dev = ["black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.4.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"]
2817
- docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"]
2818
- pool = ["psycopg-pool"]
2819
- test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"]
2820
-
2821
- [[package]]
2822
- name = "psycopg-binary"
2823
- version = "3.1.19"
2824
- description = "PostgreSQL database adapter for Python -- C optimisation distribution"
2825
- optional = false
2826
- python-versions = ">=3.7"
2827
- files = [
2828
- {file = "psycopg_binary-3.1.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7204818f05151dd08f8f851defb01972ec9d2cc925608eb0de232563f203f354"},
2829
- {file = "psycopg_binary-3.1.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4e67fd86758dbeac85641419a54f84d74495a8683b58ad5dfad08b7fc37a8f"},
2830
- {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12173e34b176e93ad2da913de30f774d5119c2d4d4640c6858d2d77dfa6c9bf"},
2831
- {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052f5193304066318853b4b2e248f523c8f52b371fc4e95d4ef63baee3f30955"},
2832
- {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29008f3f8977f600b8a7fb07c2e041b01645b08121760609cc45e861a0364dc9"},
2833
- {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6a9a651a08d876303ed059c9553df18b3c13c3406584a70a8f37f1a1fe2709"},
2834
- {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:91a645e6468c4f064b7f4f3b81074bdd68fe5aa2b8c5107de15dcd85ba6141be"},
2835
- {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5c6956808fd5cf0576de5a602243af8e04594b25b9a28675feddc71c5526410a"},
2836
- {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:1622ca27d5a7a98f7d8f35e8b146dc7efda4a4b6241d2edf7e076bd6bcecbeb4"},
2837
- {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a100482950a55228f648bd382bb71bfaff520002f29845274fccbbf02e28bd52"},
2838
- {file = "psycopg_binary-3.1.19-cp310-cp310-win_amd64.whl", hash = "sha256:955ca8905c0251fc4af7ce0a20999e824a25652f53a558ab548b60969f1f368e"},
2839
- {file = "psycopg_binary-3.1.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cf49e91dcf699b8a449944ed898ef1466b39b92720613838791a551bc8f587a"},
2840
- {file = "psycopg_binary-3.1.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:964c307e400c5f33fa762ba1e19853e048814fcfbd9679cc923431adb7a2ead2"},
2841
- {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433924e1b14074798331dc2bfae2af452ed7888067f2fc145835704d8981b15"},
2842
- {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00879d4c6be4b3afc510073f48a5e960f797200e261ab3d9bd9b7746a08c669d"},
2843
- {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a6997c80f86d3dd80a4f078bb3b200079c47eeda4fd409d8899b883c90d2ac"},
2844
- {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0106e42b481677c41caa69474fe530f786dcef88b11b70000f0e45a03534bc8f"},
2845
- {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81efe09ba27533e35709905c3061db4dc9fb814f637360578d065e2061fbb116"},
2846
- {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d312d6dddc18d9c164e1893706269c293cba1923118349d375962b1188dafb01"},
2847
- {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:bfd2c734da9950f7afaad5f132088e0e1478f32f042881fca6651bb0c8d14206"},
2848
- {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8a732610a5a6b4f06dadcf9288688a8ff202fd556d971436a123b7adb85596e2"},
2849
- {file = "psycopg_binary-3.1.19-cp311-cp311-win_amd64.whl", hash = "sha256:321814a9a3ad785855a821b842aba08ca1b7de7dfb2979a2f0492dca9ec4ae70"},
2850
- {file = "psycopg_binary-3.1.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4aa0ca13bb8a725bb6d12c13999217fd5bc8b86a12589f28a74b93e076fbb959"},
2851
- {file = "psycopg_binary-3.1.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:469424e354ebcec949aa6aa30e5a9edc352a899d9a68ad7a48f97df83cc914cf"},
2852
- {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04f5349313529ae1f1c42fe1aa0443faaf50fdf12d13866c2cc49683bfa53d0"},
2853
- {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959feabddc7fffac89b054d6f23f3b3c62d7d3c90cd414a02e3747495597f150"},
2854
- {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e9da624a6ca4bc5f7fa1f03f8485446b5b81d5787b6beea2b4f8d9dbef878ad7"},
2855
- {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1823221a6b96e38b15686170d4fc5b36073efcb87cce7d3da660440b50077f6"},
2856
- {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:866db42f986298f0cf15d805225eb8df2228bf19f7997d7f1cb5f388cbfc6a0f"},
2857
- {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:738c34657305b5973af6dbb6711b07b179dfdd21196d60039ca30a74bafe9648"},
2858
- {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb9758473200384a04374d0e0cac6f451218ff6945a024f65a1526802c34e56e"},
2859
- {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e991632777e217953ac960726158987da684086dd813ac85038c595e7382c91"},
2860
- {file = "psycopg_binary-3.1.19-cp312-cp312-win_amd64.whl", hash = "sha256:1d87484dd42c8783c44a30400949efb3d81ef2487eaa7d64d1c54df90cf8b97a"},
2861
- {file = "psycopg_binary-3.1.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d1d1723d7449c12bb61aca7eb6e0c6ab2863cd8dc0019273cc4d4a1982f84bdb"},
2862
- {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e538a8671005641fa195eab962f85cf0504defbd3b548c4c8fc27102a59f687b"},
2863
- {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c50592bc8517092f40979e4a5d934f96a1737a77724bb1d121eb78b614b30fc8"},
2864
- {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95f16ae82bc242b76cd3c3e5156441e2bd85ff9ec3a9869d750aad443e46073c"},
2865
- {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebd1e98e865e9a28ce0cb2c25b7dfd752f0d1f0a423165b55cd32a431dcc0f4"},
2866
- {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:49cd7af7d49e438a39593d1dd8cab106a1912536c2b78a4d814ebdff2786094e"},
2867
- {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:affebd61aa3b7a8880fd4ac3ee94722940125ff83ff485e1a7c76be9adaabb38"},
2868
- {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d1bac282f140fa092f2bbb6c36ed82270b4a21a6fc55d4b16748ed9f55e50fdb"},
2869
- {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1285aa54449e362b1d30d92b2dc042ad3ee80f479cc4e323448d0a0a8a1641fa"},
2870
- {file = "psycopg_binary-3.1.19-cp37-cp37m-win_amd64.whl", hash = "sha256:6cff31af8155dc9ee364098a328bab688c887c732c66b8d027e5b03818ca0287"},
2871
- {file = "psycopg_binary-3.1.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9b689c4a17dd3130791dcbb8c30dbf05602f7c2d56c792e193fb49adc7bf5f8"},
2872
- {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017518bd2de4851adc826a224fb105411e148ad845e11355edd6786ba3dfedf5"},
2873
- {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c35fd811f339a3cbe7f9b54b2d9a5e592e57426c6cc1051632a62c59c4810208"},
2874
- {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38ed45ec9673709bfa5bc17f140e71dd4cca56d4e58ef7fd50d5a5043a4f55c6"},
2875
- {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:433f1c256108f9e26f480a8cd6ddb0fb37dbc87d7f5a97e4540a9da9b881f23f"},
2876
- {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ed61e43bf5dc8d0936daf03a19fef3168d64191dbe66483f7ad08c4cea0bc36b"},
2877
- {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ae8109ff9fdf1fa0cb87ab6645298693fdd2666a7f5f85660df88f6965e0bb7"},
2878
- {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a53809ee02e3952fae7977c19b30fd828bd117b8f5edf17a3a94212feb57faaf"},
2879
- {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9d39d5ffc151fb33bcd55b99b0e8957299c0b1b3e5a1a5f4399c1287ef0051a9"},
2880
- {file = "psycopg_binary-3.1.19-cp38-cp38-win_amd64.whl", hash = "sha256:e14bc8250000921fcccd53722f86b3b3d1b57db901e206e49e2ab2afc5919c2d"},
2881
- {file = "psycopg_binary-3.1.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd88c5cea4efe614d5004fb5f5dcdea3d7d59422be796689e779e03363102d24"},
2882
- {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:621a814e60825162d38760c66351b4df679fd422c848b7c2f86ad399bff27145"},
2883
- {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46e50c05952b59a214e27d3606f6d510aaa429daed898e16b8a37bfbacc81acc"},
2884
- {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03354a9db667c27946e70162cb0042c3929154167f3678a30d23cebfe0ad55b5"},
2885
- {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c2f3b79037581afec7baa2bdbcb0a1787f1758744a7662099b0eca2d721cb"},
2886
- {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6469ebd9e93327e9f5f36dcf8692fb1e7aeaf70087c1c15d4f2c020e0be3a891"},
2887
- {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:85bca9765c04b6be90cb46e7566ffe0faa2d7480ff5c8d5e055ac427f039fd24"},
2888
- {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:a836610d5c75e9cff98b9fdb3559c007c785c09eaa84a60d5d10ef6f85f671e8"},
2889
- {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8de7a1d9fb3518cc6b58e3c80b75a824209ad52b90c542686c912db8553dad"},
2890
- {file = "psycopg_binary-3.1.19-cp39-cp39-win_amd64.whl", hash = "sha256:76fcd33342f38e35cd6b5408f1bc117d55ab8b16e5019d99b6d3ce0356c51717"},
2891
- ]
2892
-
2893
  [[package]]
2894
  name = "pydantic"
2895
- version = "2.7.2"
2896
  description = "Data validation using Python type hints"
2897
  optional = false
2898
  python-versions = ">=3.8"
2899
  files = [
2900
- {file = "pydantic-2.7.2-py3-none-any.whl", hash = "sha256:834ab954175f94e6e68258537dc49402c4a5e9d0409b9f1b86b7e934a8372de7"},
2901
- {file = "pydantic-2.7.2.tar.gz", hash = "sha256:71b2945998f9c9b7919a45bde9a50397b289937d215ae141c1d0903ba7149fd7"},
2902
  ]
2903
 
2904
  [package.dependencies]
2905
  annotated-types = ">=0.4.0"
2906
- pydantic-core = "2.18.3"
2907
  typing-extensions = ">=4.6.1"
2908
 
2909
  [package.extras]
@@ -2911,90 +2379,90 @@ email = ["email-validator (>=2.0.0)"]
2911
 
2912
  [[package]]
2913
  name = "pydantic-core"
2914
- version = "2.18.3"
2915
  description = "Core functionality for Pydantic validation and serialization"
2916
  optional = false
2917
  python-versions = ">=3.8"
2918
  files = [
2919
- {file = "pydantic_core-2.18.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:744697428fcdec6be5670460b578161d1ffe34743a5c15656be7ea82b008197c"},
2920
- {file = "pydantic_core-2.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b40c05ced1ba4218b14986fe6f283d22e1ae2ff4c8e28881a70fb81fbfcda7"},
2921
- {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a9a75622357076efb6b311983ff190fbfb3c12fc3a853122b34d3d358126c"},
2922
- {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2e253af04ceaebde8eb201eb3f3e3e7e390f2d275a88300d6a1959d710539e2"},
2923
- {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855ec66589c68aa367d989da5c4755bb74ee92ccad4fdb6af942c3612c067e34"},
2924
- {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3e42bb54e7e9d72c13ce112e02eb1b3b55681ee948d748842171201a03a98a"},
2925
- {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6ac9ffccc9d2e69d9fba841441d4259cb668ac180e51b30d3632cd7abca2b9b"},
2926
- {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c56eca1686539fa0c9bda992e7bd6a37583f20083c37590413381acfc5f192d6"},
2927
- {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:17954d784bf8abfc0ec2a633108207ebc4fa2df1a0e4c0c3ccbaa9bb01d2c426"},
2928
- {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98ed737567d8f2ecd54f7c8d4f8572ca7c7921ede93a2e52939416170d357812"},
2929
- {file = "pydantic_core-2.18.3-cp310-none-win32.whl", hash = "sha256:9f9e04afebd3ed8c15d67a564ed0a34b54e52136c6d40d14c5547b238390e779"},
2930
- {file = "pydantic_core-2.18.3-cp310-none-win_amd64.whl", hash = "sha256:45e4ffbae34f7ae30d0047697e724e534a7ec0a82ef9994b7913a412c21462a0"},
2931
- {file = "pydantic_core-2.18.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b9ebe8231726c49518b16b237b9fe0d7d361dd221302af511a83d4ada01183ab"},
2932
- {file = "pydantic_core-2.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b8e20e15d18bf7dbb453be78a2d858f946f5cdf06c5072453dace00ab652e2b2"},
2933
- {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0d9ff283cd3459fa0bf9b0256a2b6f01ac1ff9ffb034e24457b9035f75587cb"},
2934
- {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f7ef5f0ebb77ba24c9970da18b771711edc5feaf00c10b18461e0f5f5949231"},
2935
- {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73038d66614d2e5cde30435b5afdced2b473b4c77d4ca3a8624dd3e41a9c19be"},
2936
- {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6afd5c867a74c4d314c557b5ea9520183fadfbd1df4c2d6e09fd0d990ce412cd"},
2937
- {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd7df92f28d351bb9f12470f4c533cf03d1b52ec5a6e5c58c65b183055a60106"},
2938
- {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80aea0ffeb1049336043d07799eace1c9602519fb3192916ff525b0287b2b1e4"},
2939
- {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaee40f25bba38132e655ffa3d1998a6d576ba7cf81deff8bfa189fb43fd2bbe"},
2940
- {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9128089da8f4fe73f7a91973895ebf2502539d627891a14034e45fb9e707e26d"},
2941
- {file = "pydantic_core-2.18.3-cp311-none-win32.whl", hash = "sha256:fec02527e1e03257aa25b1a4dcbe697b40a22f1229f5d026503e8b7ff6d2eda7"},
2942
- {file = "pydantic_core-2.18.3-cp311-none-win_amd64.whl", hash = "sha256:58ff8631dbab6c7c982e6425da8347108449321f61fe427c52ddfadd66642af7"},
2943
- {file = "pydantic_core-2.18.3-cp311-none-win_arm64.whl", hash = "sha256:3fc1c7f67f34c6c2ef9c213e0f2a351797cda98249d9ca56a70ce4ebcaba45f4"},
2944
- {file = "pydantic_core-2.18.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f0928cde2ae416a2d1ebe6dee324709c6f73e93494d8c7aea92df99aab1fc40f"},
2945
- {file = "pydantic_core-2.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bee9bb305a562f8b9271855afb6ce00223f545de3d68560b3c1649c7c5295e9"},
2946
- {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e862823be114387257dacbfa7d78547165a85d7add33b446ca4f4fae92c7ff5c"},
2947
- {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a36f78674cbddc165abab0df961b5f96b14461d05feec5e1f78da58808b97e7"},
2948
- {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba905d184f62e7ddbb7a5a751d8a5c805463511c7b08d1aca4a3e8c11f2e5048"},
2949
- {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fdd362f6a586e681ff86550b2379e532fee63c52def1c666887956748eaa326"},
2950
- {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b214b7ee3bd3b865e963dbed0f8bc5375f49449d70e8d407b567af3222aae4"},
2951
- {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691018785779766127f531674fa82bb368df5b36b461622b12e176c18e119022"},
2952
- {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60e4c625e6f7155d7d0dcac151edf5858102bc61bf959d04469ca6ee4e8381bd"},
2953
- {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4e651e47d981c1b701dcc74ab8fec5a60a5b004650416b4abbef13db23bc7be"},
2954
- {file = "pydantic_core-2.18.3-cp312-none-win32.whl", hash = "sha256:ffecbb5edb7f5ffae13599aec33b735e9e4c7676ca1633c60f2c606beb17efc5"},
2955
- {file = "pydantic_core-2.18.3-cp312-none-win_amd64.whl", hash = "sha256:2c8333f6e934733483c7eddffdb094c143b9463d2af7e6bd85ebcb2d4a1b82c6"},
2956
- {file = "pydantic_core-2.18.3-cp312-none-win_arm64.whl", hash = "sha256:7a20dded653e516a4655f4c98e97ccafb13753987434fe7cf044aa25f5b7d417"},
2957
- {file = "pydantic_core-2.18.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:eecf63195be644b0396f972c82598cd15693550f0ff236dcf7ab92e2eb6d3522"},
2958
- {file = "pydantic_core-2.18.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c44efdd3b6125419c28821590d7ec891c9cb0dff33a7a78d9d5c8b6f66b9702"},
2959
- {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e59fca51ffbdd1638b3856779342ed69bcecb8484c1d4b8bdb237d0eb5a45e2"},
2960
- {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70cf099197d6b98953468461d753563b28e73cf1eade2ffe069675d2657ed1d5"},
2961
- {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63081a49dddc6124754b32a3774331467bfc3d2bd5ff8f10df36a95602560361"},
2962
- {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:370059b7883485c9edb9655355ff46d912f4b03b009d929220d9294c7fd9fd60"},
2963
- {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a64faeedfd8254f05f5cf6fc755023a7e1606af3959cfc1a9285744cc711044"},
2964
- {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19d2e725de0f90d8671f89e420d36c3dd97639b98145e42fcc0e1f6d492a46dc"},
2965
- {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:67bc078025d70ec5aefe6200ef094576c9d86bd36982df1301c758a9fff7d7f4"},
2966
- {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:adf952c3f4100e203cbaf8e0c907c835d3e28f9041474e52b651761dc248a3c0"},
2967
- {file = "pydantic_core-2.18.3-cp38-none-win32.whl", hash = "sha256:9a46795b1f3beb167eaee91736d5d17ac3a994bf2215a996aed825a45f897558"},
2968
- {file = "pydantic_core-2.18.3-cp38-none-win_amd64.whl", hash = "sha256:200ad4e3133cb99ed82342a101a5abf3d924722e71cd581cc113fe828f727fbc"},
2969
- {file = "pydantic_core-2.18.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:304378b7bf92206036c8ddd83a2ba7b7d1a5b425acafff637172a3aa72ad7083"},
2970
- {file = "pydantic_core-2.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c826870b277143e701c9ccf34ebc33ddb4d072612683a044e7cce2d52f6c3fef"},
2971
- {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e201935d282707394f3668380e41ccf25b5794d1b131cdd96b07f615a33ca4b1"},
2972
- {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5560dda746c44b48bf82b3d191d74fe8efc5686a9ef18e69bdabccbbb9ad9442"},
2973
- {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b32c2a1f8032570842257e4c19288eba9a2bba4712af542327de9a1204faff8"},
2974
- {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:929c24e9dea3990bc8bcd27c5f2d3916c0c86f5511d2caa69e0d5290115344a9"},
2975
- {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a8376fef60790152564b0eab376b3e23dd6e54f29d84aad46f7b264ecca943"},
2976
- {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dccf3ef1400390ddd1fb55bf0632209d39140552d068ee5ac45553b556780e06"},
2977
- {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41dbdcb0c7252b58fa931fec47937edb422c9cb22528f41cb8963665c372caf6"},
2978
- {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:666e45cf071669fde468886654742fa10b0e74cd0fa0430a46ba6056b24fb0af"},
2979
- {file = "pydantic_core-2.18.3-cp39-none-win32.whl", hash = "sha256:f9c08cabff68704a1b4667d33f534d544b8a07b8e5d039c37067fceb18789e78"},
2980
- {file = "pydantic_core-2.18.3-cp39-none-win_amd64.whl", hash = "sha256:4afa5f5973e8572b5c0dcb4e2d4fda7890e7cd63329bd5cc3263a25c92ef0026"},
2981
- {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:77319771a026f7c7d29c6ebc623de889e9563b7087911b46fd06c044a12aa5e9"},
2982
- {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:df11fa992e9f576473038510d66dd305bcd51d7dd508c163a8c8fe148454e059"},
2983
- {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d531076bdfb65af593326ffd567e6ab3da145020dafb9187a1d131064a55f97c"},
2984
- {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33ce258e4e6e6038f2b9e8b8a631d17d017567db43483314993b3ca345dcbbb"},
2985
- {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1f9cd7f5635b719939019be9bda47ecb56e165e51dd26c9a217a433e3d0d59a9"},
2986
- {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cd4a032bb65cc132cae1fe3e52877daecc2097965cd3914e44fbd12b00dae7c5"},
2987
- {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f2718430098bcdf60402136c845e4126a189959d103900ebabb6774a5d9fdb"},
2988
- {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c0037a92cf0c580ed14e10953cdd26528e8796307bb8bb312dc65f71547df04d"},
2989
- {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b95a0972fac2b1ff3c94629fc9081b16371dad870959f1408cc33b2f78ad347a"},
2990
- {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a62e437d687cc148381bdd5f51e3e81f5b20a735c55f690c5be94e05da2b0d5c"},
2991
- {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b367a73a414bbb08507da102dc2cde0fa7afe57d09b3240ce82a16d608a7679c"},
2992
- {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ecce4b2360aa3f008da3327d652e74a0e743908eac306198b47e1c58b03dd2b"},
2993
- {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4435b8d83f0c9561a2a9585b1de78f1abb17cb0cef5f39bf6a4b47d19bafe3"},
2994
- {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:616221a6d473c5b9aa83fa8982745441f6a4a62a66436be9445c65f241b86c94"},
2995
- {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7e6382ce89a92bc1d0c0c5edd51e931432202b9080dc921d8d003e616402efd1"},
2996
- {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff58f379345603d940e461eae474b6bbb6dab66ed9a851ecd3cb3709bf4dcf6a"},
2997
- {file = "pydantic_core-2.18.3.tar.gz", hash = "sha256:432e999088d85c8f36b9a3f769a8e2b57aabd817bbb729a90d1fe7f18f6f1f39"},
2998
  ]
2999
 
3000
  [package.dependencies]
@@ -3018,82 +2486,6 @@ docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"]
3018
  full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"]
3019
  image = ["Pillow (>=8.0.0)"]
3020
 
3021
- [[package]]
3022
- name = "pytest"
3023
- version = "8.2.1"
3024
- description = "pytest: simple powerful testing with Python"
3025
- optional = false
3026
- python-versions = ">=3.8"
3027
- files = [
3028
- {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"},
3029
- {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"},
3030
- ]
3031
-
3032
- [package.dependencies]
3033
- colorama = {version = "*", markers = "sys_platform == \"win32\""}
3034
- iniconfig = "*"
3035
- packaging = "*"
3036
- pluggy = ">=1.5,<2.0"
3037
-
3038
- [package.extras]
3039
- dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
3040
-
3041
- [[package]]
3042
- name = "pytest-asyncio"
3043
- version = "0.23.7"
3044
- description = "Pytest support for asyncio"
3045
- optional = false
3046
- python-versions = ">=3.8"
3047
- files = [
3048
- {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"},
3049
- {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"},
3050
- ]
3051
-
3052
- [package.dependencies]
3053
- pytest = ">=7.0.0,<9"
3054
-
3055
- [package.extras]
3056
- docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
3057
- testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
3058
-
3059
- [[package]]
3060
- name = "pytest-postgresql"
3061
- version = "6.0.0"
3062
- description = "Postgresql fixtures and fixture factories for Pytest."
3063
- optional = false
3064
- python-versions = ">=3.8"
3065
- files = [
3066
- {file = "pytest-postgresql-6.0.0.tar.gz", hash = "sha256:6a4d8e600a2eef273f3c0e846cd0b2ea577282e252de29b4ca854bfb929bb682"},
3067
- {file = "pytest_postgresql-6.0.0-py3-none-any.whl", hash = "sha256:f14272bffad16a74d9a63f4cc828f243a12ae92995e236b68fd53154760e6a5a"},
3068
- ]
3069
-
3070
- [package.dependencies]
3071
- mirakuru = "*"
3072
- port-for = ">=0.6.0"
3073
- psycopg = ">=3.0.0"
3074
- pytest = ">=6.2"
3075
- setuptools = "*"
3076
-
3077
- [[package]]
3078
- name = "pytest-xdist"
3079
- version = "3.6.1"
3080
- description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
3081
- optional = false
3082
- python-versions = ">=3.8"
3083
- files = [
3084
- {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
3085
- {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
3086
- ]
3087
-
3088
- [package.dependencies]
3089
- execnet = ">=2.1"
3090
- pytest = ">=7.0.0"
3091
-
3092
- [package.extras]
3093
- psutil = ["psutil (>=3.0)"]
3094
- setproctitle = ["setproctitle"]
3095
- testing = ["filelock"]
3096
-
3097
  [[package]]
3098
  name = "python-dateutil"
3099
  version = "2.9.0.post0"
@@ -3122,20 +2514,6 @@ files = [
3122
  [package.extras]
3123
  cli = ["click (>=5.0)"]
3124
 
3125
- [[package]]
3126
- name = "python-multipart"
3127
- version = "0.0.9"
3128
- description = "A streaming multipart parser for Python"
3129
- optional = false
3130
- python-versions = ">=3.8"
3131
- files = [
3132
- {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
3133
- {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
3134
- ]
3135
-
3136
- [package.extras]
3137
- dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"]
3138
-
3139
  [[package]]
3140
  name = "pytz"
3141
  version = "2024.1"
@@ -3207,111 +2585,6 @@ files = [
3207
  {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
3208
  ]
3209
 
3210
- [[package]]
3211
- name = "rapidfuzz"
3212
- version = "3.9.2"
3213
- description = "rapid fuzzy string matching"
3214
- optional = false
3215
- python-versions = ">=3.8"
3216
- files = [
3217
- {file = "rapidfuzz-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45e0c3e279e70589381f47ad410de7211bac943e827eb09eb8339d2124abca90"},
3218
- {file = "rapidfuzz-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:280ef2f3066df9c486ffd3874d2489978fb8021044c47c006eb96be8d47917d7"},
3219
- {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe128ac0e05ca3a71d8ff18e70884a64fde00b6fbd2b4d9f59f7a4d798257c55"},
3220
- {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fbc0f6e1b6f4063b937d0edcf0a56cbc1d7179ade9b7d6c849c94e44a7b20f6"},
3221
- {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df19455c2fb85e86a721111b84ac8dd3685194f0edc9faefb226731ad3e134a7"},
3222
- {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:801a5d97c465a3467b3cdf50cdcdadec129ddca582b24430f5d24c715c80be9b"},
3223
- {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f218524596d261a6cb33cda965687e62dd30def478d39f0befa243642c3985"},
3224
- {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5c61d53f293b4e3286919b0e081513367afabcb5aef0b6f899d006117778e558"},
3225
- {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0ed70fc6627ae37319f822e5d8d21d561044e0b3331b6f0e6904476faa8d8ed7"},
3226
- {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96fa229d06ee005d2f46374fb2af65590a590a6fa2fd56e66474829f5fa9adfe"},
3227
- {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6609e881b57cabb40d515cc226bbf570e32e768bd2cc688ba026a45ffbc60875"},
3228
- {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:204fd4d293ef4d409c4142ddf830b7613924b998670f67e512ab1f880a60218a"},
3229
- {file = "rapidfuzz-3.9.2-cp310-cp310-win32.whl", hash = "sha256:5b331a09446bc8f8971cf488c9e6c0f7dbf2739828588e063cf08fd400638a24"},
3230
- {file = "rapidfuzz-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:01a9975984953fe549649e6a4c3f0d9c60707acf458184ec09678d6a57560112"},
3231
- {file = "rapidfuzz-3.9.2-cp310-cp310-win_arm64.whl", hash = "sha256:ca4af5d7fc9c17bdc498aa1cab9ecf5140c8535c9cedeba1990bbe4b8be75098"},
3232
- {file = "rapidfuzz-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:300ab53981a5d6831fe7e0f30c407c79520ad0f0ab51b2cece8717689026f495"},
3233
- {file = "rapidfuzz-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f4828642acdb075154ce2ff3260f8afb6a17b5b0c8a437efbadac06e9995dd7b"},
3234
- {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b262883c3ce93dee1a9a974992961c8098e96b8142e2e01cabdb15ea8105c4a"},
3235
- {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf8582d85e35641734d6c1f43eb37c1f2a5eda338d3cfa8e651e078246b9ec58"},
3236
- {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e33b61ef87e1876d216c479fa2256233b3bb0424465ab2db1d94ab7b8649ae1c"},
3237
- {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fa1b3eb21756003a6a3977847dd4e0e9a26e2e02731d9daa5e92a9258e7f0db"},
3238
- {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923ae0301a56356364f1159e3005fbeb2191e7a0e8705d5cc1b481d9eea27b97"},
3239
- {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8e4041cfd87f0a022aa8a9a187d3b0824e35be2bd9b3bceada11578ddd9ad65"},
3240
- {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1f832b430f976727bdbba009ee64acda25412602976fbfb2113d41e765d81849"},
3241
- {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6ce5e57e0c6acf5a98ffbdfaf8bccb6e41fbddb9eda3e041f4cc69b7cade5fa0"},
3242
- {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d65f34e71102d9cbe733d4ba1c645e7623eef850562501bab1ac79d217831436"},
3243
- {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5dd9ba4df0db46b9f909289e4687cc7721c622985c4cd169969005dd30fc1e24"},
3244
- {file = "rapidfuzz-3.9.2-cp311-cp311-win32.whl", hash = "sha256:34c8bca3fef33d7e71f290de68be2184fac7a9e136fa0ed22b17ec597e181406"},
3245
- {file = "rapidfuzz-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e1a8872c0b8aef95c33db86d25e8bdea6f557b9cdf683123c25035b2bcfb8e"},
3246
- {file = "rapidfuzz-3.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:ed02d73e46b7a4604d2bc1e0364b25f204862d40dd162f6b36ee22b9bf6d9df2"},
3247
- {file = "rapidfuzz-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ae6c4ba2778b097397968130f2b0cb795cdc415c115539a49ce798f606152ad5"},
3248
- {file = "rapidfuzz-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7270556ddebaa98fb777f493f17ed6a733b3527de16c43342bce1db109042845"},
3249
- {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4625273447bdd94f2ab06b2951cd8b74356c3a48552208279a3ec2947ceee141"},
3250
- {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5107b5ec8821453f7cac70b2d0bc4866699b25bff4819ada8b28bf2b11e87f65"},
3251
- {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b04c851d309df8261ed42951444db657936234ceddf4032f4409b0214c95ecbe"},
3252
- {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aeefff80f3f5d6841c30ffe0cdc84d62874de5a64cff509ae26fbd7478297af8"},
3253
- {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cdc106b5a99edd46443449c767287dbb5d4464a7536475a365e368e7ee4d651"},
3254
- {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ce253a2b7a71a01a4abac71ac31fd05f6ac1f1cd2af2d98fa80fe5c402175e54"},
3255
- {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5c30407cadbfe99753b7a996f0dd6da490b1e27d318c01db227e8f49770a01ec"},
3256
- {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fb3fc387783f70387a91aababd8a5faeb230931b655ad99bcf838cd72404ba66"},
3257
- {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c409852a89535ec8720301a847bab198c1c14d0f34ed07dfabbb90b1dbfc506d"},
3258
- {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8603050e547249c1cc8a8dc6a49917076572ea69b04bc51eb1748c403cfc9f46"},
3259
- {file = "rapidfuzz-3.9.2-cp312-cp312-win32.whl", hash = "sha256:77bdb96e82d8831f0dd6db83e2ff0d4a731cff53e926d029c65a1dc3ae0f160a"},
3260
- {file = "rapidfuzz-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:09f354fa28e0fd170c6e4eea5e97eea0dba43761067df93109f49a5414ca8584"},
3261
- {file = "rapidfuzz-3.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:168299c9a2b4f20f10c1bb96d8da0bb05bf1f3b9957be3a0bae5db65ce9f095f"},
3262
- {file = "rapidfuzz-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d87621d60078f87cb52082b1cbf9849afeaa1cb6d0a2b072fce25fe21c8675b4"},
3263
- {file = "rapidfuzz-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c447d0e534418ef3eaabcd890d85c7e9f289c1c6ef6e060a0b1f239799781747"},
3264
- {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7161b205f25eff5f88ab809fb05a2a102634e06f452c0deb9535c9f41cd7b0a"},
3265
- {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f13a6bbadba8fdd42676c1213ebc692bba9fac00f7db0ae92acc06bb734294c4"},
3266
- {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54534743820a15bd0dc30a0a0010825be337973236550fd63587700a7950bbca"},
3267
- {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea61851a4c2f93148aa2779458fb3f70a62342d77c9ec3d9d08445c8485b738"},
3268
- {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e941f81a60351a842976fea208e6a6701a5899eb8a80b907e57d7c3099337900"},
3269
- {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:1bbfaf439e48efe3a48cada946cf7678b09c818ce9668e09dac40d05b772f6f8"},
3270
- {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:574f464da18d660712e9776072572d462cf6a26144c833d18d9c93778286e023"},
3271
- {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8a56c494246d29aacf5ac93ca3cf338d79588a1a5c05d8f496c3f4d7127e9031"},
3272
- {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2943b0f17195c000948a7668bb11979ea0e50079a3d3db9d139e51b68c3a7c26"},
3273
- {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:27214f93555d4f9b7b1baf107a6ba13e9daee21f1ec6e36418556d04a7ee4d9b"},
3274
- {file = "rapidfuzz-3.9.2-cp38-cp38-win32.whl", hash = "sha256:876c6628fec6241262c27f8fda3c73bab88e205e9b9394c8868361e2eda59048"},
3275
- {file = "rapidfuzz-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf1952b486589ffcfbde2015ca9be15e0f4b0e63d1e2c25f3daec0263fda4e69"},
3276
- {file = "rapidfuzz-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1ca9a135060ee4d887d6af86493c3e0eb1c99ca205bca943fe5994dc93e648d5"},
3277
- {file = "rapidfuzz-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:723518c9a18e8bda996d77aa9307b6f8b0e77905702b2772b020adf24191073a"},
3278
- {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65eb9aeae73ac60e53a9d6c509daaa217ea256a5e184eb8920c9b15295c48677"},
3279
- {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef2964f4eb9a37487c96e5e32167a3c4fa51bf8e899853d0ac67e0465a27702c"},
3280
- {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c64a252c96f29667c206726903bb9705c5195f01850360c9b9268de92ac878dc"},
3281
- {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b32b03398517b5e33c7f36d625a00fcb1c955b9fe3c939325688175fb21730"},
3282
- {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec5f7b1bac77439b624f5acbd8bfe61e7b833678701068b43f7a489c151427c0"},
3283
- {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5fd1b49fba8b4b9172eed5b131c1e9864d4d76bebea34359274f16a3591e5f44"},
3284
- {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c05b033fc3ff043f48e744f67038af7fd34003047c7810f24bec7c01ce7da05b"},
3285
- {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c3bea20db89b510d78d017b349b9d87159c32418693ddf091d9035dbe20b4dc0"},
3286
- {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:77226a77590f83ee073f4f8cc86a1232da88e24d19d349361faa169fb17ba1cd"},
3287
- {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:83ed8bc2c942dc61ab739bbca1ead791143b4639dc92156d3060bd0b6f4541ea"},
3288
- {file = "rapidfuzz-3.9.2-cp39-cp39-win32.whl", hash = "sha256:2db70f64974c10b76ae37d5cff6124dce791def815d4fdf5ac16fe60be88d905"},
3289
- {file = "rapidfuzz-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:bdead23114206dea4a22ed3aad6565b99a9e4b3fff9837c423afc556d2814b1a"},
3290
- {file = "rapidfuzz-3.9.2-cp39-cp39-win_arm64.whl", hash = "sha256:0ec69ad076cfc7c88323d671613e40bb8754ba95a203556d9a7759e60f0544e8"},
3291
- {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:018360654881e75131b227aa96cdaba543c438da881c70a12ca0c86e2c4083b2"},
3292
- {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:eaa8178ec9238f32f15b6e49f70b852accda0a848448c4e30bce77c6624ebaba"},
3293
- {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dd79b0f90ce609df96d0d48ef4327cf1f0415b9274588a466d3610a775d2f9"},
3294
- {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04a1c38a72a50f3e6d346a33d53fa51ba390552b3592fca64a07e54d749b439b"},
3295
- {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ca96eec40e815f0cf10b00008f295fd26ca43792a844cf62588a8ea614e160"},
3296
- {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c01c515a928f295f49d588b6523f44b474f047f9f2de0079bc57bcd00b870778"},
3297
- {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:07e14ef260b6f4ee03dff07a0ac95a16aff1ddbc7e6171e07e49d2d61526f3be"},
3298
- {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:64f3480bddc12b89969930f12a50a1aeb53e09aad41cf8b27694d83ca1cc7864"},
3299
- {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3c9e33ec21755bda1878095537cb84848e9cf6510d4837d22144ba04e33df29"},
3300
- {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a70045e84225697ddf67d656aa25b70d6802e2ff339d51f9545fca5b9b13fb8c"},
3301
- {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9ec1fd328518c33adb9171afe8735137cb7b492e4a81cddc23568f9980c235c"},
3302
- {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1fd8458fdac232766d55593c1228c70968f382fdc376c25685273f99b5d1d921"},
3303
- {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a373748fddb5403b562b6d682082de360bb08395f44e3cb7e74819461e39a16c"},
3304
- {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:45f80856db3e22cb5f96ad1572aa1d004714514625ed4668144661d8a7c7e61f"},
3305
- {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:663e52cf878e0ccbbad0744eb3e2bb83a784645b146f15611bac225bc218f19b"},
3306
- {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbe4d3034a8cfe59a2b477375ad7d739b3e5935f10af08abdf64aae55780cad"},
3307
- {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd38abfda97e42b30093f207108dcba944beab1edf6624ba757cf57354063177"},
3308
- {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:16b41fe360387283a3184ce72d4d26d1928e7ce809268a88e8491a776dd770af"},
3309
- {file = "rapidfuzz-3.9.2.tar.gz", hash = "sha256:c899d78709f8d4bd0059784fa27a9f6c53d04fc4aeaa21de7c0c8e34a7154e88"},
3310
- ]
3311
-
3312
- [package.extras]
3313
- full = ["numpy"]
3314
-
3315
  [[package]]
3316
  name = "regex"
3317
  version = "2024.5.15"
@@ -3919,23 +3192,6 @@ files = [
3919
  {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
3920
  ]
3921
 
3922
- [[package]]
3923
- name = "tzlocal"
3924
- version = "5.2"
3925
- description = "tzinfo object for the local timezone"
3926
- optional = false
3927
- python-versions = ">=3.8"
3928
- files = [
3929
- {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"},
3930
- {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"},
3931
- ]
3932
-
3933
- [package.dependencies]
3934
- tzdata = {version = "*", markers = "platform_system == \"Windows\""}
3935
-
3936
- [package.extras]
3937
- devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
3938
-
3939
  [[package]]
3940
  name = "urllib3"
3941
  version = "2.2.1"
@@ -4390,4 +3646,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more
4390
  [metadata]
4391
  lock-version = "2.0"
4392
  python-versions = "^3.11,<3.12"
4393
- content-hash = "99d9141afef8d1d15a2e26cd82d141146ba630edd99b3adc82803f50c2682091"
 
123
  [package.dependencies]
124
  typing-extensions = "*"
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  [[package]]
127
  name = "annotated-types"
128
  version = "0.7.0"
 
179
  test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
180
  trio = ["trio (>=0.23)"]
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  [[package]]
183
  name = "attrs"
184
  version = "23.2.0"
 
380
  marshmallow = ">=3.18.0,<4.0.0"
381
  typing-inspect = ">=0.4.0,<1"
382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  [[package]]
384
  name = "deprecated"
385
  version = "1.2.14"
 
419
  {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
420
  ]
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  [[package]]
423
  name = "fastapi"
424
  version = "0.109.2"
 
903
  {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
904
  ]
905
 
 
 
 
 
 
 
 
 
 
 
 
906
  [[package]]
907
  name = "jinja2"
908
  version = "3.1.4"
 
1001
  {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"},
1002
  ]
1003
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1004
  [[package]]
1005
  name = "llama-index"
1006
  version = "0.10.28"
 
1310
  httpx = ">=0.20.0"
1311
  pydantic = ">=1.10"
1312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1313
  [[package]]
1314
  name = "markupsafe"
1315
  version = "2.1.5"
 
1398
  docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"]
1399
  tests = ["pytest", "pytz", "simplejson"]
1400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1401
  [[package]]
1402
  name = "monotonic"
1403
  version = "1.6"
 
1620
 
1621
  [[package]]
1622
  name = "openai"
1623
+ version = "1.30.1"
1624
  description = "The official Python library for the openai API"
1625
  optional = false
1626
  python-versions = ">=3.7.1"
1627
  files = [
1628
+ {file = "openai-1.30.1-py3-none-any.whl", hash = "sha256:c9fb3c3545c118bbce8deb824397b9433a66d0d0ede6a96f7009c95b76de4a46"},
1629
+ {file = "openai-1.30.1.tar.gz", hash = "sha256:4f85190e577cba0b066e1950b8eb9b11d25bc7ebcc43a86b326ce1bfa564ec74"},
1630
  ]
1631
 
1632
  [package.dependencies]
 
2129
  {file = "opentelemetry_util_http-0.45b0.tar.gz", hash = "sha256:4ce08b6a7d52dd7c96b7705b5b4f06fdb6aa3eac1233b3b0bfef8a0cab9a92cd"},
2130
  ]
2131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2132
  [[package]]
2133
  name = "packaging"
2134
+ version = "24.0"
2135
  description = "Core utilities for Python packages"
2136
  optional = false
2137
  python-versions = ">=3.7"
2138
  files = [
2139
+ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
2140
+ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
2141
  ]
2142
 
2143
  [[package]]
 
2209
  test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
2210
  xml = ["lxml (>=4.9.2)"]
2211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2212
  [[package]]
2213
  name = "pillow"
2214
  version = "10.3.0"
 
2315
  [package.extras]
2316
  grpc = ["googleapis-common-protos (>=1.53.0)", "grpc-gateway-protoc-gen-openapiv2 (==0.1.0)", "grpcio (>=1.44.0)", "grpcio (>=1.59.0)", "lz4 (>=3.1.3)", "protobuf (>=3.20.0,<3.21.0)"]
2317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2318
  [[package]]
2319
  name = "posthog"
2320
  version = "3.5.0"
 
2358
  {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
2359
  ]
2360
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2361
  [[package]]
2362
  name = "pydantic"
2363
+ version = "2.7.1"
2364
  description = "Data validation using Python type hints"
2365
  optional = false
2366
  python-versions = ">=3.8"
2367
  files = [
2368
+ {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"},
2369
+ {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"},
2370
  ]
2371
 
2372
  [package.dependencies]
2373
  annotated-types = ">=0.4.0"
2374
+ pydantic-core = "2.18.2"
2375
  typing-extensions = ">=4.6.1"
2376
 
2377
  [package.extras]
 
2379
 
2380
  [[package]]
2381
  name = "pydantic-core"
2382
+ version = "2.18.2"
2383
  description = "Core functionality for Pydantic validation and serialization"
2384
  optional = false
2385
  python-versions = ">=3.8"
2386
  files = [
2387
+ {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"},
2388
+ {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"},
2389
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"},
2390
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"},
2391
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"},
2392
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"},
2393
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"},
2394
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"},
2395
+ {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"},
2396
+ {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"},
2397
+ {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"},
2398
+ {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"},
2399
+ {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"},
2400
+ {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"},
2401
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"},
2402
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"},
2403
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"},
2404
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"},
2405
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"},
2406
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"},
2407
+ {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"},
2408
+ {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"},
2409
+ {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"},
2410
+ {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"},
2411
+ {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"},
2412
+ {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"},
2413
+ {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"},
2414
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"},
2415
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"},
2416
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"},
2417
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"},
2418
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"},
2419
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"},
2420
+ {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"},
2421
+ {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"},
2422
+ {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"},
2423
+ {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"},
2424
+ {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"},
2425
+ {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"},
2426
+ {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"},
2427
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"},
2428
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"},
2429
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"},
2430
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"},
2431
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"},
2432
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"},
2433
+ {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"},
2434
+ {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"},
2435
+ {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"},
2436
+ {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"},
2437
+ {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"},
2438
+ {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"},
2439
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"},
2440
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"},
2441
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"},
2442
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"},
2443
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"},
2444
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"},
2445
+ {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"},
2446
+ {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"},
2447
+ {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"},
2448
+ {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"},
2449
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"},
2450
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"},
2451
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"},
2452
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"},
2453
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"},
2454
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"},
2455
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"},
2456
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"},
2457
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"},
2458
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"},
2459
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"},
2460
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"},
2461
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"},
2462
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"},
2463
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"},
2464
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"},
2465
+ {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"},
2466
  ]
2467
 
2468
  [package.dependencies]
 
2486
  full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"]
2487
  image = ["Pillow (>=8.0.0)"]
2488
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2489
  [[package]]
2490
  name = "python-dateutil"
2491
  version = "2.9.0.post0"
 
2514
  [package.extras]
2515
  cli = ["click (>=5.0)"]
2516
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2517
  [[package]]
2518
  name = "pytz"
2519
  version = "2024.1"
 
2585
  {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
2586
  ]
2587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2588
  [[package]]
2589
  name = "regex"
2590
  version = "2024.5.15"
 
3192
  {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
3193
  ]
3194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3195
  [[package]]
3196
  name = "urllib3"
3197
  version = "2.2.1"
 
3646
  [metadata]
3647
  lock-version = "2.0"
3648
  python-versions = "^3.11,<3.12"
3649
+ content-hash = "e1009d6d5a3112c225a6916b387f78d5e01bbda9edbd6ee1b78759540da5e72e"
pyproject.toml CHANGED
@@ -3,7 +3,7 @@
3
  name = "app"
4
  version = "0.1.0"
5
  description = ""
6
- authors = [ "Praneeth Yerrapragada, Patrick Alexis" ]
7
  readme = "README.md"
8
  package-mode = false
9
 
@@ -19,42 +19,14 @@ llama-index = "0.10.28"
19
  llama-index-core = "0.10.28"
20
  llama-index-vector-stores-pinecone = "^0.1.7"
21
  traceloop-sdk = "^0.19.0"
22
- langchain = "^0.2.1"
23
- langchain-openai = "^0.1.8"
24
- openai = "^1.30.5"
25
- tenacity = "^8.3.0"
26
- rapidfuzz = "^3.9.2"
27
- pydantic = "^2.7.2"
28
- dateparser = "^1.2.0"
29
- pandas = "^2.2.2"
30
- path = "^16.14.0"
31
- pytest-xdist = "^3.6.1"
32
- pytest-asyncio = "^0.23.7"
33
- httpx = "^0.27.0"
34
- alembic = "^1.13.1"
35
- asyncpg = "^0.29.0"
36
- asyncio = "^3.4.3"
37
- pytest-postgresql = "^6.0.0"
38
- psycopg-binary = "^3.1.19"
39
- python-multipart = "^0.0.9"
40
 
41
  [tool.poetry.dependencies.uvicorn]
42
  extras = [ "standard" ]
43
  version = "^0.23.2"
44
 
45
- [tool.poetry.dependencies.sqlalchemy]
46
- extras = [ "asyncio" ]
47
- version = "^2.0.30"
48
-
49
  [tool.poetry.dependencies.llama-index-agent-openai]
50
  version = "0.2.2"
51
 
52
- [tool.black]
53
- line-length = 119
54
-
55
- [tool.pytest.ini_options]
56
- asyncio_mode = "auto"
57
-
58
  [build-system]
59
  requires = [ "poetry-core" ]
60
  build-backend = "poetry.core.masonry.api"
 
3
  name = "app"
4
  version = "0.1.0"
5
  description = ""
6
+ authors = [ "Marcus Schiesser <mail@marcusschiesser.de>" ]
7
  readme = "README.md"
8
  package-mode = false
9
 
 
19
  llama-index-core = "0.10.28"
20
  llama-index-vector-stores-pinecone = "^0.1.7"
21
  traceloop-sdk = "^0.19.0"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  [tool.poetry.dependencies.uvicorn]
24
  extras = [ "standard" ]
25
  version = "^0.23.2"
26
 
 
 
 
 
27
  [tool.poetry.dependencies.llama-index-agent-openai]
28
  version = "0.2.2"
29
 
 
 
 
 
 
 
30
  [build-system]
31
  requires = [ "poetry-core" ]
32
  build-backend = "poetry.core.masonry.api"
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiostream==0.5.2
2
+ cachetools==5.3.3
3
+ docx2txt==0.8
4
+ fastapi==0.109.1
5
+ llama-index-agent-openai==0.2.2
6
+ llama-index-core==0.10.28
7
+ llama-index-vector-stores-pinecone==0.1.3
8
+ llama-index==0.10.28
9
+ python-dotenv==1.0.0
10
+ traceloop-sdk==0.15.11
11
+ uvicorn==0.23.2
tests/conftest.py DELETED
@@ -1,83 +0,0 @@
1
- # Credits: https://github.com/ThomasAitken/demo-fastapi-async-sqlalchemy/blob/main/backend/app/conftest.py
2
-
3
- import asyncio
4
- from contextlib import ExitStack
5
-
6
- import pytest
7
- from alembic.config import Config
8
- from alembic.migration import MigrationContext
9
- from alembic.operations import Operations
10
- from alembic.script import ScriptDirectory
11
- from config.index import config as settings
12
- from app.engine.postgresdb import Base, get_db_session, postgresdb as sessionmanager
13
- from main import init_app
14
- from asyncpg import Connection
15
- from fastapi.testclient import TestClient
16
- from pytest_postgresql import factories
17
- from pytest_postgresql.factories.noprocess import postgresql_noproc
18
- from pytest_postgresql.janitor import DatabaseJanitor
19
- from sqlalchemy.testing.entities import ComparableEntity
20
-
21
- from config.index import config as env
22
-
23
- test_db = factories.postgresql_proc(dbname="test_db", port=5433)
24
-
25
-
26
- @pytest.fixture(autouse=True)
27
- def app():
28
- with ExitStack():
29
- # Don't initialize database connection.
30
- # This is because we want to initialize the database connection manually, so that we can create the test database.
31
- yield init_app(init_db=False)
32
-
33
-
34
- @pytest.fixture
35
- def client(app):
36
- with TestClient(app) as c:
37
- yield c
38
-
39
-
40
- @pytest.fixture(scope="session")
41
- def event_loop(request):
42
- loop = asyncio.get_event_loop_policy().new_event_loop()
43
- yield loop
44
- loop.close()
45
-
46
-
47
- @pytest.fixture(scope="function", autouse=True)
48
- async def connection_test(test_db, event_loop):
49
- pg_host = test_db.host
50
- pg_port = test_db.port
51
- pg_user = test_db.user
52
- pg_db = test_db.dbname
53
- pg_password = test_db.password
54
-
55
- with DatabaseJanitor(user=pg_user, host=pg_host, port=pg_port, dbname=pg_db, version=test_db.version, password=pg_password):
56
- connection_str = f"postgresql+psycopg://{pg_user}:@{pg_host}:{pg_port}/{pg_db}"
57
- sessionmanager.init(connection_str,
58
- # {"echo": True, "future": True}
59
- )
60
- yield
61
- await sessionmanager.close()
62
-
63
-
64
- @pytest.fixture(scope="function", autouse=True)
65
- async def create_tables(connection_test):
66
- async with sessionmanager.connect() as connection:
67
- await sessionmanager.drop_all(connection)
68
- await sessionmanager.create_all(connection)
69
-
70
-
71
- @pytest.fixture(scope="function", autouse=True)
72
- async def session_override(app, connection_test):
73
- async def get_db_session_override():
74
- async with sessionmanager.session() as session:
75
- yield session
76
-
77
- app.dependency_overrides[get_db_session] = get_db_session_override
78
-
79
-
80
- @pytest.fixture(scope="function", autouse=True)
81
- async def get_db_session_fixture():
82
- async with sessionmanager.session() as session:
83
- yield session
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/pytest.ini DELETED
@@ -1,4 +0,0 @@
1
- [pytest]
2
- asyncio_mode = auto
3
- timeout = 60
4
- addopts = -vv --disable-warnings --durations=10 --durations-min=1.0
 
 
 
 
 
tests/test_income_statement.py DELETED
@@ -1,51 +0,0 @@
1
- from fastapi.testclient import TestClient
2
- import pytest
3
-
4
- from sqlalchemy.ext.asyncio import AsyncSession
5
- from app.model.transaction import Transaction
6
- from app.model.user import User
7
- from tests.utils import get_fake_transactions
8
-
9
- @pytest.mark.asyncio
10
- async def test_income_statement(client: TestClient, get_db_session_fixture: AsyncSession) -> None:
11
- session_override = get_db_session_fixture
12
-
13
- # 1. Create a user
14
- user = await User.create(session_override, name="user", email="email", hashed_password="password")
15
-
16
- # 2. Create a bunch of transactions
17
- fake_transactions = get_fake_transactions(user.id)
18
- await Transaction.bulk_create(session_override, fake_transactions)
19
-
20
- # 3. Create an income statement
21
- min_date = min(t.transaction_date for t in fake_transactions)
22
- max_date = max(t.transaction_date for t in fake_transactions)
23
-
24
- print(f"min_date: {min_date}, max_date: {max_date}")
25
- response = client.post(
26
- "/api/v1/income_statement",
27
- json={
28
- "user_id": 1,
29
- "date_from": str(min_date),
30
- "date_to": str(max_date),
31
- },
32
- )
33
-
34
- assert response.status_code == 200
35
-
36
- # 4. Verify that the income statement matches the transactions
37
- response = client.get(f"/api/v1/income_statement/user/1")
38
- print(response.json())
39
- assert response.status_code == 200
40
- assert response.json()[0].get("income")
41
- assert response.json()[0].get("expenses")
42
-
43
- report_id = response.json()[0].get("id")
44
-
45
- # # 5. Verify that the income statement can be retrieved
46
- if report_id is not None:
47
- response = client.get(f"/api/v1/income_statement/report/{report_id}")
48
- assert response.status_code == 200
49
- assert response.json().get("income")
50
- assert response.json().get("expenses")
51
- assert response.json().get("id") == report_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_transactions.py DELETED
@@ -1,25 +0,0 @@
1
- from fastapi.testclient import TestClient
2
- import pytest
3
-
4
- from app.model.transaction import Transaction
5
- from app.model.user import User
6
- from sqlalchemy.ext.asyncio import AsyncSession
7
- from tests.utils import get_fake_transactions
8
-
9
-
10
- @pytest.mark.asyncio
11
- async def test_transactions(client: TestClient, get_db_session_fixture: AsyncSession) -> None:
12
-
13
- session_override = get_db_session_fixture
14
-
15
- # 1. Create a user
16
- user = await User.create(session_override, name="user", email="email", hashed_password="password")
17
-
18
- # 2. Create a bunch of transactions
19
- fake_transactions = get_fake_transactions(user.id)
20
- await Transaction.bulk_create(session_override, fake_transactions)
21
-
22
- # 3. Verify that the transactions are returned
23
- response = client.get("/api/v1/transactions/1")
24
- assert response.status_code == 200
25
- assert len(response.json()) == 10