diff --git a/.github/workflows/docker-backend.yml b/.github/workflows/docker-backend.yml
new file mode 100644
index 0000000..77ea501
--- /dev/null
+++ b/.github/workflows/docker-backend.yml
@@ -0,0 +1,149 @@
+
+name: Build, Push and Deploy SLRmentor Backend
+
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - closed
+
+permissions:
+ contents: read
+
+concurrency:
+ group: slrmentor-backend-deploy
+ cancel-in-progress: false
+
+jobs:
+
+ # ==========================================
+ # PHASE 1: Generate Docker Image Version
+ # ==========================================
+
+ version:
+ name: "1. Generate Version"
+
+ if: >-
+ github.event.pull_request.merged == true &&
+ github.event.pull_request.head.ref == 'backend'
+
+ runs-on: ubuntu-latest
+
+ outputs:
+ version: ${{ steps.generate.outputs.version }}
+
+ steps:
+ - name: Generate version
+ id: generate
+ shell: bash
+ env:
+ COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }}
+ run: |
+ TIMESTAMP=$(date -u +"%Y%m%d.%H%M%S")
+ SHORT_SHA="${COMMIT_SHA:0:7}"
+ VERSION="v${TIMESTAMP}-${SHORT_SHA}"
+
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+ echo "Generated version: $VERSION"
+
+
+ # ==========================================
+ # PHASE 2: Build and Push Docker Image
+ # ==========================================
+
+ build-and-push:
+ name: "2. Build & Push Docker Image"
+
+ needs: version
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout merged commit
+ uses: actions/checkout@v6
+ with:
+ ref: ${{ github.event.pull_request.merge_commit_sha }}
+
+ - name: Exclude environment files
+ shell: bash
+ run: |
+ cat >> backend/.dockerignore <<'EOF'
+
+ # Environment files
+ .env
+ .env.*
+ **/.env
+ **/.env.*
+
+ # Local Python environment
+ .venv/
+ __pycache__/
+ *.pyc
+ EOF
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v4
+ with:
+ username: seallabuofc
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v7
+ with:
+ context: ./backend
+ file: ./backend/Dockerfile
+ platforms: linux/amd64
+ push: true
+ tags: |
+ seallabuofc/slrmentor-backend:${{ needs.version.outputs.version }}
+ seallabuofc/slrmentor-backend:latest
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ - name: Build summary
+ shell: bash
+ env:
+ VERSION: ${{ needs.version.outputs.version }}
+ run: |
+ echo "### Docker Image Published" >> "$GITHUB_STEP_SUMMARY"
+ echo "- Repository: seallabuofc/slrmentor-backend" >> "$GITHUB_STEP_SUMMARY"
+ echo "- Version: $VERSION" >> "$GITHUB_STEP_SUMMARY"
+ echo "- Latest: updated" >> "$GITHUB_STEP_SUMMARY"
+
+
+ # ==========================================
+ # PHASE 3: Deploy Image to Render
+ # ==========================================
+
+ deploy:
+ name: "3. Deploy to Render"
+
+ needs:
+ - version
+ - build-and-push
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Trigger Render deployment
+ shell: bash
+ env:
+ RENDER_DEPLOY_HOOK: ${{ secrets.RENDER_DEPLOY_HOOK }}
+ run: |
+ curl --fail --silent --show-error \
+ --retry 3 \
+ --request POST \
+ "$RENDER_DEPLOY_HOOK"
+
+ - name: Deployment summary
+ shell: bash
+ env:
+ VERSION: ${{ needs.version.outputs.version }}
+ run: |
+ echo "### SLRmentor Backend Deployment" >> "$GITHUB_STEP_SUMMARY"
+ echo "- Image: seallabuofc/slrmentor-backend" >> "$GITHUB_STEP_SUMMARY"
+ echo "- Version: $VERSION" >> "$GITHUB_STEP_SUMMARY"
+ echo "- Render deployment: triggered" >> "$GITHUB_STEP_SUMMARY"
diff --git a/Bruno_SLRmentor/.gitignore b/Bruno_SLRmentor/.gitignore
new file mode 100644
index 0000000..e19311f
--- /dev/null
+++ b/Bruno_SLRmentor/.gitignore
@@ -0,0 +1,9 @@
+# Secrets
+.env*
+
+# Dependencies
+node_modules
+
+# OS files
+.DS_Store
+Thumbs.db
\ No newline at end of file
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get Chat history.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Chat history.yml
new file mode 100644
index 0000000..448bb9c
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Chat history.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test Get Chat history
+ type: http
+ seq: 4
+
+http:
+ method: POST
+ url: https://api.slrmentor.ca/getchathistory
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"2e8b4de7\"\r
+
+ }\r
+
+ // 1fc49d43"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get Criteria Chat history.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Criteria Chat history.yml
new file mode 100644
index 0000000..7611097
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Criteria Chat history.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Get Criteria Chat history
+ type: http
+ seq: 8
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/getcriteriachathistory
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"2e8b4de7\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get Mentor history.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Mentor history.yml
new file mode 100644
index 0000000..54e573e
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get Mentor history.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Get Mentor history
+ type: http
+ seq: 7
+
+http:
+ method: POST
+ url: https://api.slrmentor.ca/getmentorchathistory
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"2e8b4de7\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context.yml
new file mode 100644
index 0000000..4aa0784
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Get System Context
+ type: http
+ seq: 5
+
+http:
+ method: POST
+ url: https://api.slrmentor.ca/getsystemcontext
+ body:
+ type: json
+ data: " {\r
+
+ \ \"hash_plain_text\": \"624a1591\"\r
+
+ \ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context_1.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context_1.yml
new file mode 100644
index 0000000..e8716b9
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Get System Context_1.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Get System Context_1
+ type: http
+ seq: 14
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/getsystemcontext
+ body:
+ type: json
+ data: " {\r
+
+ \ \"hash_plain_text\": \"c16ada85\"\r
+
+ \ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Mentor.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Mentor.yml
new file mode 100644
index 0000000..9a74203
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Mentor.yml
@@ -0,0 +1,79 @@
+info:
+ name: Test Mentor
+ type: http
+ seq: 12
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/mentor
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"2e8b4de7\",\r
+
+ \ \"user_message\": \"What is a systematic map?\"\r
+
+ \r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test Response.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test Response.yml
new file mode 100644
index 0000000..fe85a96
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test Response.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Response
+ type: http
+ seq: 1
+
+http:
+ method: GET
+ url: https://api.slrmentor.ca/
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Status code is 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Validate that the response is json object\r
+
+ test(\"Response is a json object\", function () {\r
+
+ \ expect(res.getBody()).to.be.an('object');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has an arg object\r
+
+ test(\"Response to have 'arg' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('args');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has headers object\r
+
+ test(\"Response to have 'headers' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('headers');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has url property\r
+
+ test(\"Response to have 'url' property\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('url');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: |-
+ The tests in this request validate that:
+
+ - the response code is 200
+ - the response is a JSON object
+ - the response has an 'arg' object
+ - the response has 'headers' object
+ - the response has a 'URL' property
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test conversion formats.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test conversion formats.yml
new file mode 100644
index 0000000..53fc6e7
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test conversion formats.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test conversion formats
+ type: http
+ seq: 2
+
+http:
+ method: GET
+ url: https://api.slrmentor.ca/conversionformats
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Status code is 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Validate that the response is json object\r
+
+ test(\"Response is a json object\", function () {\r
+
+ \ expect(res.getBody()).to.be.an('object');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has an arg object\r
+
+ test(\"Response to have 'arg' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('args');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has headers object\r
+
+ test(\"Response to have 'headers' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('headers');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has url property\r
+
+ test(\"Response to have 'url' property\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('url');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: |-
+ The tests in this request validate that:
+
+ - the response code is 200
+ - the response is a JSON object
+ - the response has an 'arg' object
+ - the response has 'headers' object
+ - the response has a 'URL' property
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test convert string.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test convert string.yml
new file mode 100644
index 0000000..8c2e017
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test convert string.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test convert string
+ type: http
+ seq: 13
+
+http:
+ method: POST
+ url: https://api.slrmentor.ca/convertsearchstring
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"8e75ffec\",\r
+
+ \ \"conversion_format\": \"Google Scholar\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test criteria.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test criteria.yml
new file mode 100644
index 0000000..7d84444
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test criteria.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test criteria
+ type: http
+ seq: 10
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/criteria
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"2e8b4de7\",\r
+
+ \ \"user_message\": \"the goal of my research is to see the effects of internet usage on young children?\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test generate hash.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test generate hash.yml
new file mode 100644
index 0000000..bb14be8
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test generate hash.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test generate hash
+ type: http
+ seq: 3
+
+http:
+ method: POST
+ url: https://api.slrmentor.ca/createchat
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Status code is 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Validate that the response is json object\r
+
+ test(\"Response is a json object\", function () {\r
+
+ \ expect(res.getBody()).to.be.an('object');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has an arg object\r
+
+ test(\"Response to have 'arg' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('args');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has headers object\r
+
+ test(\"Response to have 'headers' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('headers');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has url property\r
+
+ test(\"Response to have 'url' property\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('url');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: |-
+ The tests in this request validate that:
+
+ - the response code is 200
+ - the response is a JSON object
+ - the response has an 'arg' object
+ - the response has 'headers' object
+ - the response has a 'URL' property
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test prompt.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test prompt.yml
new file mode 100644
index 0000000..8d38620
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test prompt.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test prompt
+ type: http
+ seq: 9
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/prompt
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"2e8b4de7\",\r
+
+ \ \"user_message\": \"what are the effects of phone on young children? This is my reserach question\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test ragquery.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test ragquery.yml
new file mode 100644
index 0000000..4b26c3d
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test ragquery.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test ragquery
+ type: http
+ seq: 11
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/criteria
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"2e8b4de7\",\r
+
+ \ \"user_message\": \"the goal of my research is to see the effects of internet usage on young children?\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context.yml
new file mode 100644
index 0000000..011621b
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test set System Context
+ type: http
+ seq: 6
+
+http:
+ method: POST
+ url: https://api.slrmentor.ca/setsystemcontext
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\",\r
+
+ \ \"system_context\": \"skibi\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context_1.yml b/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context_1.yml
new file mode 100644
index 0000000..7a7f5ef
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/Test set System Context_1.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test set System Context_1
+ type: http
+ seq: 15
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/setsystemcontext
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\",\r
+
+ \ \"system_context\": \"test\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Cloud Testing/opencollection.yml b/Bruno_SLRmentor/collections/Cloud Testing/opencollection.yml
new file mode 100644
index 0000000..4397fc1
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Cloud Testing/opencollection.yml
@@ -0,0 +1,43 @@
+opencollection: 1.0.0
+
+info:
+ name: Cloud Testing
+
+request:
+ variables:
+ - name: baseUrl
+ value: https://postman-echo.com
+
+docs:
+ content: |-
+ # About this collection
+
+ This collection helps you set up contract tests to ensure that two separate systems are compatible and can communicate with one another.
+
+ ## **Using the collection**
+
+ **Step 1:** Send the sample requests and view the response and test results.
+
+ **Step 2:** Replace the sample request URLs with your desired API endpoints.
+
+ **Step 3:** Customize the tests in the "Tests" tab if needed. Don't forget to save your changes.
+
+ ### Resources
+
+ [Scripting in Postman](https://learning.postman.com/docs/writing-scripts/intro-to-scripts/)
+
+ [Test script examples](https://learning.postman.com/docs/writing-scripts/script-references/test-examples/)
+
+ [Postman Sandbox API reference](https://learning.postman.com/docs/sending-requests/grpc/postman-sandbox-api/#writing-assertions)
+
+ [Using the Collection Runner](https://learning.postman.com/docs/collections/running-collections/intro-to-collection-runs/)
+
+ [
+ ](https://postman.postman.co/documentation/24552533-5bead607-0197-4e1d-87d4-3160bc7631c1?entity=&branch=&version=)
+ type: text/markdown
+bundled: false
+extensions:
+ bruno:
+ ignore:
+ - node_modules
+ - .git
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Get Chat history.yml b/Bruno_SLRmentor/collections/Route Testing/Test Get Chat history.yml
new file mode 100644
index 0000000..13dc2c3
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test Get Chat history.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test Get Chat history
+ type: http
+ seq: 4
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/getchathistory
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\"\r
+
+ }\r
+
+ // 1fc49d43"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Get Criteria Chat history.yml b/Bruno_SLRmentor/collections/Route Testing/Test Get Criteria Chat history.yml
new file mode 100644
index 0000000..2fc7bbf
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test Get Criteria Chat history.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Get Criteria Chat history
+ type: http
+ seq: 8
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/getcriteriachathistory
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Get Mentor history.yml b/Bruno_SLRmentor/collections/Route Testing/Test Get Mentor history.yml
new file mode 100644
index 0000000..15a459a
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test Get Mentor history.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Get Mentor history
+ type: http
+ seq: 7
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/getmentorchathistory
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Get System Context.yml b/Bruno_SLRmentor/collections/Route Testing/Test Get System Context.yml
new file mode 100644
index 0000000..a792243
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test Get System Context.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Get System Context
+ type: http
+ seq: 5
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/getsystemcontext
+ body:
+ type: json
+ data: " {\r
+
+ \ \"hash_plain_text\": \"c16ada85\"\r
+
+ \ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Mentor.yml b/Bruno_SLRmentor/collections/Route Testing/Test Mentor.yml
new file mode 100644
index 0000000..91b09ed
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test Mentor.yml
@@ -0,0 +1,79 @@
+info:
+ name: Test Mentor
+ type: http
+ seq: 12
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/mentor
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\",\r
+
+ \ \"user_message\": \"whats the structure of a systematic literacture review\"\r
+
+ \r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test Response.yml b/Bruno_SLRmentor/collections/Route Testing/Test Response.yml
new file mode 100644
index 0000000..1680b4f
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test Response.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test Response
+ type: http
+ seq: 1
+
+http:
+ method: GET
+ url: http://127.0.0.1:5000
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Status code is 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Validate that the response is json object\r
+
+ test(\"Response is a json object\", function () {\r
+
+ \ expect(res.getBody()).to.be.an('object');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has an arg object\r
+
+ test(\"Response to have 'arg' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('args');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has headers object\r
+
+ test(\"Response to have 'headers' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('headers');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has url property\r
+
+ test(\"Response to have 'url' property\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('url');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: |-
+ The tests in this request validate that:
+
+ - the response code is 200
+ - the response is a JSON object
+ - the response has an 'arg' object
+ - the response has 'headers' object
+ - the response has a 'URL' property
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test conversion formats.yml b/Bruno_SLRmentor/collections/Route Testing/Test conversion formats.yml
new file mode 100644
index 0000000..4a76d8f
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test conversion formats.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test conversion formats
+ type: http
+ seq: 2
+
+http:
+ method: GET
+ url: http://127.0.0.1:5000/conversionformats
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Status code is 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Validate that the response is json object\r
+
+ test(\"Response is a json object\", function () {\r
+
+ \ expect(res.getBody()).to.be.an('object');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has an arg object\r
+
+ test(\"Response to have 'arg' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('args');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has headers object\r
+
+ test(\"Response to have 'headers' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('headers');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has url property\r
+
+ test(\"Response to have 'url' property\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('url');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: |-
+ The tests in this request validate that:
+
+ - the response code is 200
+ - the response is a JSON object
+ - the response has an 'arg' object
+ - the response has 'headers' object
+ - the response has a 'URL' property
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test convert string.yml b/Bruno_SLRmentor/collections/Route Testing/Test convert string.yml
new file mode 100644
index 0000000..bbe60d9
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test convert string.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test convert string
+ type: http
+ seq: 13
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/convertsearchstring
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"8e75ffec\",\r
+
+ \ \"conversion_format\": \"IEEE Xplore\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test criteria.yml b/Bruno_SLRmentor/collections/Route Testing/Test criteria.yml
new file mode 100644
index 0000000..f597ef0
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test criteria.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test criteria
+ type: http
+ seq: 10
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/criteria
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\",\r
+
+ \ \"user_message\": \"My research is about the effect of phones on teenagers\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test generate hash.yml b/Bruno_SLRmentor/collections/Route Testing/Test generate hash.yml
new file mode 100644
index 0000000..bb14be8
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test generate hash.yml
@@ -0,0 +1,75 @@
+info:
+ name: Test generate hash
+ type: http
+ seq: 3
+
+http:
+ method: POST
+ url: https://api.slrmentor.ca/createchat
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Status code is 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Validate that the response is json object\r
+
+ test(\"Response is a json object\", function () {\r
+
+ \ expect(res.getBody()).to.be.an('object');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has an arg object\r
+
+ test(\"Response to have 'arg' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('args');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has headers object\r
+
+ test(\"Response to have 'headers' object\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('headers');\r
+
+ });\r
+
+ \r
+
+ // Validate that the response has url property\r
+
+ test(\"Response to have 'url' property\", function () {\r
+
+ \ expect(res.getBody()).to.have.property('url');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: |-
+ The tests in this request validate that:
+
+ - the response code is 200
+ - the response is a JSON object
+ - the response has an 'arg' object
+ - the response has 'headers' object
+ - the response has a 'URL' property
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test prompt.yml b/Bruno_SLRmentor/collections/Route Testing/Test prompt.yml
new file mode 100644
index 0000000..3470c71
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test prompt.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test prompt
+ type: http
+ seq: 9
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/prompt
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\",\r
+
+ \ \"user_message\": \"what is a search string?\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test ragquery.yml b/Bruno_SLRmentor/collections/Route Testing/Test ragquery.yml
new file mode 100644
index 0000000..36b372c
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test ragquery.yml
@@ -0,0 +1,74 @@
+info:
+ name: Test ragquery
+ type: http
+ seq: 11
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/ragquery
+ body:
+ type: json
+ data: |-
+ {
+ "query": "What is a systematic literacture review?"
+ }
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/Test set System Context.yml b/Bruno_SLRmentor/collections/Route Testing/Test set System Context.yml
new file mode 100644
index 0000000..a3f8d42
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/Test set System Context.yml
@@ -0,0 +1,77 @@
+info:
+ name: Test set System Context
+ type: http
+ seq: 6
+
+http:
+ method: POST
+ url: http://127.0.0.1:5000/setsystemcontext
+ body:
+ type: json
+ data: "{\r
+
+ \ \"hash_plain_text\": \"624a1591\",\r
+
+ \ \"system_context\": \"test\"\r
+
+ }"
+ auth: inherit
+
+runtime:
+ scripts:
+ - type: after-response
+ code: "// Validate that the response code should be 200\r
+
+ test(\"Response Code should be 200\", function () {\r
+
+ \ expect(res.getStatus()).to.equal(200);\r
+
+ });\r
+
+ \r
+
+ // Run validations on response headers like Content-Type\r
+
+ test(\"Content-Type should be JSON\", function () {\r
+
+ \ expect(res.getHeader('Content-Type')).to.eql('application/json; charset=utf-8');\r
+
+ });\r
+
+ \r
+
+ const json = res.getBody();\r
+
+ \r
+
+ // The response body, including individual attributes can be evaluated for correctness\r
+
+ test(\"`args` should contain the correct query params\", function () {\r
+
+ \ expect(json.args).to.be.an('object');\r
+
+ \ expect(json.args.foo).to.eql('bar');\r
+
+ \ expect(json.args.baz).to.eql('value');\r
+
+ });\r
+
+ \r
+
+ // Non-trivial data types like Dates can also be tested against\r
+
+ test(\"Date in the header should be valid\", function () {\r
+
+ \ const parsedDate = new Date(res.getHeader('Date'));\r
+
+ \ expect(parsedDate).to.be.a('date');\r
+
+ });"
+
+settings:
+ encodeUrl: true
+ timeout: 0
+ followRedirects: true
+ maxRedirects: 5
+
+docs: The tests in the request validate the response code and the data returned in the response is the same as the one being sent.
diff --git a/Bruno_SLRmentor/collections/Route Testing/opencollection.yml b/Bruno_SLRmentor/collections/Route Testing/opencollection.yml
new file mode 100644
index 0000000..048cc1e
--- /dev/null
+++ b/Bruno_SLRmentor/collections/Route Testing/opencollection.yml
@@ -0,0 +1,43 @@
+opencollection: 1.0.0
+
+info:
+ name: Route Testing
+
+request:
+ variables:
+ - name: baseUrl
+ value: https://postman-echo.com
+
+docs:
+ content: |-
+ # About this collection
+
+ This collection helps you set up contract tests to ensure that two separate systems are compatible and can communicate with one another.
+
+ ## **Using the collection**
+
+ **Step 1:** Send the sample requests and view the response and test results.
+
+ **Step 2:** Replace the sample request URLs with your desired API endpoints.
+
+ **Step 3:** Customize the tests in the "Tests" tab if needed. Don't forget to save your changes.
+
+ ### Resources
+
+ [Scripting in Postman](https://learning.postman.com/docs/writing-scripts/intro-to-scripts/)
+
+ [Test script examples](https://learning.postman.com/docs/writing-scripts/script-references/test-examples/)
+
+ [Postman Sandbox API reference](https://learning.postman.com/docs/sending-requests/grpc/postman-sandbox-api/#writing-assertions)
+
+ [Using the Collection Runner](https://learning.postman.com/docs/collections/running-collections/intro-to-collection-runs/)
+
+ [
+ ](https://postman.postman.co/documentation/24552533-5bead607-0197-4e1d-87d4-3160bc7631c1?entity=&branch=&version=)
+ type: text/markdown
+bundled: false
+extensions:
+ bruno:
+ ignore:
+ - node_modules
+ - .git
diff --git a/Bruno_SLRmentor/workspace.yml b/Bruno_SLRmentor/workspace.yml
new file mode 100644
index 0000000..d010ad7
--- /dev/null
+++ b/Bruno_SLRmentor/workspace.yml
@@ -0,0 +1,14 @@
+opencollection: 1.0.0
+info:
+ name: "SLRmentor"
+ type: workspace
+
+collections:
+ - name: "Cloud Testing"
+ path: "collections/Cloud Testing"
+ - name: "Route Testing"
+ path: "collections/Route Testing"
+
+specs:
+
+docs: ''
diff --git a/RAG/.gitignore b/RAG/.gitignore
new file mode 100644
index 0000000..11edc3b
--- /dev/null
+++ b/RAG/.gitignore
@@ -0,0 +1,6 @@
+.venv
+venv
+__pycache__
+.env
+.venv/
+__pycache__/
\ No newline at end of file
diff --git a/RAG/1_testconnection.py b/RAG/1_testconnection.py
deleted file mode 100644
index 07ab2b1..0000000
--- a/RAG/1_testconnection.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from pymongo.mongo_client import MongoClient
-from pymongo.server_api import ServerApi
-from dotenv import load_dotenv
-import os
-
-# Load environment variables from .env
-load_dotenv()
-
-# Get the URI from the environment
-uri = os.getenv("MONGODB_URI")
-
-# Create a new client and connect to the server
-client = MongoClient(uri, server_api=ServerApi('1'))
-
-# Send a ping to confirm a successful connection
-try:
- client.admin.command('ping')
- print("✅ Pinged your deployment. You successfully connected to MongoDB!")
-except Exception as e:
- print("❌ Connection failed:", e)
diff --git a/RAG/RagPipelineTest.py b/RAG/RagPipelineTest.py
deleted file mode 100644
index 62e86a5..0000000
--- a/RAG/RagPipelineTest.py
+++ /dev/null
@@ -1,132 +0,0 @@
-import os
-import pprint
-from dotenv import load_dotenv
-from pathlib import Path
-from pymongo import MongoClient
-
-# LangChain modules (1.x)
-from langchain_community.embeddings import HuggingFaceEmbeddings
-from langchain_community.document_loaders import PyPDFLoader
-from langchain_community.llms import GPT4All
-from langchain_community.vectorstores import MongoDBAtlasVectorSearch
-from langchain_text_splitters import RecursiveCharacterTextSplitter
-from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
-from langchain_core.prompts import PromptTemplate
-from langchain_core.output_parsers import StrOutputParser
-from langchain_core.runnables import RunnablePassthrough
-
-
-# -----------------------------
-# 1. Load environment & MongoDB URI
-# -----------------------------
-load_dotenv()
-MONGODB_URI = os.getenv("MONGO_URI")
-if not MONGODB_URI:
- raise ValueError("❌ MONGO_URI not found in .env file")
-
-DB_NAMESPACE = "SLRMentor.document_rag" # your database.collection
-
-# -----------------------------
-# 2. Load embedding model
-# -----------------------------
-print("🔹 Loading embedding model...")
-embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
-
-# -----------------------------
-# 3. Connect to MongoDB Atlas vector store
-# -----------------------------
-print("🔹 Connecting to MongoDB Atlas...")
-vector_store = MongoDBAtlasVectorSearch.from_connection_string(
- connection_string=MONGODB_URI,
- namespace=DB_NAMESPACE,
- embedding=embedding_model,
- index_name="vector_index"
-)
-
-# -----------------------------
-# 4. Load PDFs and split into chunks
-# -----------------------------
-docs_folder = Path("documents")
-all_docs = []
-
-for pdf_file in docs_folder.glob("*.pdf"):
- print(f"📄 Loading PDF: {pdf_file}")
- loader = PyPDFLoader(str(pdf_file))
- data = loader.load()
-
- print(f"✂️ Splitting PDF into chunks...")
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)
- chunks = text_splitter.split_documents(data)
- all_docs.extend(chunks)
-
-print(f"📄 Total chunks to process: {len(all_docs)}")
-
-# -----------------------------
-# 5. Upload vectors only if collection empty
-# -----------------------------
-db_name, coll_name = DB_NAMESPACE.split(".")
-mongo_client = MongoClient(MONGODB_URI)
-collection = mongo_client[db_name][coll_name]
-
-existing_docs = collection.count_documents({})
-
-if existing_docs > 0:
- print(f"⚠️ Collection already contains {existing_docs} documents — skipping upload.")
-else:
- print("📥 Adding documents to MongoDB vector store...")
- vector_store.add_documents(all_docs)
- print("✅ Documents added successfully.")
- vector_store.create_vector_search_index(dimensions=384) # MiniLM dimensions
- print("🧱 Vector search index created.")
-
-# -----------------------------
-# 6. Load local LLM
-# -----------------------------
-local_model_path = "./mistral-7b-openorca.gguf2.Q4_0.gguf"
-print(f"🤖 Loading local model from {local_model_path}...")
-callbacks = [StreamingStdOutCallbackHandler()]
-llm = GPT4All(model=local_model_path, callbacks=callbacks, verbose=True, device="cpu")
-
-# -----------------------------
-# 7. Create RAG chain
-# -----------------------------
-retriever = vector_store.as_retriever()
-
-template = """
-Use the following pieces of context to answer the question at the end.
-
-{context}
-
-Question: {question}
-"""
-custom_rag_prompt = PromptTemplate.from_template(template)
-
-def format_docs(docs):
- return "\n\n".join(doc.page_content for doc in docs)
-
-rag_chain = (
- {"context": retriever | format_docs, "question": RunnablePassthrough()}
- | custom_rag_prompt
- | llm
- | StrOutputParser()
-)
-
-# -----------------------------
-# 8. Interactive question loop
-# -----------------------------
-print("\n💬 Ready! Ask a question about the documents (type 'exit' to quit)\n")
-
-while True:
- question = input("Question: ").strip()
- if question.lower() in ["exit", "quit"]:
- print("👋 Exiting.")
- break
-
- print("\n🧠 Thinking...\n")
- answer = rag_chain.invoke(question)
- print("\nAnswer:", answer)
-
- documents = retriever.invoke(question)
- print("\n📚 Source documents:")
- pprint.pprint(documents[:2]) # show first 2
- print("-" * 80)
diff --git a/RAG/env_linux.yml b/RAG/env_linux.yml
deleted file mode 100644
index 399d947..0000000
--- a/RAG/env_linux.yml
+++ /dev/null
@@ -1,204 +0,0 @@
-name: seallapp
-channels:
- - defaults
-dependencies:
- - _libgcc_mutex=0.1=main
- - _openmp_mutex=5.1=1_gnu
- - bzip2=1.0.8=h5eee18b_6
- - ca-certificates=2025.7.15=h06a4308_0
- - expat=2.7.1=h6a678d5_0
- - ld_impl_linux-64=2.40=h12ee557_0
- - libffi=3.4.4=h6a678d5_1
- - libgcc-ng=11.2.0=h1234567_1
- - libgomp=11.2.0=h1234567_1
- - libstdcxx-ng=11.2.0=h1234567_1
- - libuuid=1.41.5=h5eee18b_0
- - libxcb=1.17.0=h9b100fa_0
- - ncurses=6.5=h7934f7d_0
- - openssl=3.0.17=h5eee18b_0
- - pip=25.1=pyhc872135_2
- - pthread-stubs=0.3=h0ce48e5_1
- - python=3.10.18=h1a3bd86_0
- - readline=8.3=hc2a1206_0
- - setuptools=78.1.1=py310h06a4308_0
- - sqlite=3.50.2=hb25bd0a_1
- - tk=8.6.15=h54e0aa7_0
- - wheel=0.45.1=py310h06a4308_0
- - xorg-libx11=1.8.12=h9b100fa_1
- - xorg-libxau=1.0.12=h9b100fa_0
- - xorg-libxdmcp=1.1.5=h9b100fa_0
- - xorg-xorgproto=2024.1=h5eee18b_1
- - xz=5.6.4=h5eee18b_1
- - zlib=1.2.13=h5eee18b_1
- - pip:
- - aiohappyeyeballs==2.6.1
- - aiohttp==3.12.13
- - aiosignal==1.4.0
- - alabaster==1.0.0
- - annotated-types==0.7.0
- - anyio==4.9.0
- - arrow==1.3.0
- - async-timeout==4.0.3
- - attrs==25.3.0
- - babel==2.17.0
- - bcrypt==4.3.0
- - beautifulsoup4==4.13.4
- - bibtexparser==1.4.3
- - blinker==1.9.0
- - cachetools==5.5.2
- - certifi==2025.4.26
- - cffi==1.17.1
- - charset-normalizer==3.4.2
- - click==8.2.1
- - colorama==0.4.6
- - dataclasses-json==0.6.7
- - deprecated==1.2.18
- - distro==1.9.0
- - dnspython==1.16.0
- - docutils==0.21.2
- - dotenv==0.9.9
- - et-xmlfile==2.0.0
- - exceptiongroup==1.3.0
- - fake-useragent==2.2.0
- - filelock==3.20.0
- - flask==3.1.1
- - flask-cors==6.0.0
- - flask-pymongo==3.0.1
- - free-proxy==1.1.3
- - frozenlist==1.7.0
- - fsspec==2025.9.0
- - google==3.0.0
- - google-ai-generativelanguage==0.6.15
- - google-api-core==2.25.1rc0
- - google-api-python-client==2.172.0
- - google-auth==2.40.3
- - google-auth-httplib2==0.2.0
- - google-genai==1.19.0
- - google-generativeai==0.8.5
- - googleapis-common-protos==1.70.0
- - gpt4all==2.8.2
- - greenlet==3.2.4
- - grpcio==1.73.0
- - grpcio-status==1.71.0
- - gunicorn==23.0.0
- - h11==0.16.0
- - hf-xet==1.1.10
- - httpcore==1.0.9
- - httplib2==0.22.0
- - httpx==0.28.1
- - httpx-sse==0.4.3
- - huggingface-hub==0.35.3
- - idna==3.10
- - imagesize==1.4.1
- - itsdangerous==2.2.0
- - jinja2==3.1.6
- - jiter==0.10.0
- - joblib==1.5.2
- - jsonpatch==1.33
- - jsonpointer==3.0.0
- - langchain==0.3.27
- - langchain-community==0.3.31
- - langchain-core==0.3.79
- - langchain-huggingface==0.3.1
- - langchain-mongodb==0.7.0
- - langchain-text-splitters==0.3.11
- - langsmith==0.4.34
- - lark==1.3.0
- - lxml==6.0.0
- - markupsafe==3.0.2
- - marshmallow==3.26.1
- - mpmath==1.3.0
- - multidict==6.6.3
- - mypy-extensions==1.1.0
- - networkx==3.4.2
- - numpy==2.2.6
- - nvidia-cublas-cu12==12.8.4.1
- - nvidia-cuda-cupti-cu12==12.8.90
- - nvidia-cuda-nvrtc-cu12==12.8.93
- - nvidia-cuda-runtime-cu12==12.8.90
- - nvidia-cudnn-cu12==9.10.2.21
- - nvidia-cufft-cu12==11.3.3.83
- - nvidia-cufile-cu12==1.13.1.3
- - nvidia-curand-cu12==10.3.9.90
- - nvidia-cusolver-cu12==11.7.3.90
- - nvidia-cusparse-cu12==12.5.8.93
- - nvidia-cusparselt-cu12==0.7.1
- - nvidia-nccl-cu12==2.27.3
- - nvidia-nvjitlink-cu12==12.8.93
- - nvidia-nvtx-cu12==12.8.90
- - openai==1.93.0
- - openpyxl==3.1.5
- - orjson==3.11.3
- - outcome==1.3.0.post0
- - packaging==25.0
- - pandas==2.3.0
- - pillow==11.3.0
- - propcache==0.3.2
- - proto-plus==1.26.1
- - protobuf==5.29.5
- - pyasn1==0.6.1
- - pyasn1-modules==0.4.2
- - pycparser==2.22
- - pycurl==7.45.6
- - pydantic==2.11.5
- - pydantic-core==2.33.2
- - pydantic-settings==2.11.0
- - pygments==2.19.2
- - pymongo==4.15.3
- - pyparsing==3.2.3
- - pypdf==6.1.1
- - pysocks==1.7.1
- - python-dateutil==2.9.0.post0
- - python-dotenv==1.1.0
- - pytz==2025.2
- - pyyaml==6.0.3
- - regex==2025.9.18
- - requests==2.32.5
- - requests-toolbelt==1.0.0
- - rsa==4.9.1
- - safetensors==0.6.2
- - scholarly==1.7.11
- - scikit-learn==1.7.2
- - scipy==1.15.3
- - selenium==4.34.0
- - sentence-transformers==5.1.1
- - six==1.17.0
- - sniffio==1.3.1
- - snowballstemmer==3.0.1
- - sortedcontainers==2.4.0
- - soupsieve==2.7
- - sphinx==8.1.3
- - sphinx-rtd-theme==3.0.2
- - sphinxcontrib-applehelp==2.0.0
- - sphinxcontrib-devhelp==2.0.0
- - sphinxcontrib-htmlhelp==2.1.0
- - sphinxcontrib-jquery==4.1
- - sphinxcontrib-jsmath==1.0.1
- - sphinxcontrib-qthelp==2.0.0
- - sphinxcontrib-serializinghtml==2.0.0
- - sqlalchemy==2.0.44
- - sympy==1.14.0
- - tenacity==9.1.2
- - threadpoolctl==3.6.0
- - tokenizers==0.22.1
- - tomli==2.2.1
- - torch==2.8.0
- - tqdm==4.67.1
- - transformers==4.57.0
- - trio==0.30.0
- - trio-websocket==0.12.2
- - triton==3.4.0
- - types-python-dateutil==2.9.0.20250516
- - typing-extensions==4.14.0
- - typing-inspect==0.9.0
- - typing-inspection==0.4.1
- - tzdata==2025.2
- - uritemplate==4.2.0
- - urllib3==2.4.0
- - websocket-client==1.8.0
- - websockets==15.0.1
- - werkzeug==3.1.3
- - wrapt==1.17.2
- - wsproto==1.2.0
- - yarl==1.20.1
- - zstandard==0.25.0
diff --git a/RAG/env_windows.yml b/RAG/env_windows.yml
deleted file mode 100644
index 0940288..0000000
--- a/RAG/env_windows.yml
+++ /dev/null
@@ -1,190 +0,0 @@
-name: seallapp
-channels:
- - defaults
- - https://repo.anaconda.com/pkgs/main
- - https://repo.anaconda.com/pkgs/r
- - https://repo.anaconda.com/pkgs/msys2
-dependencies:
- - bzip2=1.0.8=h2bbff1b_6
- - ca-certificates=2025.9.9=haa95532_0
- - expat=2.7.1=h8ddb27b_0
- - libffi=3.4.4=hd77b12b_1
- - libzlib=1.3.1=h02ab6af_0
- - openssl=3.0.18=h543e019_0
- - pip=25.2=pyhc872135_1
- - python=3.10.19=h981015d_0
- - setuptools=80.9.0=py310haa95532_0
- - sqlite=3.50.2=hda9a48d_1
- - tk=8.6.15=hf199647_0
- - ucrt=10.0.22621.0=haa95532_0
- - vc=14.3=h2df5915_10
- - vc14_runtime=14.44.35208=h4927774_10
- - vs2015_runtime=14.44.35208=ha6b5a95_10
- - wheel=0.45.1=py310haa95532_0
- - xz=5.6.4=h4754444_1
- - zlib=1.3.1=h02ab6af_0
- - pip:
- - aiohappyeyeballs==2.6.1
- - aiohttp==3.12.13
- - aiosignal==1.4.0
- - alabaster==1.0.0
- - annotated-types==0.7.0
- - anyio==4.9.0
- - arrow==1.3.0
- - async-timeout==4.0.3
- - attrs==25.3.0
- - babel==2.17.0
- - bcrypt==4.3.0
- - beautifulsoup4==4.13.4
- - bibtexparser==1.4.3
- - blinker==1.9.0
- - cachetools==5.5.2
- - certifi==2025.4.26
- - cffi==1.17.1
- - charset-normalizer==3.4.2
- - click==8.2.1
- - colorama==0.4.6
- - dataclasses-json==0.6.7
- - deprecated==1.2.18
- - distro==1.9.0
- - dnspython==2.7.0
- - docutils==0.21.2
- - dotenv==0.9.9
- - et-xmlfile==2.0.0
- - exceptiongroup==1.3.0
- - fake-useragent==2.2.0
- - filelock==3.20.0
- - flask==3.1.1
- - flask-cors==6.0.0
- - flask-pymongo==3.0.1
- - free-proxy==1.1.3
- - frozenlist==1.7.0
- - fsspec==2025.9.0
- - google==3.0.0
- - google-ai-generativelanguage==0.6.15
- - google-api-core==2.25.1rc0
- - google-api-python-client==2.172.0
- - google-auth==2.40.3
- - google-auth-httplib2==0.2.0
- - google-genai==1.19.0
- - google-generativeai==0.8.5
- - googleapis-common-protos==1.70.0
- - gpt4all==2.8.2
- - greenlet==3.2.4
- - grpcio==1.73.0
- - grpcio-status==1.71.0
- - gunicorn==23.0.0
- - h11==0.16.0
- - httpcore==1.0.9
- - httplib2==0.22.0
- - httpx==0.28.1
- - httpx-sse==0.4.3
- - huggingface-hub==0.36.0
- - idna==3.10
- - imagesize==1.4.1
- - itsdangerous==2.2.0
- - jinja2==3.1.6
- - jiter==0.10.0
- - joblib==1.5.2
- - jsonpatch==1.33
- - jsonpointer==3.0.0
- - langchain==1.0.2
- - langchain-classic==1.0.0
- - langchain-community==0.4
- - langchain-core==1.0.1
- - langchain-huggingface==1.0.0
- - langchain-mongodb==0.7.1
- - langchain-text-splitters==1.0.0
- - langchainhub==0.1.21
- - langgraph==1.0.1
- - langgraph-checkpoint==3.0.0
- - langgraph-prebuilt==1.0.1
- - langgraph-sdk==0.2.9
- - langsmith==0.4.38
- - lark==1.3.0
- - lxml==6.0.0
- - markupsafe==3.0.2
- - marshmallow==3.26.1
- - mpmath==1.3.0
- - multidict==6.6.3
- - mypy-extensions==1.1.0
- - networkx==3.4.2
- - numpy==2.2.6
- - openai==1.93.0
- - openpyxl==3.1.5
- - orjson==3.11.4
- - ormsgpack==1.11.0
- - outcome==1.3.0.post0
- - packaging==24.2
- - pandas==2.3.0
- - pillow==12.0.0
- - propcache==0.3.2
- - proto-plus==1.26.1
- - protobuf==5.29.5
- - pyasn1==0.6.1
- - pyasn1-modules==0.4.2
- - pycparser==2.22
- - pycurl==7.45.6
- - pydantic==2.11.5
- - pydantic-core==2.33.2
- - pydantic-settings==2.11.0
- - pygments==2.19.2
- - pymongo==4.15.3
- - pyparsing==3.2.3
- - pypdf==6.1.3
- - pysocks==1.7.1
- - python-dateutil==2.9.0.post0
- - python-dotenv==1.1.0
- - pytz==2025.2
- - pyyaml==6.0.3
- - regex==2025.10.23
- - requests==2.32.5
- - requests-toolbelt==1.0.0
- - rsa==4.9.1
- - safetensors==0.6.2
- - scholarly==1.7.11
- - scikit-learn==1.7.2
- - scipy==1.15.3
- - selenium==4.34.0
- - sentence-transformers==5.1.2
- - six==1.17.0
- - sniffio==1.3.1
- - snowballstemmer==3.0.1
- - sortedcontainers==2.4.0
- - soupsieve==2.7
- - sphinx==8.1.3
- - sphinx-rtd-theme==3.0.2
- - sphinxcontrib-applehelp==2.0.0
- - sphinxcontrib-devhelp==2.0.0
- - sphinxcontrib-htmlhelp==2.1.0
- - sphinxcontrib-jquery==4.1
- - sphinxcontrib-jsmath==1.0.1
- - sphinxcontrib-qthelp==2.0.0
- - sphinxcontrib-serializinghtml==2.0.0
- - sqlalchemy==2.0.44
- - sympy==1.14.0
- - tenacity==9.1.2
- - threadpoolctl==3.6.0
- - tokenizers==0.22.1
- - tomli==2.2.1
- - torch==2.9.0
- - tqdm==4.67.1
- - transformers==4.57.1
- - trio==0.30.0
- - trio-websocket==0.12.2
- - types-python-dateutil==2.9.0.20250516
- - types-requests==2.32.4.20250913
- - typing-extensions==4.14.0
- - typing-inspect==0.9.0
- - typing-inspection==0.4.1
- - tzdata==2025.2
- - uritemplate==4.2.0
- - urllib3==2.4.0
- - websocket-client==1.8.0
- - websockets==15.0.1
- - werkzeug==3.1.3
- - wrapt==1.17.2
- - wsproto==1.2.0
- - xxhash==3.6.0
- - yarl==1.20.1
- - zstandard==0.25.0
diff --git a/RAG/langchain-local-rag.ipynb b/RAG/langchain-local-rag.ipynb
deleted file mode 100644
index 01760df..0000000
--- a/RAG/langchain-local-rag.ipynb
+++ /dev/null
@@ -1,123 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "8585862a",
- "metadata": {
- "vscode": {
- "languageId": "plaintext"
- }
- },
- "outputs": [],
- "source": [
- "from pymongo.mongo_client import MongoClient\n",
- "from pymongo.server_api import ServerApi\n",
- "from dotenv import load_dotenv\n",
- "import os\n",
- "\n",
- "# Load environment variables from .env\n",
- "load_dotenv()\n",
- "\n",
- "# Get the URI from the environment\n",
- "uri = os.getenv(\"MONGODB_URI\")\n",
- "\n",
- "# Create a new client and connect to the server\n",
- "client = MongoClient(uri, server_api=ServerApi('1'))\n",
- "\n",
- "# Send a ping to confirm a successful connection\n",
- "try:\n",
- " client.admin.command('ping')\n",
- " print(\"✅ Pinged your deployment. You successfully connected to MongoDB!\")\n",
- "except Exception as e:\n",
- " print(\"❌ Connection failed:\", e)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "8f4e3c10",
- "metadata": {
- "vscode": {
- "languageId": "plaintext"
- }
- },
- "outputs": [],
- "source": [
- "from langchain_mongodb import MongoDBAtlasVectorSearch\n",
- "from langchain_huggingface import HuggingFaceEmbeddings\n",
- "from pymongo.mongo_client import MongoClient\n",
- "from pymongo.server_api import ServerApi\n",
- "from langchain_community.document_loaders import PyPDFLoader\n",
- "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
- "from dotenv import load_dotenv\n",
- "import os\n",
- "\n",
- "load_dotenv()\n",
- "MONGODB_URI = os.getenv(\"MONGODB_URI\")\n",
- "\n",
- "\n",
- "# Load the embedding model (https://huggingface.co/mixedbread-ai/mxbai-embed-large-v1)\n",
- "embedding_model = HuggingFaceEmbeddings(model_name=\"mixedbread-ai/mxbai-embed-large-v1\")\n",
- "\n",
- "# Instantiate vector store\n",
- "vector_store = MongoDBAtlasVectorSearch.from_connection_string(\n",
- " connection_string = MONGODB_URI,\n",
- " namespace = \"langchain_db.local_rag\",\n",
- " embedding=embedding_model,\n",
- " index_name=\"vector_index\"\n",
- ")\n",
- "\n",
- "# Load the PDF\n",
- "loader = PyPDFLoader(\"documents/001_Petersen.pdf\")\n",
- "data = loader.load()\n",
- "print(data)\n",
- "\n",
- "# Split PDF into documents\n",
- "text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)\n",
- "docs = text_splitter.split_documents(data)\n",
- "\n",
- "# Add data to the vector store\n",
- "vector_store.add_documents(docs)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "4b46165b",
- "metadata": {
- "vscode": {
- "languageId": "plaintext"
- }
- },
- "outputs": [],
- "source": [
- "# Use helper method to create the vector search index\n",
- "vector_store.create_vector_search_index(\n",
- " dimensions = 1024 # The dimensions of the vector embeddings to be indexed\n",
- ")"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.13.5"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/RAG/rag-ingestion/.python-version b/RAG/rag-ingestion/.python-version
new file mode 100644
index 0000000..6324d40
--- /dev/null
+++ b/RAG/rag-ingestion/.python-version
@@ -0,0 +1 @@
+3.14
diff --git a/RAG/rag-ingestion/README.md b/RAG/rag-ingestion/README.md
new file mode 100644
index 0000000..287cdc6
--- /dev/null
+++ b/RAG/rag-ingestion/README.md
@@ -0,0 +1,412 @@
+# RAG Database Workflow Tools
+
+This directory contains utility scripts used to manage the MongoDB data and Vector Search resources used by the application's RAG workflow.
+
+These scripts are not a standalone application or separate RAG service. They are development and maintenance tools for working with the RAG-related MongoDB collection.
+
+They support common database workflows such as:
+
+- Extracting and chunking research papers
+- Populating the RAG collection
+- ingesting existing papers
+- Testing semantic retrieval
+- Clearing the collection when rebuilding the RAG data
+
+The scripts use the same .env as the backend to work
+
+---
+
+# RAG Data Workflow
+
+The current default workflow uses MongoDB Atlas Automated Embedding.
+
+```text
+Research Papers
+ ↓
+PDF Extraction
+ ↓
+Text Cleaning
+ ↓
+Chunking
+ ↓
+MongoDB Collection
+ ↓
+MongoDB Automated Embedding
+ ↓
+MongoDB Vector Search
+ ↓
+Retrieved Document Segments
+ ↓
+Backend RAG Workflow
+```
+
+The utilities in this directory primarily manage the part of the workflow between the source PDFs and the MongoDB Vector Search index.
+
+They do not generate the final RAG response.
+
+The backend uses the retrieved document segments as context when communicating with the application's LLM.
+
+---
+
+# Embedding Approaches
+
+Two versions of the ingestion and retrieval utilities are kept in this directory.
+
+## MongoDB Automated Embedding
+
+This is the current and recommended workflow.
+
+Scripts:
+
+```text
+ingest.py
+test_search.py
+```
+
+The scripts send plain text to MongoDB.
+
+MongoDB Atlas is responsible for:
+
+```text
+Document embedding
+Query embedding
+Vector indexing
+Vector similarity search
+```
+
+The backend and these management scripts do not need to host or run an embedding model.
+
+---
+
+## Local Embedding Model
+
+The previous implementation is retained for development, comparison, and testing.
+
+Scripts:
+
+```text
+ingest_withmodel.py
+test_search_withmodel.py
+```
+
+These scripts use:
+
+```text
+sentence-transformers/all-MiniLM-L6-v2
+```
+
+to generate embeddings locally.
+
+The generated vectors have:
+
+```text
+384 dimensions
+```
+
+This workflow requires the Python process to load the embedding model and explicitly generate both document and query vectors.
+
+---
+
+# Directory Structure
+
+```text
+rag-ingestion/
+│
+├── data/
+│ ├── paper-1.pdf
+│ ├── paper-2.pdf
+│ └── ...
+│
+├── ingest.py
+├── ingest_withmodel.py
+├── ingest_all.py
+│
+├── test_search.py
+├── test_search_withmodel.py
+│
+├── delete_all.py
+│
+├── pyproject.toml
+├── uv.lock
+└── README.md
+```
+
+The `data/` directory is used as the local source directory for research papers that need to be added to the RAG collection.
+
+---
+
+# Setup
+
+Synchronize the project dependencies with:
+
+```bash
+uv sync
+```
+
+This project uses the same `.env` file as the backend.
+
+---
+
+# Script Overview
+
+| Script | Purpose |
+|---|---|
+| `ingest.py` | Populate or update the RAG collection using MongoDB Automated Embedding |
+| `ingest_withmodel.py` | Populate the collection using locally generated MiniLM embeddings |
+| `ingest_all.py` | Run `ingest.py` for every PDF in `data/` |
+| `test_search.py` | Test retrieval using MongoDB Automated Embedding |
+| `test_search_withmodel.py` | Test retrieval using locally generated MiniLM query embeddings |
+| `delete_all.py` | Remove all documents from the configured RAG collection |
+
+---
+
+# Scripts
+
+## `ingest.py`
+
+`ingest.py` is the primary utility for adding a single research paper to the RAG collection.
+
+It prepares the PDF data required by the application's RAG retrieval workflow.
+
+The script:
+
+1. Reads the PDF.
+2. Extracts text page by page.
+3. Cleans common PDF extraction artifacts.
+4. Splits the text into sentence-aware chunks.
+5. Generates a deterministic `paper_id`.
+6. Stores each chunk and its metadata in MongoDB.
+7. Allows the MongoDB Atlas Vector Search index to automatically embed the `content` field.
+
+The script does not generate embeddings locally.
+
+A stored document has a structure similar to:
+
+```json
+{
+ "paper_id": "abc123...",
+ "source": "paper.pdf",
+ "page": 6,
+ "chunk_index": 18,
+ "content": "The study employed a mixed-methods methodology..."
+}
+```
+
+MongoDB Atlas handles the vector representation separately through the configured Automated Embedding index.
+
+Each PDF receives a deterministic `paper_id` generated from the SHA-256 hash of the file.
+
+This allows the same PDF to be ingested again without creating duplicate chunk entries. Existing chunks for that paper are updated instead.
+
+### Usage
+
+```bash
+uv run ingest.py data/paper.pdf
+```
+
+Example:
+
+```bash
+uv run ingest.py data/adhd-study.pdf
+```
+
+---
+
+## `ingest_withmodel.py`
+
+`ingest_withmodel.py` preserves the previous ingestion workflow.
+
+It performs the same general PDF extraction and chunking process, but generates embeddings locally before storing the chunks in MongoDB.
+
+The workflow is:
+
+```text
+PDF
+ ↓
+Text extraction
+ ↓
+Token-aware chunking
+ ↓
+all-MiniLM-L6-v2
+ ↓
+384-dimensional vector
+ ↓
+MongoDB
+```
+
+The local model is:
+
+```text
+sentence-transformers/all-MiniLM-L6-v2
+```
+
+The script stores additional embedding-related information:
+
+- Embedding vector
+- Embedding model
+- Embedding dimensions
+
+A document created by this workflow has a structure similar to:
+
+```json
+{
+ "paper_id": "abc123...",
+ "source": "paper.pdf",
+ "page": 6,
+ "chunk_index": 18,
+ "content": "The study employed a mixed-methods methodology...",
+ "embedding": [
+ 0.012,
+ -0.034,
+ 0.081
+ ],
+ "embedding_model": "sentence-transformers/all-MiniLM-L6-v2",
+ "embedding_dimensions": 384
+}
+```
+
+### Usage
+
+```bash
+uv run ingest_withmodel.py data/paper.pdf
+```
+
+Example:
+
+```bash
+uv run ingest_withmodel.py data/adhd-study.pdf
+```
+
+This script is mainly retained for testing or comparing the previous local-embedding workflow with MongoDB Automated Embedding.
+
+---
+
+# Ingestion Workflow Comparison
+
+## `ingest.py`
+
+```text
+PDF
+ ↓
+Extract text
+ ↓
+Create chunks
+ ↓
+Store chunks in MongoDB
+ ↓
+MongoDB generates embeddings
+```
+
+Responsibilities of the utility:
+
+```text
+PDF parsing
+Text cleaning
+Chunking
+Metadata
+MongoDB insertion
+```
+
+Responsibilities of MongoDB Atlas:
+
+```text
+Embedding generation
+Vector storage/indexing
+Vector Search
+```
+
+---
+
+## `ingest_withmodel.py`
+
+```text
+PDF
+ ↓
+Extract text
+ ↓
+Create chunks
+ ↓
+Load MiniLM
+ ↓
+Generate embeddings
+ ↓
+Store chunks and vectors in MongoDB
+```
+
+In this workflow, embedding generation is also the responsibility of the Python utility.
+
+---
+
+# `ingest_all.py`
+
+`ingest_all.py` is a convenience utility for populating the RAG collection with every PDF in the `data/` directory.
+
+It automatically discovers `.pdf` files and executes:
+
+```text
+ingest.py
+```
+
+for each one.
+
+Because it uses `ingest.py`, it uses the MongoDB Automated Embedding workflow.
+
+For example:
+
+```text
+data/
+├── paper-1.pdf
+├── paper-2.pdf
+├── paper-3.pdf
+└── notes.txt
+```
+
+The following files will be processed:
+
+```text
+paper-1.pdf
+paper-2.pdf
+paper-3.pdf
+```
+
+Non-PDF files are ignored.
+
+If one paper fails, the script continues processing the remaining papers and reports the results at the end.
+
+### Usage
+
+```bash
+uv run ingest_all.py
+```
+
+Example output:
+
+```text
+Found 3 PDF file(s):
+ - paper-1.pdf
+ - paper-2.pdf
+ - paper-3.pdf
+
+...
+
+INGESTION SUMMARY
+
+Total: 3
+Successful: 3
+Failed: 0
+```
+
+This is the main utility for populating or rebuilding the RAG collection from the PDFs currently stored in `data/`.
+
+---
+
+# `test_search.py`
+
+`test_search.py` is a diagnostic utility for testing the current MongoDB Vector Search configuration.
+
+It allows retrieval to be tested independently from the rest of the backend RAG workflow.
+
+This is useful for verifying that:
+
+- Papers have been correctly ingested.
+- Mong
\ No newline at end of file
diff --git a/RAG/documents/001_Petersen.pdf b/RAG/rag-ingestion/data/001_Petersen.pdf
similarity index 100%
rename from RAG/documents/001_Petersen.pdf
rename to RAG/rag-ingestion/data/001_Petersen.pdf
diff --git a/RAG/documents/1-s2.0-S0950584908001390-main.pdf b/RAG/rag-ingestion/data/1-s2.0-S0950584908001390-main.pdf
similarity index 100%
rename from RAG/documents/1-s2.0-S0950584908001390-main.pdf
rename to RAG/rag-ingestion/data/1-s2.0-S0950584908001390-main.pdf
diff --git a/RAG/documents/Evidence_based_software_engineering.pdf b/RAG/rag-ingestion/data/Evidence_based_software_engineering.pdf
similarity index 100%
rename from RAG/documents/Evidence_based_software_engineering.pdf
rename to RAG/rag-ingestion/data/Evidence_based_software_engineering.pdf
diff --git "a/RAG/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" "b/RAG/rag-ingestion/data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf"
similarity index 100%
rename from "RAG/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf"
rename to "RAG/rag-ingestion/data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf"
diff --git a/RAG/documents/Kitchenham-2007Systematicreviews5-8.pdf b/RAG/rag-ingestion/data/Kitchenham-2007Systematicreviews5-8.pdf
similarity index 100%
rename from RAG/documents/Kitchenham-2007Systematicreviews5-8.pdf
rename to RAG/rag-ingestion/data/Kitchenham-2007Systematicreviews5-8.pdf
diff --git a/RAG/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf b/RAG/rag-ingestion/data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf
similarity index 100%
rename from RAG/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf
rename to RAG/rag-ingestion/data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf
diff --git a/RAG/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf b/RAG/rag-ingestion/data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf
similarity index 100%
rename from RAG/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf
rename to RAG/rag-ingestion/data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf
diff --git a/RAG/documents/Rapid Reviews in Software Engineering.pdf b/RAG/rag-ingestion/data/Rapid Reviews in Software Engineering.pdf
similarity index 100%
rename from RAG/documents/Rapid Reviews in Software Engineering.pdf
rename to RAG/rag-ingestion/data/Rapid Reviews in Software Engineering.pdf
diff --git a/RAG/documents/SLR Student Guide.pdf b/RAG/rag-ingestion/data/SLR Student Guide.pdf
similarity index 100%
rename from RAG/documents/SLR Student Guide.pdf
rename to RAG/rag-ingestion/data/SLR Student Guide.pdf
diff --git a/RAG/documents/Synthesizing Evidence in Software Engineering Research.pdf b/RAG/rag-ingestion/data/Synthesizing Evidence in Software Engineering Research.pdf
similarity index 100%
rename from RAG/documents/Synthesizing Evidence in Software Engineering Research.pdf
rename to RAG/rag-ingestion/data/Synthesizing Evidence in Software Engineering Research.pdf
diff --git "a/RAG/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" "b/RAG/rag-ingestion/data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf"
similarity index 100%
rename from "RAG/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf"
rename to "RAG/rag-ingestion/data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf"
diff --git a/RAG/documents/pizard_2024.pdf b/RAG/rag-ingestion/data/pizard_2024.pdf
similarity index 100%
rename from RAG/documents/pizard_2024.pdf
rename to RAG/rag-ingestion/data/pizard_2024.pdf
diff --git a/RAG/rag-ingestion/delete_all.py b/RAG/rag-ingestion/delete_all.py
new file mode 100644
index 0000000..829dcfb
--- /dev/null
+++ b/RAG/rag-ingestion/delete_all.py
@@ -0,0 +1,74 @@
+import os
+import sys
+
+from dotenv import load_dotenv
+from pymongo import MongoClient
+
+
+load_dotenv()
+
+MONGODB_URI = os.environ["MONGO_URI"]
+MONGODB_DATABASE = os.environ.get(
+ "MONGO_DB_NAME"
+)
+MONGODB_COLLECTION = os.environ.get(
+ "MONGO_DB_RAG_COLLECTION_NAME"
+)
+
+
+def main():
+ client = MongoClient(
+ MONGODB_URI,
+ serverSelectionTimeoutMS=10000,
+ )
+
+ try:
+ client.admin.command("ping")
+
+ collection = client[
+ MONGODB_DATABASE
+ ][
+ MONGODB_COLLECTION
+ ]
+
+ document_count = collection.count_documents({})
+
+ print(
+ f"Database: {MONGODB_DATABASE}"
+ )
+ print(
+ f"Collection: {MONGODB_COLLECTION}"
+ )
+ print(
+ f"Documents: {document_count}"
+ )
+
+ if document_count == 0:
+ print("\nCollection is already empty.")
+ return
+
+ confirmation = input(
+ "\nDelete ALL documents from this collection? "
+ "Type 'yes' to continue: "
+ )
+
+ if confirmation.lower() != "yes":
+ print("Cancelled.")
+ return
+
+ result = collection.delete_many({})
+
+ print()
+ print(
+ f"Deleted {result.deleted_count} documents."
+ )
+ print(
+ "Collection was NOT deleted."
+ )
+
+ finally:
+ client.close()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/RAG/rag-ingestion/documents/001_Petersen.pdf b/RAG/rag-ingestion/documents/001_Petersen.pdf
new file mode 100644
index 0000000..280be87
Binary files /dev/null and b/RAG/rag-ingestion/documents/001_Petersen.pdf differ
diff --git a/RAG/rag-ingestion/documents/1-s2.0-S0950584908001390-main.pdf b/RAG/rag-ingestion/documents/1-s2.0-S0950584908001390-main.pdf
new file mode 100644
index 0000000..f69e221
Binary files /dev/null and b/RAG/rag-ingestion/documents/1-s2.0-S0950584908001390-main.pdf differ
diff --git a/RAG/rag-ingestion/documents/Evidence_based_software_engineering.pdf b/RAG/rag-ingestion/documents/Evidence_based_software_engineering.pdf
new file mode 100644
index 0000000..a09398a
Binary files /dev/null and b/RAG/rag-ingestion/documents/Evidence_based_software_engineering.pdf differ
diff --git "a/RAG/rag-ingestion/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" "b/RAG/rag-ingestion/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf"
new file mode 100644
index 0000000..6fddd9d
Binary files /dev/null and "b/RAG/rag-ingestion/documents/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" differ
diff --git a/RAG/rag-ingestion/documents/Kitchenham-2007Systematicreviews5-8.pdf b/RAG/rag-ingestion/documents/Kitchenham-2007Systematicreviews5-8.pdf
new file mode 100644
index 0000000..ee87046
Binary files /dev/null and b/RAG/rag-ingestion/documents/Kitchenham-2007Systematicreviews5-8.pdf differ
diff --git a/RAG/rag-ingestion/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf b/RAG/rag-ingestion/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf
new file mode 100644
index 0000000..4972fb7
Binary files /dev/null and b/RAG/rag-ingestion/documents/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf differ
diff --git a/RAG/rag-ingestion/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf b/RAG/rag-ingestion/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf
new file mode 100644
index 0000000..c7a965b
Binary files /dev/null and b/RAG/rag-ingestion/documents/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf differ
diff --git a/RAG/rag-ingestion/documents/Rapid Reviews in Software Engineering.pdf b/RAG/rag-ingestion/documents/Rapid Reviews in Software Engineering.pdf
new file mode 100644
index 0000000..f527880
Binary files /dev/null and b/RAG/rag-ingestion/documents/Rapid Reviews in Software Engineering.pdf differ
diff --git a/RAG/rag-ingestion/documents/SLR Student Guide.pdf b/RAG/rag-ingestion/documents/SLR Student Guide.pdf
new file mode 100644
index 0000000..c92c656
Binary files /dev/null and b/RAG/rag-ingestion/documents/SLR Student Guide.pdf differ
diff --git a/RAG/rag-ingestion/documents/Synthesizing Evidence in Software Engineering Research.pdf b/RAG/rag-ingestion/documents/Synthesizing Evidence in Software Engineering Research.pdf
new file mode 100644
index 0000000..e720f94
Binary files /dev/null and b/RAG/rag-ingestion/documents/Synthesizing Evidence in Software Engineering Research.pdf differ
diff --git "a/RAG/rag-ingestion/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" "b/RAG/rag-ingestion/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf"
new file mode 100644
index 0000000..2c153fd
Binary files /dev/null and "b/RAG/rag-ingestion/documents/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" differ
diff --git a/RAG/rag-ingestion/documents/pizard_2024.pdf b/RAG/rag-ingestion/documents/pizard_2024.pdf
new file mode 100644
index 0000000..0c8d28a
Binary files /dev/null and b/RAG/rag-ingestion/documents/pizard_2024.pdf differ
diff --git a/RAG/rag-ingestion/ingest.py b/RAG/rag-ingestion/ingest.py
new file mode 100644
index 0000000..0519be3
--- /dev/null
+++ b/RAG/rag-ingestion/ingest.py
@@ -0,0 +1,613 @@
+import hashlib
+import os
+import re
+import sys
+from pathlib import Path
+
+from dotenv import load_dotenv
+from pypdf import PdfReader
+from pymongo import MongoClient, ReplaceOne
+
+
+# ============================================================
+# Configuration
+# ============================================================
+
+# Chunking is word-based so we do not need to load any
+# tokenizer or embedding model in the backend.
+MAX_CHUNK_WORDS = 200
+OVERLAP_WORDS = 20
+
+load_dotenv()
+
+MONGODB_URI = os.environ["MONGO_URI"]
+
+MONGODB_DATABASE = os.environ.get(
+ "MONGO_DB_NAME",
+ "rag_db_test",
+)
+
+MONGODB_COLLECTION = os.environ.get(
+ "MONGO_DB_RAG_COLLECTION_NAME",
+ "test_paper_chunks",
+)
+
+
+# ============================================================
+# Helpers
+# ============================================================
+
+def generate_paper_id(pdf_path: Path) -> str:
+ """
+ Generate a stable SHA-256 ID from the PDF contents.
+
+ If the exact same PDF is ingested again,
+ it receives the same paper_id.
+ """
+
+ sha256 = hashlib.sha256()
+
+ with pdf_path.open("rb") as file:
+ while chunk := file.read(1024 * 1024):
+ sha256.update(chunk)
+
+ return sha256.hexdigest()
+
+
+def clean_text(text: str) -> str:
+ """
+ Clean common PDF extraction artifacts without heavily
+ modifying the source text.
+ """
+
+ # Remove null bytes.
+ text = text.replace("\x00", "")
+
+ # Join words broken across lines:
+ #
+ # transfor-
+ # mation
+ #
+ # becomes:
+ #
+ # transformation
+ text = re.sub(
+ r"(?<=\w)-\s*\n\s*(?=\w)",
+ "",
+ text,
+ )
+
+ # Normalize line breaks inside paragraphs.
+ text = re.sub(
+ r"(? list[dict]:
+ """
+ Extract text from each PDF page independently so page
+ metadata can be retained in MongoDB.
+ """
+
+ reader = PdfReader(pdf_path)
+
+ pages = []
+
+ for page_number, page in enumerate(
+ reader.pages,
+ start=1,
+ ):
+ raw_text = page.extract_text()
+
+ if not raw_text:
+ continue
+
+ text = clean_text(raw_text)
+
+ if not text:
+ continue
+
+ pages.append(
+ {
+ "page": page_number,
+ "text": text,
+ }
+ )
+
+ return pages
+
+
+# ============================================================
+# Chunking
+# ============================================================
+
+def split_into_sentences(text: str) -> list[str]:
+ """
+ Basic sentence-aware splitter.
+
+ Keeping sentence boundaries generally produces more useful
+ RAG chunks than blindly splitting every N words.
+ """
+
+ sentences = re.split(
+ r"(?<=[.!?])\s+",
+ text,
+ )
+
+ return [
+ sentence.strip()
+ for sentence in sentences
+ if sentence.strip()
+ ]
+
+
+def word_count(text: str) -> int:
+ """
+ Approximate chunk size without requiring a tokenizer.
+
+ Atlas/Voyage performs its own tokenization when generating
+ the actual embeddings.
+ """
+
+ return len(text.split())
+
+
+def split_long_sentence(
+ sentence: str,
+) -> list[str]:
+ """
+ Split an unusually long sentence into smaller pieces.
+ """
+
+ words = sentence.split()
+
+ pieces = []
+
+ start = 0
+
+ while start < len(words):
+
+ end = min(
+ start + MAX_CHUNK_WORDS,
+ len(words),
+ )
+
+ piece = " ".join(
+ words[start:end]
+ ).strip()
+
+ if piece:
+ pieces.append(piece)
+
+ if end >= len(words):
+ break
+
+ start = end - OVERLAP_WORDS
+
+ return pieces
+
+
+def get_overlap_sentences(
+ sentences: list[str],
+) -> list[str]:
+ """
+ Preserve approximately OVERLAP_WORDS words from the end
+ of the previous chunk.
+
+ This provides some context between adjacent chunks while
+ keeping duplicated embedding input relatively small.
+ """
+
+ overlap = []
+ total_words = 0
+
+ for sentence in reversed(sentences):
+
+ sentence_words = word_count(
+ sentence
+ )
+
+ if (
+ total_words + sentence_words
+ > OVERLAP_WORDS
+ ):
+ break
+
+ overlap.insert(
+ 0,
+ sentence,
+ )
+
+ total_words += sentence_words
+
+ return overlap
+
+
+def create_chunks(
+ text: str,
+) -> list[str]:
+ """
+ Create sentence-aware chunks using an approximate word
+ limit.
+
+ No embedding model or tokenizer is required locally.
+ """
+
+ sentences = split_into_sentences(text)
+
+ chunks = []
+
+ current_sentences = []
+ current_words = 0
+
+ for sentence in sentences:
+
+ sentence_words = word_count(
+ sentence
+ )
+
+ # ----------------------------------------------------
+ # Handle an unusually long individual sentence
+ # ----------------------------------------------------
+
+ if sentence_words > MAX_CHUNK_WORDS:
+
+ if current_sentences:
+ chunks.append(
+ " ".join(
+ current_sentences
+ )
+ )
+
+ current_sentences = []
+ current_words = 0
+
+ chunks.extend(
+ split_long_sentence(
+ sentence
+ )
+ )
+
+ continue
+
+ # ----------------------------------------------------
+ # Sentence fits in current chunk
+ # ----------------------------------------------------
+
+ if (
+ current_words + sentence_words
+ <= MAX_CHUNK_WORDS
+ ):
+ current_sentences.append(
+ sentence
+ )
+
+ current_words += sentence_words
+
+ continue
+
+ # ----------------------------------------------------
+ # Finish current chunk
+ # ----------------------------------------------------
+
+ if current_sentences:
+ chunks.append(
+ " ".join(
+ current_sentences
+ )
+ )
+
+ # ----------------------------------------------------
+ # Add limited overlap
+ # ----------------------------------------------------
+
+ overlap = get_overlap_sentences(
+ current_sentences
+ )
+
+ # Make sure overlap + new sentence does not exceed
+ # the maximum chunk size.
+ while (
+ overlap
+ and (
+ sum(
+ word_count(item)
+ for item in overlap
+ )
+ + sentence_words
+ > MAX_CHUNK_WORDS
+ )
+ ):
+ overlap.pop(0)
+
+ current_sentences = (
+ overlap + [sentence]
+ )
+
+ current_words = sum(
+ word_count(item)
+ for item in current_sentences
+ )
+
+ # Final chunk.
+ if current_sentences:
+ chunks.append(
+ " ".join(
+ current_sentences
+ )
+ )
+
+ return chunks
+
+
+# ============================================================
+# Main ingestion
+# ============================================================
+
+def ingest_pdf(
+ pdf_path: Path,
+) -> None:
+
+ # --------------------------------------------------------
+ # Validate file
+ # --------------------------------------------------------
+
+ if not pdf_path.exists():
+ raise FileNotFoundError(
+ f"PDF not found: {pdf_path}"
+ )
+
+ if pdf_path.suffix.lower() != ".pdf":
+ raise ValueError(
+ f"Expected a PDF file, got: {pdf_path}"
+ )
+
+ print(f"PDF: {pdf_path}")
+
+ # --------------------------------------------------------
+ # Extract PDF
+ # --------------------------------------------------------
+
+ print("Extracting PDF text...")
+
+ pages = extract_pages(
+ pdf_path
+ )
+
+ if not pages:
+ raise RuntimeError(
+ "No text could be extracted from the PDF."
+ )
+
+ print(
+ f"Extracted text from "
+ f"{len(pages)} pages."
+ )
+
+ # --------------------------------------------------------
+ # Create stable paper ID
+ # --------------------------------------------------------
+
+ paper_id = generate_paper_id(
+ pdf_path
+ )
+
+ print(
+ f"Paper ID: "
+ f"{paper_id[:16]}..."
+ )
+
+ # --------------------------------------------------------
+ # Create chunks
+ # --------------------------------------------------------
+
+ print("Creating chunks...")
+
+ documents = []
+
+ chunk_index = 0
+
+ for page in pages:
+
+ chunks = create_chunks(
+ page["text"]
+ )
+
+ for chunk in chunks:
+
+ documents.append(
+ {
+ "paper_id": paper_id,
+ "source": pdf_path.name,
+ "page": page["page"],
+ "chunk_index": chunk_index,
+
+ # This is the field indexed by the
+ # Atlas autoEmbed Vector Search index.
+ "content": chunk,
+ }
+ )
+
+ chunk_index += 1
+
+ if not documents:
+ raise RuntimeError(
+ "No chunks were generated."
+ )
+
+ print(
+ f"Created {len(documents)} chunks."
+ )
+
+ # --------------------------------------------------------
+ # Connect to MongoDB
+ # --------------------------------------------------------
+
+ print("Connecting to MongoDB...")
+
+ client = MongoClient(
+ MONGODB_URI,
+ serverSelectionTimeoutMS=10000,
+ )
+
+ try:
+ # Verify connection.
+ client.admin.command(
+ "ping"
+ )
+
+ database = client[
+ MONGODB_DATABASE
+ ]
+
+ collection = database[
+ MONGODB_COLLECTION
+ ]
+
+ print(
+ "MongoDB connection successful."
+ )
+
+ # ----------------------------------------------------
+ # Regular MongoDB index
+ # ----------------------------------------------------
+
+ collection.create_index(
+ [
+ ("paper_id", 1),
+ ("chunk_index", 1),
+ ],
+ unique=True,
+ )
+
+ # ----------------------------------------------------
+ # Upsert chunks
+ # ----------------------------------------------------
+
+ print("Uploading chunks...")
+
+ operations = []
+
+ for document in documents:
+
+ operations.append(
+ ReplaceOne(
+ {
+ "paper_id":
+ document["paper_id"],
+ "chunk_index":
+ document["chunk_index"],
+ },
+ document,
+ upsert=True,
+ )
+ )
+
+ result = collection.bulk_write(
+ operations,
+ ordered=False,
+ )
+
+ # ----------------------------------------------------
+ # Remove obsolete chunks
+ # ----------------------------------------------------
+ #
+ # Useful if the same PDF was previously ingested with
+ # different chunk settings.
+
+ delete_result = collection.delete_many(
+ {
+ "paper_id": paper_id,
+ "chunk_index": {
+ "$gte": len(documents)
+ },
+ }
+ )
+
+ # ----------------------------------------------------
+ # Summary
+ # ----------------------------------------------------
+
+ print()
+ print("=" * 70)
+ print("INGESTION COMPLETE")
+ print("=" * 70)
+
+ print(
+ f"PDF: {pdf_path.name}"
+ )
+
+ print(
+ f"Pages: {len(pages)}"
+ )
+
+ print(
+ f"Chunks: {len(documents)}"
+ )
+
+ print(
+ f"Inserted: "
+ f"{result.upserted_count}"
+ )
+
+ print(
+ f"Updated: "
+ f"{result.modified_count}"
+ )
+
+ print(
+ f"Deleted: "
+ f"{delete_result.deleted_count}"
+ )
+
+ print()
+ print(
+ "Embeddings are handled automatically "
+ "by the MongoDB Vector Search index."
+ )
+
+ finally:
+ client.close()
+
+
+# ============================================================
+# CLI
+# ============================================================
+
+if __name__ == "__main__":
+
+ if len(sys.argv) != 2:
+
+ print(
+ "Usage:\n"
+ " uv run ingest.py "
+ "data/paper.pdf"
+ )
+
+ sys.exit(1)
+
+ path = Path(
+ sys.argv[1]
+ )
+
+ ingest_pdf(
+ path
+ )
\ No newline at end of file
diff --git a/RAG/rag-ingestion/ingest_all.py b/RAG/rag-ingestion/ingest_all.py
new file mode 100644
index 0000000..769416f
--- /dev/null
+++ b/RAG/rag-ingestion/ingest_all.py
@@ -0,0 +1,136 @@
+import subprocess
+import sys
+from pathlib import Path
+
+
+DATA_FOLDER = Path("data")
+INGEST_SCRIPT = Path("ingest.py")
+
+
+def get_pdf_files() -> list[Path]:
+ """
+ Return all PDF files inside the data folder,
+ sorted alphabetically.
+ """
+
+ if not DATA_FOLDER.exists():
+ raise FileNotFoundError(
+ f"Data folder not found: {DATA_FOLDER.resolve()}"
+ )
+
+ return sorted(
+ file
+ for file in DATA_FOLDER.iterdir()
+ if file.is_file()
+ and file.suffix.lower() == ".pdf"
+ )
+
+
+def ingest_pdf(pdf_path: Path) -> bool:
+ """
+ Run ingest.py for one PDF.
+
+ Returns True if ingestion succeeds,
+ otherwise False.
+ """
+
+ print()
+ print("=" * 70)
+ print(f"Ingesting: {pdf_path.name}")
+ print("=" * 70)
+
+ result = subprocess.run(
+ [
+ sys.executable,
+ str(INGEST_SCRIPT),
+ str(pdf_path),
+ ]
+ )
+
+ if result.returncode == 0:
+ print()
+ print(f"✓ Successfully ingested: {pdf_path.name}")
+ return True
+
+ print()
+ print(f"✗ Failed to ingest: {pdf_path.name}")
+
+ return False
+
+
+def main() -> None:
+
+ if not INGEST_SCRIPT.exists():
+ print(
+ f"Could not find ingest script: "
+ f"{INGEST_SCRIPT.resolve()}"
+ )
+ sys.exit(1)
+
+ try:
+ pdf_files = get_pdf_files()
+
+ except FileNotFoundError as error:
+ print(error)
+ sys.exit(1)
+
+ if not pdf_files:
+ print(
+ f"No PDF files found in "
+ f"{DATA_FOLDER.resolve()}"
+ )
+ return
+
+ print(
+ f"Found {len(pdf_files)} PDF file(s):"
+ )
+
+ for pdf in pdf_files:
+ print(f" - {pdf.name}")
+
+ successful = []
+ failed = []
+
+ for pdf in pdf_files:
+
+ if ingest_pdf(pdf):
+ successful.append(pdf.name)
+ else:
+ failed.append(pdf.name)
+
+ # --------------------------------------------------------
+ # Summary
+ # --------------------------------------------------------
+
+ print()
+ print("=" * 70)
+ print("INGESTION SUMMARY")
+ print("=" * 70)
+
+ print(
+ f"Total: {len(pdf_files)}"
+ )
+
+ print(
+ f"Successful: {len(successful)}"
+ )
+
+ print(
+ f"Failed: {len(failed)}"
+ )
+
+ if failed:
+ print()
+ print("Failed files:")
+
+ for filename in failed:
+ print(f" - {filename}")
+
+ sys.exit(1)
+
+ print()
+ print("All papers successfully ingested.")
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/RAG/rag-ingestion/ingest_withmodel.py b/RAG/rag-ingestion/ingest_withmodel.py
new file mode 100644
index 0000000..bd9bb5f
--- /dev/null
+++ b/RAG/rag-ingestion/ingest_withmodel.py
@@ -0,0 +1,567 @@
+import hashlib
+import os
+import re
+import sys
+from pathlib import Path
+
+from dotenv import load_dotenv
+from pypdf import PdfReader
+from pymongo import MongoClient, ReplaceOne
+from sentence_transformers import SentenceTransformer
+
+
+# ============================================================
+# Configuration
+# ============================================================
+
+MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
+
+# MiniLM supports up to ~256 tokens.
+# Keep some margin below that limit.
+MAX_CHUNK_TOKENS = 200
+OVERLAP_TOKENS = 30
+
+EMBEDDING_DIMENSIONS = 384
+EMBEDDING_BATCH_SIZE = 32
+
+load_dotenv()
+
+MONGODB_URI = os.environ["MONGO_URI"]
+MONGODB_DATABASE = os.environ.get("MONGO_DB_NAME", "rag_db_test")
+MONGODB_COLLECTION = os.environ.get(
+ "MONGO_DB_COLLECTION_NAME",
+ "test_paper_chunks",
+)
+
+
+# ============================================================
+# Helpers
+# ============================================================
+
+def generate_paper_id(pdf_path: Path) -> str:
+ """
+ Generate a stable ID based on the PDF contents.
+
+ If the exact same PDF is ingested again, it receives
+ the same paper_id.
+ """
+ sha256 = hashlib.sha256()
+
+ with pdf_path.open("rb") as file:
+ while chunk := file.read(1024 * 1024):
+ sha256.update(chunk)
+
+ return sha256.hexdigest()
+
+
+def clean_text(text: str) -> str:
+ """
+ Clean common PDF extraction artifacts without heavily
+ modifying the source text.
+ """
+
+ # Remove null bytes
+ text = text.replace("\x00", "")
+
+ # Join words broken across lines:
+ #
+ # "transfor-\nmation"
+ # becomes
+ # "transformation"
+ text = re.sub(
+ r"(?<=\w)-\s*\n\s*(?=\w)",
+ "",
+ text,
+ )
+
+ # Normalize line breaks inside paragraphs
+ text = re.sub(
+ r"(? list[dict]:
+ """
+ Extract text from each PDF page independently so that
+ page metadata can be retained in MongoDB.
+ """
+
+ reader = PdfReader(pdf_path)
+
+ pages = []
+
+ for page_number, page in enumerate(
+ reader.pages,
+ start=1,
+ ):
+ raw_text = page.extract_text()
+
+ if not raw_text:
+ continue
+
+ text = clean_text(raw_text)
+
+ if not text:
+ continue
+
+ pages.append(
+ {
+ "page": page_number,
+ "text": text,
+ }
+ )
+
+ return pages
+
+
+# ============================================================
+# Chunking
+# ============================================================
+
+def split_into_sentences(text: str) -> list[str]:
+ """
+ Basic sentence splitter.
+
+ This keeps chunks more natural than blindly splitting
+ every N tokens.
+ """
+
+ sentences = re.split(
+ r"(?<=[.!?])\s+",
+ text,
+ )
+
+ return [
+ sentence.strip()
+ for sentence in sentences
+ if sentence.strip()
+ ]
+
+
+def token_count(
+ text: str,
+ tokenizer,
+) -> int:
+ return len(
+ tokenizer.encode(
+ text,
+ add_special_tokens=False,
+ )
+ )
+
+
+def split_long_sentence(
+ sentence: str,
+ tokenizer,
+) -> list[str]:
+ """
+ Fallback for a sentence that by itself exceeds the
+ maximum chunk size.
+ """
+
+ token_ids = tokenizer.encode(
+ sentence,
+ add_special_tokens=False,
+ )
+
+ pieces = []
+
+ start = 0
+
+ while start < len(token_ids):
+ end = min(
+ start + MAX_CHUNK_TOKENS,
+ len(token_ids),
+ )
+
+ piece = tokenizer.decode(
+ token_ids[start:end],
+ skip_special_tokens=True,
+ ).strip()
+
+ if piece:
+ pieces.append(piece)
+
+ if end >= len(token_ids):
+ break
+
+ start = end - OVERLAP_TOKENS
+
+ return pieces
+
+
+def get_overlap_sentences(
+ sentences: list[str],
+ tokenizer,
+) -> list[str]:
+ """
+ Preserve the final ~OVERLAP_TOKENS worth of sentences
+ for the next chunk.
+ """
+
+ overlap = []
+ total_tokens = 0
+
+ for sentence in reversed(sentences):
+ sentence_tokens = token_count(
+ sentence,
+ tokenizer,
+ )
+
+ if (
+ total_tokens + sentence_tokens
+ > OVERLAP_TOKENS
+ ):
+ break
+
+ overlap.insert(0, sentence)
+ total_tokens += sentence_tokens
+
+ return overlap
+
+
+def create_chunks(
+ text: str,
+ tokenizer,
+) -> list[str]:
+ """
+ Create sentence-aware chunks while respecting the model's
+ token limit.
+ """
+
+ sentences = split_into_sentences(text)
+
+ chunks = []
+ current_sentences = []
+ current_tokens = 0
+
+ for sentence in sentences:
+
+ sentence_tokens = token_count(
+ sentence,
+ tokenizer,
+ )
+
+ # Handle unusually long individual sentences
+ if sentence_tokens > MAX_CHUNK_TOKENS:
+
+ if current_sentences:
+ chunks.append(
+ " ".join(current_sentences)
+ )
+
+ current_sentences = []
+ current_tokens = 0
+
+ chunks.extend(
+ split_long_sentence(
+ sentence,
+ tokenizer,
+ )
+ )
+
+ continue
+
+ # Sentence still fits in current chunk
+ if (
+ current_tokens + sentence_tokens
+ <= MAX_CHUNK_TOKENS
+ ):
+ current_sentences.append(sentence)
+ current_tokens += sentence_tokens
+ continue
+
+ # Finish the existing chunk
+ chunks.append(
+ " ".join(current_sentences)
+ )
+
+ # Keep some semantic overlap with the next chunk
+ overlap = get_overlap_sentences(
+ current_sentences,
+ tokenizer,
+ )
+
+ current_sentences = overlap + [sentence]
+
+ current_tokens = sum(
+ token_count(item, tokenizer)
+ for item in current_sentences
+ )
+
+ if current_sentences:
+ chunks.append(
+ " ".join(current_sentences)
+ )
+
+ return chunks
+
+
+# ============================================================
+# Main ingestion
+# ============================================================
+
+def ingest_pdf(pdf_path: Path) -> None:
+
+ if not pdf_path.exists():
+ raise FileNotFoundError(
+ f"PDF not found: {pdf_path}"
+ )
+
+ if pdf_path.suffix.lower() != ".pdf":
+ raise ValueError(
+ f"Expected a PDF file, got: {pdf_path}"
+ )
+
+ print(f"PDF: {pdf_path}")
+
+ # --------------------------------------------------------
+ # Load embedding model
+ # --------------------------------------------------------
+
+ print(f"Loading model: {MODEL_NAME}")
+
+ model = SentenceTransformer(
+ MODEL_NAME
+ )
+
+ tokenizer = model.tokenizer
+
+ # --------------------------------------------------------
+ # Extract PDF
+ # --------------------------------------------------------
+
+ print("Extracting PDF text...")
+
+ pages = extract_pages(pdf_path)
+
+ if not pages:
+ raise RuntimeError(
+ "No text could be extracted from the PDF."
+ )
+
+ print(
+ f"Extracted text from {len(pages)} pages."
+ )
+
+ # --------------------------------------------------------
+ # Create stable paper ID
+ # --------------------------------------------------------
+
+ paper_id = generate_paper_id(pdf_path)
+
+ print(
+ f"Paper ID: {paper_id[:16]}..."
+ )
+
+ # --------------------------------------------------------
+ # Create chunks
+ # --------------------------------------------------------
+
+ documents = []
+
+ chunk_index = 0
+
+ for page in pages:
+
+ chunks = create_chunks(
+ page["text"],
+ tokenizer,
+ )
+
+ for chunk in chunks:
+
+ documents.append(
+ {
+ "paper_id": paper_id,
+ "source": pdf_path.name,
+ "page": page["page"],
+ "chunk_index": chunk_index,
+ "content": chunk,
+ }
+ )
+
+ chunk_index += 1
+
+ if not documents:
+ raise RuntimeError(
+ "No chunks were generated."
+ )
+
+ print(
+ f"Created {len(documents)} chunks."
+ )
+
+ # --------------------------------------------------------
+ # Generate embeddings
+ # --------------------------------------------------------
+
+ print("Generating embeddings...")
+
+ texts = [
+ document["content"]
+ for document in documents
+ ]
+
+ embeddings = model.encode(
+ texts,
+ batch_size=EMBEDDING_BATCH_SIZE,
+ show_progress_bar=True,
+ normalize_embeddings=True,
+ convert_to_numpy=True,
+ )
+
+ if embeddings.shape[1] != EMBEDDING_DIMENSIONS:
+ raise RuntimeError(
+ "Unexpected embedding dimension: "
+ f"{embeddings.shape[1]}"
+ )
+
+ # --------------------------------------------------------
+ # Attach embeddings + metadata
+ # --------------------------------------------------------
+
+ for document, embedding in zip(
+ documents,
+ embeddings,
+ ):
+ document["embedding"] = (
+ embedding.tolist()
+ )
+
+ document["embedding_model"] = (
+ MODEL_NAME
+ )
+
+ document["embedding_dimensions"] = (
+ EMBEDDING_DIMENSIONS
+ )
+
+ # --------------------------------------------------------
+ # Connect to MongoDB
+ # --------------------------------------------------------
+
+ print("Connecting to MongoDB...")
+
+ client = MongoClient(
+ MONGODB_URI,
+ serverSelectionTimeoutMS=10000,
+ )
+
+ # Verify connection
+ client.admin.command("ping")
+
+ database = client[
+ MONGODB_DATABASE
+ ]
+
+ collection = database[
+ MONGODB_COLLECTION
+ ]
+
+ print("MongoDB connection successful.")
+
+ # --------------------------------------------------------
+ # Useful regular MongoDB index
+ # --------------------------------------------------------
+
+ collection.create_index(
+ [
+ ("paper_id", 1),
+ ("chunk_index", 1),
+ ],
+ unique=True,
+ )
+
+ # --------------------------------------------------------
+ # Upsert chunks
+ # --------------------------------------------------------
+
+ print("Uploading chunks...")
+
+ operations = []
+
+ for document in documents:
+
+ operations.append(
+ ReplaceOne(
+ {
+ "paper_id": document["paper_id"],
+ "chunk_index": document["chunk_index"],
+ },
+ document,
+ upsert=True,
+ )
+ )
+
+ result = collection.bulk_write(
+ operations,
+ ordered=False,
+ )
+
+ # Remove obsolete chunks in case this document had
+ # previously been ingested with different chunk settings.
+ collection.delete_many(
+ {
+ "paper_id": paper_id,
+ "chunk_index": {
+ "$gte": len(documents)
+ },
+ }
+ )
+
+ print()
+ print("Ingestion complete.")
+ print(f"PDF: {pdf_path.name}")
+ print(f"Pages: {len(pages)}")
+ print(f"Chunks: {len(documents)}")
+ print(
+ f"Inserted: {result.upserted_count}"
+ )
+ print(
+ f"Updated: {result.modified_count}"
+ )
+ print(
+ f"Embedding dimensions: "
+ f"{EMBEDDING_DIMENSIONS}"
+ )
+
+ client.close()
+
+
+# ============================================================
+# CLI
+# ============================================================
+
+if __name__ == "__main__":
+
+ if len(sys.argv) != 2:
+ print(
+ "Usage:\n"
+ " uv run ingest.py "
+ "data/paper.pdf"
+ )
+
+ sys.exit(1)
+
+ path = Path(sys.argv[1])
+
+ ingest_pdf(path)
\ No newline at end of file
diff --git a/RAG/rag-ingestion/original_data/001_Petersen.pdf b/RAG/rag-ingestion/original_data/001_Petersen.pdf
new file mode 100644
index 0000000..280be87
Binary files /dev/null and b/RAG/rag-ingestion/original_data/001_Petersen.pdf differ
diff --git a/RAG/rag-ingestion/original_data/1-s2.0-S0950584908001390-main.pdf b/RAG/rag-ingestion/original_data/1-s2.0-S0950584908001390-main.pdf
new file mode 100644
index 0000000..f69e221
Binary files /dev/null and b/RAG/rag-ingestion/original_data/1-s2.0-S0950584908001390-main.pdf differ
diff --git a/RAG/rag-ingestion/original_data/Evidence_based_software_engineering.pdf b/RAG/rag-ingestion/original_data/Evidence_based_software_engineering.pdf
new file mode 100644
index 0000000..a09398a
Binary files /dev/null and b/RAG/rag-ingestion/original_data/Evidence_based_software_engineering.pdf differ
diff --git "a/RAG/rag-ingestion/original_data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" "b/RAG/rag-ingestion/original_data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf"
new file mode 100644
index 0000000..6fddd9d
Binary files /dev/null and "b/RAG/rag-ingestion/original_data/Int Statistical Rev - 2009 - Rosenblad - Introduction to Meta\342\200\220Analysis by Michael Borenstein Larry V Hedges Julian P T .pdf" differ
diff --git a/RAG/rag-ingestion/original_data/Kitchenham-2007Systematicreviews5-8.pdf b/RAG/rag-ingestion/original_data/Kitchenham-2007Systematicreviews5-8.pdf
new file mode 100644
index 0000000..ee87046
Binary files /dev/null and b/RAG/rag-ingestion/original_data/Kitchenham-2007Systematicreviews5-8.pdf differ
diff --git a/RAG/rag-ingestion/original_data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf b/RAG/rag-ingestion/original_data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf
new file mode 100644
index 0000000..4972fb7
Binary files /dev/null and b/RAG/rag-ingestion/original_data/Paving the Way for Mature Secondary Research The Seven Types of Literature Review.pdf differ
diff --git a/RAG/rag-ingestion/original_data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf b/RAG/rag-ingestion/original_data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf
new file mode 100644
index 0000000..c7a965b
Binary files /dev/null and b/RAG/rag-ingestion/original_data/Preferred Reporting Items for Systematic Reviews and Meta-Analyses The PRISMA Statement.pdf differ
diff --git a/RAG/rag-ingestion/original_data/Rapid Reviews in Software Engineering.pdf b/RAG/rag-ingestion/original_data/Rapid Reviews in Software Engineering.pdf
new file mode 100644
index 0000000..f527880
Binary files /dev/null and b/RAG/rag-ingestion/original_data/Rapid Reviews in Software Engineering.pdf differ
diff --git a/RAG/rag-ingestion/original_data/SLR Student Guide.pdf b/RAG/rag-ingestion/original_data/SLR Student Guide.pdf
new file mode 100644
index 0000000..c92c656
Binary files /dev/null and b/RAG/rag-ingestion/original_data/SLR Student Guide.pdf differ
diff --git a/RAG/rag-ingestion/original_data/Synthesizing Evidence in Software Engineering Research.pdf b/RAG/rag-ingestion/original_data/Synthesizing Evidence in Software Engineering Research.pdf
new file mode 100644
index 0000000..e720f94
Binary files /dev/null and b/RAG/rag-ingestion/original_data/Synthesizing Evidence in Software Engineering Research.pdf differ
diff --git "a/RAG/rag-ingestion/original_data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" "b/RAG/rag-ingestion/original_data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf"
new file mode 100644
index 0000000..2c153fd
Binary files /dev/null and "b/RAG/rag-ingestion/original_data/Systematic Reviews \342\200\223 A Practical Guide for Beginners.pdf" differ
diff --git a/RAG/rag-ingestion/original_data/pizard_2024.pdf b/RAG/rag-ingestion/original_data/pizard_2024.pdf
new file mode 100644
index 0000000..0c8d28a
Binary files /dev/null and b/RAG/rag-ingestion/original_data/pizard_2024.pdf differ
diff --git a/RAG/rag-ingestion/pyproject.toml b/RAG/rag-ingestion/pyproject.toml
new file mode 100644
index 0000000..b1f3b91
--- /dev/null
+++ b/RAG/rag-ingestion/pyproject.toml
@@ -0,0 +1,12 @@
+[project]
+name = "rag-ingestion"
+version = "0.1.0"
+description = "Add your description here"
+readme = "README.md"
+requires-python = ">=3.14"
+dependencies = [
+ "pymongo>=4.18.1",
+ "pypdf>=6.18.1",
+ "python-dotenv>=1.2.3",
+ "sentence-transformers>=6.0.1",
+]
diff --git a/RAG/rag-ingestion/test_search.py b/RAG/rag-ingestion/test_search.py
new file mode 100644
index 0000000..c5cebfa
--- /dev/null
+++ b/RAG/rag-ingestion/test_search.py
@@ -0,0 +1,268 @@
+import os
+import sys
+
+from dotenv import load_dotenv
+from pymongo import MongoClient
+
+
+# ============================================================
+# Configuration
+# ============================================================
+
+load_dotenv()
+
+MONGODB_URI = os.environ["MONGO_URI"]
+
+MONGODB_DATABASE = os.environ.get(
+ "MONGO_DB_NAME"
+)
+
+MONGODB_COLLECTION = os.environ.get(
+ "MONGO_DB_RAG_COLLECTION_NAME",
+)
+
+# IMPORTANT:
+# Change this if your Atlas Vector Search index has
+# a different name.
+VECTOR_SEARCH_INDEX = os.environ.get(
+ "VECTOR_SEARCH_INDEX",
+ "autoembed_index",
+)
+
+DEFAULT_LIMIT = 5
+
+
+# ============================================================
+# Vector Search
+# ============================================================
+
+def search(
+ query: str,
+ limit: int = DEFAULT_LIMIT,
+) -> list[dict]:
+ """
+ Send a plain-text query to MongoDB Atlas.
+
+ Atlas automatically generates the query embedding using
+ the embedding model configured in the Vector Search index
+ and returns the most semantically similar chunks.
+ """
+
+ client = MongoClient(
+ MONGODB_URI,
+ serverSelectionTimeoutMS=10000,
+ )
+
+ try:
+ # Verify connection.
+ client.admin.command(
+ "ping"
+ )
+
+ database = client[
+ MONGODB_DATABASE
+ ]
+
+ collection = database[
+ MONGODB_COLLECTION
+ ]
+
+ pipeline = [
+ {
+ "$vectorSearch": {
+ "index":
+ VECTOR_SEARCH_INDEX,
+
+ # Same field configured as autoEmbed
+ # in the Vector Search index.
+ "path":
+ "content",
+
+ # Plain text.
+ #
+ # NO local embedding generation.
+ # MongoDB embeds this automatically.
+ "query": {
+ "text": query
+ },
+
+ # Number of candidate vectors Atlas
+ # considers before returning the best
+ # matches.
+ "numCandidates":
+ max(limit * 10, 50),
+
+ "limit":
+ limit,
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+
+ "paper_id": 1,
+ "source": 1,
+ "page": 1,
+ "chunk_index": 1,
+ "content": 1,
+
+ "score": {
+ "$meta":
+ "vectorSearchScore"
+ },
+ }
+ },
+ ]
+
+ return list(
+ collection.aggregate(
+ pipeline
+ )
+ )
+
+ finally:
+ client.close()
+
+
+# ============================================================
+# Result display
+# ============================================================
+
+def print_results(
+ query: str,
+ results: list[dict],
+) -> None:
+
+ print()
+ print("=" * 80)
+ print("QUERY")
+ print("=" * 80)
+ print(query)
+
+ if not results:
+
+ print()
+ print(
+ "No results found."
+ )
+
+ return
+
+ print()
+ print(
+ f"Returned "
+ f"{len(results)} result(s)."
+ )
+
+ for index, result in enumerate(
+ results,
+ start=1,
+ ):
+
+ print()
+ print("=" * 80)
+ print(
+ f"RESULT {index}"
+ )
+ print("=" * 80)
+
+ score = result.get(
+ "score"
+ )
+
+ if isinstance(
+ score,
+ (int, float),
+ ):
+ print(
+ f"Score: {score:.6f}"
+ )
+
+ else:
+ print(
+ f"Score: {score}"
+ )
+
+ print(
+ f"Source: "
+ f"{result.get('source', 'N/A')}"
+ )
+
+ print(
+ f"Page: "
+ f"{result.get('page', 'N/A')}"
+ )
+
+ print(
+ f"Chunk: "
+ f"{result.get('chunk_index', 'N/A')}"
+ )
+
+ print()
+ print(
+ result.get(
+ "content",
+ "",
+ )
+ )
+
+
+# ============================================================
+# CLI
+# ============================================================
+
+def main() -> None:
+
+ # Query supplied directly:
+ #
+ # uv run test_search.py "your question"
+
+ if len(sys.argv) > 1:
+
+ query = " ".join(
+ sys.argv[1:]
+ ).strip()
+
+ # Or interactive mode:
+ #
+ # uv run test_search.py
+
+ else:
+
+ query = input(
+ "Enter search query: "
+ ).strip()
+
+ if not query:
+
+ print(
+ "Query cannot be empty."
+ )
+
+ sys.exit(1)
+
+ try:
+ results = search(
+ query=query,
+ limit=DEFAULT_LIMIT,
+ )
+
+ except Exception as error:
+
+ print()
+ print(
+ "Vector search failed:"
+ )
+
+ print(error)
+
+ sys.exit(1)
+
+ print_results(
+ query,
+ results,
+ )
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/RAG/rag-ingestion/test_search_withmodel.py b/RAG/rag-ingestion/test_search_withmodel.py
new file mode 100644
index 0000000..c79be70
--- /dev/null
+++ b/RAG/rag-ingestion/test_search_withmodel.py
@@ -0,0 +1,269 @@
+import argparse
+import os
+import sys
+
+from dotenv import load_dotenv
+from pymongo import MongoClient
+from sentence_transformers import SentenceTransformer
+
+
+# ============================================================
+# Configuration
+# ============================================================
+
+MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
+EMBEDDING_DIMENSIONS = 384
+
+VECTOR_INDEX_NAME = "paper_vector_index"
+VECTOR_FIELD = "embedding"
+
+DEFAULT_LIMIT = 5
+
+
+# ============================================================
+# Environment
+# ============================================================
+
+load_dotenv()
+
+MONGODB_URI = os.environ["MONGO_URI"]
+MONGODB_DATABASE = os.environ.get(
+ "MONGO_DB_NAME",
+ "rag_db",
+)
+MONGODB_COLLECTION = os.environ.get(
+ "MONGO_DB_COLLECTION_NAME",
+ "rag_paper_chunks",
+)
+
+
+# ============================================================
+# Search
+# ============================================================
+
+def search(
+ query: str,
+ limit: int = DEFAULT_LIMIT,
+) -> list[dict]:
+
+ print(f'Query: "{query}"')
+ print()
+
+ # --------------------------------------------------------
+ # Load same embedding model used for ingestion
+ # --------------------------------------------------------
+
+ print(f"Loading model: {MODEL_NAME}")
+
+ model = SentenceTransformer(
+ MODEL_NAME
+ )
+
+ # --------------------------------------------------------
+ # Generate query embedding
+ # --------------------------------------------------------
+
+ print("Generating query embedding...")
+
+ query_embedding = model.encode(
+ query,
+ normalize_embeddings=True,
+ convert_to_numpy=True,
+ )
+
+ if len(query_embedding) != EMBEDDING_DIMENSIONS:
+ raise RuntimeError(
+ "Unexpected embedding dimension: "
+ f"{len(query_embedding)}"
+ )
+
+ query_vector = query_embedding.tolist()
+
+ # --------------------------------------------------------
+ # MongoDB connection
+ # --------------------------------------------------------
+
+ print("Connecting to MongoDB...")
+
+ client = MongoClient(
+ MONGODB_URI,
+ serverSelectionTimeoutMS=10000,
+ )
+
+ try:
+ client.admin.command("ping")
+
+ collection = client[
+ MONGODB_DATABASE
+ ][
+ MONGODB_COLLECTION
+ ]
+
+ print("MongoDB connection successful.")
+
+ # MongoDB recommends considering substantially more
+ # candidates than the final number of results.
+ num_candidates = max(
+ limit * 20,
+ 100,
+ )
+
+ # ----------------------------------------------------
+ # Vector Search
+ # ----------------------------------------------------
+
+ pipeline = [
+ {
+ "$vectorSearch": {
+ "index": VECTOR_INDEX_NAME,
+ "path": VECTOR_FIELD,
+ "queryVector": query_vector,
+ "numCandidates": num_candidates,
+ "limit": limit,
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "paper_id": 1,
+ "source": 1,
+ "page": 1,
+ "chunk_index": 1,
+ "content": 1,
+ "score": {
+ "$meta": "vectorSearchScore"
+ },
+ }
+ },
+ ]
+
+ return list(
+ collection.aggregate(pipeline)
+ )
+
+ finally:
+ client.close()
+
+
+# ============================================================
+# Output
+# ============================================================
+
+def print_results(results: list[dict]) -> None:
+
+ if not results:
+ print()
+ print("No results found.")
+ print(
+ "Make sure paper_vector_index is READY "
+ "and that documents have been ingested."
+ )
+ return
+
+ print()
+ print("=" * 70)
+ print(f"FOUND {len(results)} RESULTS")
+ print("=" * 70)
+
+ for position, result in enumerate(
+ results,
+ start=1,
+ ):
+ print()
+ print(f"RESULT #{position}")
+ print("-" * 70)
+
+ print(
+ f"Score: "
+ f"{result.get('score', 0):.4f}"
+ )
+
+ print(
+ f"Source: "
+ f"{result.get('source', 'unknown')}"
+ )
+
+ print(
+ f"Page: "
+ f"{result.get('page', 'unknown')}"
+ )
+
+ print(
+ f"Chunk: "
+ f"{result.get('chunk_index', 'unknown')}"
+ )
+
+ print()
+ print(result.get("content", ""))
+
+ print()
+ print("-" * 70)
+
+
+# ============================================================
+# CLI
+# ============================================================
+
+def main():
+
+ parser = argparse.ArgumentParser(
+ description=(
+ "Test semantic search against "
+ "MongoDB Vector Search."
+ )
+ )
+
+ parser.add_argument(
+ "query",
+ nargs="*",
+ help="Natural-language query",
+ )
+
+ parser.add_argument(
+ "--limit",
+ type=int,
+ default=DEFAULT_LIMIT,
+ help="Number of results to return (default: 5)",
+ )
+
+ args = parser.parse_args()
+
+ # Allow either:
+ #
+ # uv run test_search.py "some query"
+ #
+ # or:
+ #
+ # uv run test_search.py
+ # Query: ...
+
+ if args.query:
+ query = " ".join(args.query)
+ else:
+ query = input("Query: ").strip()
+
+ if not query:
+ print("Query cannot be empty.")
+ sys.exit(1)
+
+ if args.limit < 1:
+ print("--limit must be at least 1.")
+ sys.exit(1)
+
+ try:
+ results = search(
+ query=query,
+ limit=args.limit,
+ )
+
+ print_results(results)
+
+ except Exception as error:
+ print()
+ print("Search failed:")
+ print(error)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/RAG/rag-ingestion/uv.lock b/RAG/rag-ingestion/uv.lock
new file mode 100644
index 0000000..80af0ac
--- /dev/null
+++ b/RAG/rag-ingestion/uv.lock
@@ -0,0 +1,1049 @@
+version = 1
+revision = 3
+requires-python = ">=3.14"
+
+[[package]]
+name = "annotated-doc"
+version = "0.0.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.15.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "typing-extensions", marker = "python_full_version < '3.15'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.7.22"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
+]
+
+[[package]]
+name = "cloudpickle"
+version = "3.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "cuda-bindings"
+version = "13.4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cuda-pathfinder" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/17/74346b49114779920929ec0ea1361f0a0357262d6020cfdd017f670da638/cuda_bindings-13.4.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7df2dddb81feb15787e8c4a13b7aa3f4c23eabeec7746037e94dbbed065fc6c", size = 6406821, upload-time = "2026-09-10T01:17:12.344Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/77/2f9a38be7399a34e3703b6ce1be60bb2c56611d324750f8d544ff90b0473/cuda_bindings-13.4.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62df23df11074e9833bf348bbcf0b8eec2fcbded4f305c6fbaa3ed067433e97d", size = 6972525, upload-time = "2026-09-10T01:17:15.098Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/de/41197aebf91f6c5f82b35e06e3b4bbe08edddb335c4d6e53c1f1fe542e0f/cuda_bindings-13.4.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0947a6c491a622b076e9afcbc0444eefd9cbe001630558bbcba8d0a90dab7fe7", size = 6243282, upload-time = "2026-09-10T01:17:21.158Z" },
+ { url = "https://files.pythonhosted.org/packages/be/d6/1b697092f53cfd4d721fbbe13e7d66dc72a9d508156af9be4c86607e4373/cuda_bindings-13.4.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e730e80d997b6037566033a79aae3995e1981654054536f512e5c091679c70", size = 6803245, upload-time = "2026-09-10T01:17:23.309Z" },
+]
+
+[[package]]
+name = "cuda-pathfinder"
+version = "1.8.1"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/e6/22df83f82f9bc26cb1c42265cf14d34d4908dba2a0f261bd7b28244acb00/cuda_pathfinder-1.8.1-py3-none-any.whl", hash = "sha256:ae0137ff9e56ea97499bcbf54f5f2778ec25f3266715ac86da192a795af982a8", size = 62552, upload-time = "2026-09-02T16:55:28.64Z" },
+]
+
+[[package]]
+name = "cuda-toolkit"
+version = "13.0.3.0"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" },
+]
+
+[package.optional-dependencies]
+cublas = [
+ { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+ { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+cudart = [
+ { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+cufft = [
+ { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+ { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+cufile = [
+ { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+cupti = [
+ { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+curand = [
+ { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+cusolver = [
+ { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+ { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+ { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+ { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+cusparse = [
+ { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+ { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+nvjitlink = [
+ { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+nvrtc = [
+ { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+nvtx = [
+ { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" },
+]
+
+[[package]]
+name = "dnspython"
+version = "2.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
+]
+
+[[package]]
+name = "filelock"
+version = "3.32.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/38/46/126b1831dca12060d4a8296bf9c4fe5c93c4f22197fa239cb0cc82042bba/filelock-3.32.6.tar.gz", hash = "sha256:a3f55a18af3652a94d8f47d6055df434f254ca1d02ef2524850c6d249ca2512c", size = 225172, upload-time = "2026-09-08T22:57:11.528Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cc/06/4f138f618dbea66803291274f228f01daf29f306fe8b96bc30dab765df75/filelock-3.32.6-py3-none-any.whl", hash = "sha256:3f16ecd0117feae0dfc147e8c62eb5daeccd8bd800378c3ddf416de9b4feb6b1", size = 100189, upload-time = "2026-09-08T22:57:10.182Z" },
+]
+
+[[package]]
+name = "fsspec"
+version = "2026.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "hf-xet"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527, upload-time = "2026-08-03T22:33:13.243Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/62/3c062f593bd92ef4e77a0ef39541e3d82a0a1d3947c8a777a02a13a27828/hf_xet-1.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:70cbb9c896901600128cb9b6f06e132954fbede1db30f31f7c6c63f84cb7c31d", size = 4074584, upload-time = "2026-08-03T22:32:47.364Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/1e/c0ad437dd267a8e435bef594acf781bbc3874ff0b6435b4962d03ecf7cc4/hf_xet-1.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23379c2f9ec8696d952b16414a2bae72cad86a52df869b050698ba60f538c675", size = 3867381, upload-time = "2026-08-03T22:32:49.049Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ee/7c0d7b6ab336167531b1c30af2af003f054af4c749becbd7209ae33a77c3/hf_xet-1.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f7278c05c22fd60cb436cda1269649b3e81db65ecdc8496e5e164aa4143e7b", size = 4453982, upload-time = "2026-08-03T22:32:50.568Z" },
+ { url = "https://files.pythonhosted.org/packages/63/06/ad8eab1c9525246650cbaa821caa3cdbaca734ab1a5b8c91bea09cbd8d69/hf_xet-1.6.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:948f15d3a9545cfe5932f6bd8b440f6ae630aee108f14b7bd6c561f7c2dcc522", size = 4249445, upload-time = "2026-08-03T22:32:52.391Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/26/1eee8aedb0dafc1ab9717dc9ac602cde33361b232dc06803f1f6ed18b58c/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5153e6bb103ad49d6ea9f1b2e230db5a2ea32551ad09a706d2f61d7c7c80d80e", size = 4451099, upload-time = "2026-08-03T22:32:54.114Z" },
+ { url = "https://files.pythonhosted.org/packages/67/57/0b88af1f194ab6c9c650547d9cc06bfeaab836ae4dcdb331676bfb8be95a/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:35cec30d75c6f9eb9c16a77cef68e85a103b72e24d4b473714ec9ff06428bab9", size = 4664712, upload-time = "2026-08-03T22:32:55.547Z" },
+ { url = "https://files.pythonhosted.org/packages/53/a0/26b717a9d1840e8abf48dcec64b5ed8fbe472671d38ad28d30e147132b33/hf_xet-1.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5789835d7c6bc9436962853192082374297fb72d7eff7e7762ec25ceb7e25338", size = 4025906, upload-time = "2026-08-03T22:32:57.391Z" },
+ { url = "https://files.pythonhosted.org/packages/49/f6/4a9966633c6fef83af997e2cff68ec1963676d412bdfd096df2a93b8e185/hf_xet-1.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:75765820ce4700db3750c94acc8fe27c5fae4c9ec000a0dbac3ca082acf97765", size = 3849221, upload-time = "2026-08-03T22:32:59.123Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729, upload-time = "2026-08-03T22:33:00.721Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287, upload-time = "2026-08-03T22:33:02.251Z" },
+ { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663, upload-time = "2026-08-03T22:33:03.802Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538, upload-time = "2026-08-03T22:33:05.287Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520, upload-time = "2026-08-03T22:33:06.81Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937, upload-time = "2026-08-03T22:33:08.616Z" },
+ { url = "https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b", size = 4033128, upload-time = "2026-08-03T22:33:10.171Z" },
+ { url = "https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3", size = 3859359, upload-time = "2026-08-03T22:33:11.725Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "huggingface-hub"
+version = "1.31.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "filelock" },
+ { name = "fsspec" },
+ { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
+ { name = "httpx" },
+ { name = "packaging" },
+ { name = "pyyaml" },
+ { name = "tqdm" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9b/f0/61159db90b5cd275d55516fe27920828e7d3be4053fdbdb27c3f70e5f1ef/huggingface_hub-1.31.0.tar.gz", hash = "sha256:f8e9e710a210613fa5d0f26bba6da05ef4aef9fba5a0f23f508f5ac4d08b6f90", size = 968039, upload-time = "2026-09-10T10:27:22.724Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c3/7f/3f886a625043b77312b80da2f2bf00b5ecbf5a73061af1aa0259cd258c9d/huggingface_hub-1.31.0-py3-none-any.whl", hash = "sha256:9dbb6a503cbe2494ea666695207e7262d410659e09134059deb83e5480864667", size = 798313, upload-time = "2026-09-10T10:27:20.798Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.19"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "joblib"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cloudpickle" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d5/1d/537ab090f302b838943a1b56497dd53059b9a9b46a074936470173a2e207/joblib-1.6.0.tar.gz", hash = "sha256:2ccc96785b12046c08fd6d55839c12857831b54a3c1673ffadd2f04bfc4eda03", size = 327903, upload-time = "2026-08-31T09:39:04.122Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/53/84099323c2ec4be98d935f63c033ac4151ee83836ca1050ede3b3aadf155/joblib-1.6.0-py3-none-any.whl", hash = "sha256:3dbbf9f6e4b592a2357b854608e980fe6390d131d7a82f011a377ef2ebef7aba", size = 306115, upload-time = "2026-08-31T09:39:02.298Z" },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "4.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mdurl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
+]
+
+[[package]]
+name = "mpmath"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
+]
+
+[[package]]
+name = "narwhals"
+version = "2.26.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/35/68/5351e34623d253423240ea7de3f8fc74fa8ab14b1ab3c0ec4ac8997413c9/narwhals-2.26.0.tar.gz", hash = "sha256:6b9cadca82f375c7e4cf584fdc86ca25da54827307a9c58f94547ee6104b82dd", size = 686970, upload-time = "2026-09-08T13:32:08.964Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/40/b5/1b84b2c784db76d69442334bc8b8748c840f13ca53be086f4f250ad4a0bc/narwhals-2.26.0-py3-none-any.whl", hash = "sha256:29326d74f107c347fd1009bd58e38d9f7c7c5b51e6de97bc93dbc325d9038b54", size = 474034, upload-time = "2026-09-08T13:32:07.159Z" },
+]
+
+[[package]]
+name = "networkx"
+version = "3.6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.5.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/70/78/cf416f15dc29375a229d9dfebf8db6e313f291580b39fa1a568b6052bb07/numpy-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a", size = 16998686, upload-time = "2026-09-06T16:25:33.171Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/59/abcc2d8def4fd60eec7d87f92d27c13448ffd9ab14339bcc63a0d7a2fdea/numpy-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c", size = 12013862, upload-time = "2026-09-06T16:25:36.748Z" },
+ { url = "https://files.pythonhosted.org/packages/94/75/4640d2d6e4b64a049e48425a82728a41ef4adb61332d2cba68055774878b/numpy-2.5.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07", size = 5449793, upload-time = "2026-09-06T16:25:39.476Z" },
+ { url = "https://files.pythonhosted.org/packages/96/cd/625b57ae33d4ca560f32cc0b47b4a5922146d9beb998ddf773900d440a73/numpy-2.5.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0", size = 6785176, upload-time = "2026-09-06T16:25:42.069Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/72/12918652e7912ef9751e8694c88820fcd1908e0618cb23f5f3caa6004b7b/numpy-2.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc", size = 15703377, upload-time = "2026-09-06T16:25:45.135Z" },
+ { url = "https://files.pythonhosted.org/packages/45/8f/9beacf79ca7c650688ad0baa80931adb988fe6e6e5d5903c23cc3dbd70eb/numpy-2.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b", size = 16711928, upload-time = "2026-09-06T16:25:48.461Z" },
+ { url = "https://files.pythonhosted.org/packages/09/8d/41d0a56e1ac4c87495c897a211b1368691b7237aadabec8b3b8f3a74d48f/numpy-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104", size = 17059507, upload-time = "2026-09-06T16:25:51.873Z" },
+ { url = "https://files.pythonhosted.org/packages/08/1e/0dfbc5cc251d54e2af790f254d24ec38637fa97ec7d5d11de7ffed787098/numpy-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694", size = 18471002, upload-time = "2026-09-06T16:25:55.233Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/2c/dfa40f6991f8185c8c30ffd023dfcbb11888e823cfab9557b920f3bb7bed/numpy-2.5.3-cp314-cp314-win32.whl", hash = "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e", size = 6180485, upload-time = "2026-09-06T16:25:58.157Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/73/d2c08231e4fde7e415501fd02c715d96e98599b2d8384445933944152984/numpy-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10", size = 12698179, upload-time = "2026-09-06T16:26:00.789Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e9/dcdcc9b95cf5f49815055573aee1b11cfbf5299f38a180e437ded050810f/numpy-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6", size = 10769383, upload-time = "2026-09-06T16:26:04.011Z" },
+ { url = "https://files.pythonhosted.org/packages/49/c4/af8bc08a7ef4e1529a7c0cf24969accce316b783999802089a581ec99272/numpy-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297", size = 12132668, upload-time = "2026-09-06T16:26:07.138Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/ae/0f15eb56d4ec5e13c1f7ff04ff407f997d1acbadb45d3e1f2e2645a8f43c/numpy-2.5.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841", size = 5568580, upload-time = "2026-09-06T16:26:09.828Z" },
+ { url = "https://files.pythonhosted.org/packages/23/fb/c72a8f25d4b6e96c354e7ab45ace3b27dc11e5d6a13b6c7d0cd6b08bf112/numpy-2.5.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891", size = 6882634, upload-time = "2026-09-06T16:26:12.524Z" },
+ { url = "https://files.pythonhosted.org/packages/07/a9/968c90ed2ab15060c338e8137f1215b5a60756ae07328e0a60d1c6734df4/numpy-2.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6", size = 15748923, upload-time = "2026-09-06T16:26:15.092Z" },
+ { url = "https://files.pythonhosted.org/packages/59/08/9df04103947b95e3b6b1f2ed1a70521f325647a31b82da6a2aae3a485508/numpy-2.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211", size = 16746748, upload-time = "2026-09-06T16:26:18.43Z" },
+ { url = "https://files.pythonhosted.org/packages/41/a0/14c8d5fe5b53a334aabb653deb391c0fef49558f491880ea300ed6785224/numpy-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653", size = 17111561, upload-time = "2026-09-06T16:26:22.113Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/a6/d7e96e42f01522e154c32489640f16dfc4f6181d165d05fc3bec8c2c4999/numpy-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33", size = 18513945, upload-time = "2026-09-06T16:26:25.401Z" },
+ { url = "https://files.pythonhosted.org/packages/25/39/3453afb7119d0449ef11c886874120ff180e2c337760e0e2d88f70f1a945/numpy-2.5.3-cp314-cp314t-win32.whl", hash = "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c", size = 6335421, upload-time = "2026-09-06T16:26:28.175Z" },
+ { url = "https://files.pythonhosted.org/packages/99/01/22815d2b19a1a746b1d45205cffebb3fe511a18acb75fba6c88491fc9894/numpy-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435", size = 12896420, upload-time = "2026-09-06T16:26:31.265Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/ee/a7cbba67eeaff038dc29ca8b98a88396c8b0cc9c89d4924f4a27a5c9150b/numpy-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a", size = 10857177, upload-time = "2026-09-06T16:26:34.167Z" },
+ { url = "https://files.pythonhosted.org/packages/45/56/78194492883ff5eec90423fe56a3a44b154da047d88a6307f629713c584f/numpy-2.5.3-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058", size = 16996531, upload-time = "2026-09-06T16:26:37.287Z" },
+ { url = "https://files.pythonhosted.org/packages/11/39/dd55c0af90bbab564b09ae3b0aa60ec5c02b900fa4f1ba23440525c8b32d/numpy-2.5.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be", size = 12012569, upload-time = "2026-09-06T16:26:40.707Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/51/04f67d32e4862b281b1cb84ceeaed3421189a84fb6fb51a391cd6d5009f7/numpy-2.5.3-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5", size = 5448498, upload-time = "2026-09-06T16:26:43.435Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/c9/25b4dc0dd1344ec26c7319e84fd4e9809d2b5628f4e12decd618036e5178/numpy-2.5.3-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90", size = 6783026, upload-time = "2026-09-06T16:26:46.374Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/c7/29285be1e5232a6e7ee3268a33c85843f5a8ee93350c6465cddd66ebbf76/numpy-2.5.3-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159", size = 15697322, upload-time = "2026-09-06T16:26:49.415Z" },
+ { url = "https://files.pythonhosted.org/packages/55/49/bbad5335fb4996a16881f853ff3e0ba582f01720e55c89b1c06b8fc42a90/numpy-2.5.3-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab", size = 16708995, upload-time = "2026-09-06T16:26:53.127Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/e9/1df35483760b04a65ea44669f89dc64f30e5aca098b48ceb8b1310b0e0fe/numpy-2.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2", size = 17052508, upload-time = "2026-09-06T16:26:56.464Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/99/66e54da8265cc8be8a7382bf96edce17aaa2837d6f484432025932a3caa5/numpy-2.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7", size = 18468224, upload-time = "2026-09-06T16:26:59.966Z" },
+ { url = "https://files.pythonhosted.org/packages/01/bc/b5e90a91c115168d793dfd2ad9c69c438c2fe7a13a437e770bc5b078e732/numpy-2.5.3-cp315-cp315-win32.whl", hash = "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034", size = 6179919, upload-time = "2026-09-06T16:27:03.166Z" },
+ { url = "https://files.pythonhosted.org/packages/37/ea/780748fd3985109075514ef8fc64cd25f943e40dde13a6d59141eb268fc8/numpy-2.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae", size = 12697656, upload-time = "2026-09-06T16:27:06.153Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/16/407be69a2a87c8cab64d95975a8977a426a29e138f07e276ec258f0fe4e5/numpy-2.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5", size = 10767601, upload-time = "2026-09-06T16:27:09.297Z" },
+ { url = "https://files.pythonhosted.org/packages/44/bf/a97ffb01e41d50a32a9177aef942a4d0e389a3daf451d04e5f38ef6afb87/numpy-2.5.3-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a", size = 17090092, upload-time = "2026-09-06T16:27:12.907Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/24/136c02f2c2af9a067a84d0c3aa10c99012c0476fa5066732fa4a4202557d/numpy-2.5.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832", size = 12129429, upload-time = "2026-09-06T16:27:16.089Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/6c/b47582d6597789bf946d5efbeb6b9e56fd8bcbd5efc6fbf51dbe1ea31eb3/numpy-2.5.3-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0", size = 5565452, upload-time = "2026-09-06T16:27:19.868Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b4/ef3cc6da73774202d4deae16bb321fd8298a4e0561e3539f8c4be237d916/numpy-2.5.3-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997", size = 6876736, upload-time = "2026-09-06T16:27:22.232Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/24/e3813329498596cb842703dcacac1741612ed9fb9c4e6a3e0c7e2ebbc597/numpy-2.5.3-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85", size = 15745777, upload-time = "2026-09-06T16:27:25.181Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/9e/4e7a07fd0776dc2210cdacf2010be8665194d094defc10c419d7dea794cc/numpy-2.5.3-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4", size = 16746949, upload-time = "2026-09-06T16:27:28.576Z" },
+ { url = "https://files.pythonhosted.org/packages/91/db/01674c0e20335057813a00c2ebd546ed25bff9ed7914f9bced00f8c55d94/numpy-2.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c", size = 17108994, upload-time = "2026-09-06T16:27:31.946Z" },
+ { url = "https://files.pythonhosted.org/packages/45/7a/584c5e71f8d378e57cac0b033891ed65c683ef90573ba4854e8c28203db0/numpy-2.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0", size = 18512266, upload-time = "2026-09-06T16:27:35.196Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/d2/4e1014173aa3c55e6a756e0e567290743a6ab33a288460374d7ef6bcd239/numpy-2.5.3-cp315-cp315t-win32.whl", hash = "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469", size = 6330292, upload-time = "2026-09-06T16:27:38.149Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/b0/ff5658a58199b7bcaad87bf260eef6713d9d42cca4e028f935b4fc5fbac6/numpy-2.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551", size = 12884918, upload-time = "2026-09-06T16:27:40.965Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/0b/b12a2df5d1b774bd9007a6fdff9381145b6223d37f11afc9c37ab0efd9a1/numpy-2.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d", size = 10850807, upload-time = "2026-09-06T16:27:43.868Z" },
+]
+
+[[package]]
+name = "nvidia-cublas"
+version = "13.1.1.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nvidia-cuda-nvrtc" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" },
+]
+
+[[package]]
+name = "nvidia-cuda-cupti"
+version = "13.0.85"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" },
+ { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" },
+]
+
+[[package]]
+name = "nvidia-cuda-nvrtc"
+version = "13.0.88"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" },
+]
+
+[[package]]
+name = "nvidia-cuda-runtime"
+version = "13.0.96"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" },
+]
+
+[[package]]
+name = "nvidia-cudnn-cu13"
+version = "9.24.0.43"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nvidia-cublas" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ca/30/7c257e3d5cb4fecb147b93895c66e29c93f8e76d74b45bb418ff0587c4ec/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:a6812a554a1ff0413e9c52b84c26c050380649ab9615f9c16bded368ce9f421f", size = 650976863, upload-time = "2026-07-02T16:23:39.248Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/ba/791cffd048fe5b044e620df55267e3e95c0e6e07d50b41e377c03dfc910f/nvidia_cudnn_cu13-9.24.0.43-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:71f181cd810e90f9b6023b01186fe82d13d65f0ec098581ee201d39fad769e4b", size = 553099438, upload-time = "2026-07-02T16:27:42.58Z" },
+]
+
+[[package]]
+name = "nvidia-cufft"
+version = "12.0.0.61"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nvidia-nvjitlink" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" },
+]
+
+[[package]]
+name = "nvidia-cufile"
+version = "1.15.1.6"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" },
+]
+
+[[package]]
+name = "nvidia-curand"
+version = "10.4.0.35"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" },
+]
+
+[[package]]
+name = "nvidia-cusolver"
+version = "12.0.4.66"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nvidia-cublas" },
+ { name = "nvidia-cusparse" },
+ { name = "nvidia-nvjitlink" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" },
+]
+
+[[package]]
+name = "nvidia-cusparse"
+version = "12.6.3.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nvidia-nvjitlink" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" },
+]
+
+[[package]]
+name = "nvidia-cusparselt-cu13"
+version = "0.8.1"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" },
+ { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" },
+]
+
+[[package]]
+name = "nvidia-nccl-cu13"
+version = "2.30.7"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/21/a73174c6157101bdf1ffc22b517f76ff0082613989dd9bc8f43e8034caac/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:ca786ffa5a647c75d4d1f5cc72a6c4f537947e2ba8823d7c8aaf768e7a7b9f77", size = 215983881, upload-time = "2026-06-09T03:23:15.633Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/34/c500f90c7ae641b8e0f98965b36b8a7ac79cc8b296e8d251fe3eb592ee54/nvidia_nccl_cu13-2.30.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:cefa7fdb9710efd0f39c5f1be1d61ff6fc9a996c451265bd7fbdcf9455ed4b50", size = 215965170, upload-time = "2026-06-09T03:23:39.73Z" },
+]
+
+[[package]]
+name = "nvidia-nvjitlink"
+version = "13.4.52"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7f/c1/091f198d7f87e31d67fa9680eec8f8e4c6f889881f729b759db36ff01612/nvidia_nvjitlink-13.4.52-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:90401db7e5a580a5067a468b3086e0b65f3b96ab3c42524afd741efc8a0e150a", size = 42452221, upload-time = "2026-09-09T18:01:32.895Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/a9/225ff51e80de170be880cb88e992193bc8134a51059cc0a3952f967f62c5/nvidia_nvjitlink-13.4.52-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a589e3732140839349545efd0db67426523459b12e6952c3c73b6d55900f200", size = 40419746, upload-time = "2026-09-09T18:01:25.103Z" },
+]
+
+[[package]]
+name = "nvidia-nvshmem-cu13"
+version = "3.4.5"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" },
+]
+
+[[package]]
+name = "nvidia-nvtx"
+version = "13.0.85"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.21.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
+]
+
+[[package]]
+name = "pymongo"
+version = "4.18.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "dnspython" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/77/56/8c52e7f57719051748378fe534fbf916440b1eb8fdb8053c9b43003c9865/pymongo-4.18.1.tar.gz", hash = "sha256:d04134ff4ea0e7d14b6e2ef23dbfe06112a99318c9578b213d2dee9060b23dce", size = 2744931, upload-time = "2026-09-10T14:57:11.817Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e4/ef/869a383a4ac3e615599a35340c9bcf02ecbc9a5059d2227b8f96ba6af38e/pymongo-4.18.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:67d56552a940d3553024fdf39535975c4de2ff5b82cf20287ba1ffef028f004b", size = 818559, upload-time = "2026-09-10T14:56:15.241Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/30/a1b0870b71efbd49ee0affa61bf8be067acfdc3f91244443a3395edf9c94/pymongo-4.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae18db68105a0ba7adc190a13cbd73e6c87593bc55a336719f70ad042aa66aaf", size = 819124, upload-time = "2026-09-10T14:56:16.9Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/89/40e08f302e66a3d4da98c7679136f674f736e24c0ac4b306567a0b5cbf0d/pymongo-4.18.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4541273796aa3022b39de892e265aac4a437adcc6e4894396c23258d990f8c97", size = 1040989, upload-time = "2026-09-10T14:56:18.508Z" },
+ { url = "https://files.pythonhosted.org/packages/59/d0/7bb489c716c64c5e3c77df69d5e38ea67374ec2e75e5af413fed263dcc1f/pymongo-4.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3f52a1a793262ef1af15b36535220d194b3ad5ce7564f98c15f3881ffbc269", size = 1050963, upload-time = "2026-09-10T14:56:20.259Z" },
+ { url = "https://files.pythonhosted.org/packages/53/31/09c78af31758923c0b39aa4ec934cb261446619453c7e263ed8d38d4abf6/pymongo-4.18.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab33db087dc52f8f28bdb8680682ebc8fc5fb0be84e89a0853a8dc059fcc06a8", size = 1074988, upload-time = "2026-09-10T14:56:22.057Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/dc/83956070a4313cc9efa676f45d742240540078a44727fc52e3521a15e8a5/pymongo-4.18.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:173a69caa539731284a815ee2fd616442431d12e8c84d970f3cc555d9ac5ef81", size = 1064428, upload-time = "2026-09-10T14:56:23.663Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/05/8817d987e5eadd127a4f44d19ce7a31e507c300d6f1511de99bc73f880f5/pymongo-4.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b46ef2de0b077a2c708790e9ced5c8afb5b9deef1cacba28e8d9a90027e86d2", size = 1049218, upload-time = "2026-09-10T14:56:25.288Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/24/b3c38caeb743a7055a1cc29afe2edee06206fe2406e90b3e0d4900242df0/pymongo-4.18.1-cp314-cp314-win32.whl", hash = "sha256:d9be9221bf2db1562d490bb67522ce2f7378513bc8bcc157e824a0bb3d87eba0", size = 816052, upload-time = "2026-09-10T14:56:26.935Z" },
+ { url = "https://files.pythonhosted.org/packages/02/0e/e1a8406b5361b06043762b7f50a13a3d9c04b2ea8202f39c8cde737a8f13/pymongo-4.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:134a8f787360c3cea18cbbc126bf51cd233f1789db8099f7081ace5b1cff7e22", size = 821969, upload-time = "2026-09-10T14:56:28.571Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/ff/82420a828245bb3bf36dcb6a59eebd914fba78dac87985c48145d03a405b/pymongo-4.18.1-cp314-cp314-win_arm64.whl", hash = "sha256:088e78ab7340d7ab4c14cd7a96852f9ea3b51c25fc9fb02244715d21f57ec2e5", size = 816505, upload-time = "2026-09-10T14:56:30.184Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/b1/fa1c5cd5ea032ffbce4f4c7876403405405c9cc9de7abba33c78254bd278/pymongo-4.18.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9bf92d46b15fb8fcfcd517442b0b90055924d835fef77e574ff4678b40be49ce", size = 821533, upload-time = "2026-09-10T14:56:31.96Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/cf/41c06f9cce2035bf061248d1576439bc60701852f75118fd2b5a3408abd0/pymongo-4.18.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:58c3392585c5823ad90e318b6e7c0fb0709763631fb06d81de45166a1844ea18", size = 821971, upload-time = "2026-09-10T14:56:33.83Z" },
+ { url = "https://files.pythonhosted.org/packages/53/3f/c698242cc5013a0d99099db622813c480e23dac7c8bad3852610fb5a5145/pymongo-4.18.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4b8f7b212ab86552db2f35b2779cc290561a341b73fba1ab1030ed9d48e1b1e5", size = 1105379, upload-time = "2026-09-10T14:56:35.497Z" },
+ { url = "https://files.pythonhosted.org/packages/37/a8/02fc8305155d46651c0e3980998d3da881db287866be8a5c98d9a0fb67a2/pymongo-4.18.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2ef166249993afd94b704bd25af27104fccb259580960a41355f8d7869496ed", size = 1125164, upload-time = "2026-09-10T14:56:37.216Z" },
+ { url = "https://files.pythonhosted.org/packages/53/0b/7fcdda4b6e07497ce63d59a932ce193e3da098c309cb18a2d55ed7c99f84/pymongo-4.18.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adebc03de2d1520bb163953361779288316e4e9c4b66fe4ce9e91d4183e313b8", size = 1144618, upload-time = "2026-09-10T14:56:38.929Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/ce/b9213ece00a38f742ca4cd6afb812d73d4cfe857b1018e90f803d72c4b9f/pymongo-4.18.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9ae9c28e6c290c2524c3288a0a6e27dca958daeeaba2f36a6a4ee97c44e5f49", size = 1136374, upload-time = "2026-09-10T14:56:40.647Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/42/aa057e91930209d731ec3fed03edf32cf6aa59838f7766227ee219142574/pymongo-4.18.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5907ccdcc2f7925100b4bed8069acd0d93156e5cf013a8e15aa5932819ce3d5e", size = 1117151, upload-time = "2026-09-10T14:56:42.561Z" },
+ { url = "https://files.pythonhosted.org/packages/00/a5/86777256266850eb2217d6c8c8f382a213a1773c144383d51f61bbb2e651/pymongo-4.18.1-cp314-cp314t-win32.whl", hash = "sha256:9da74b79e382bb37311b9bd5beb89f1369a1c3e27e7c97bae79661cfdf3893aa", size = 818806, upload-time = "2026-09-10T14:56:47.024Z" },
+ { url = "https://files.pythonhosted.org/packages/49/6d/08e9256de9fab497465e7e6589fab4d40ca266dcceb88d3acdd0947095e1/pymongo-4.18.1-cp314-cp314t-win_amd64.whl", hash = "sha256:016d21e7fbf55ac89f419daa48da95314896205d902ed9aa4058f26a436e789c", size = 826141, upload-time = "2026-09-10T14:56:48.886Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/f2/12afe9d59fb82b60832356f9fe8f454a99371c0e0a42f0ec7856f5c893fd/pymongo-4.18.1-cp314-cp314t-win_arm64.whl", hash = "sha256:960c61ed86a316d3636b1f184006d3cc2b972b8546186ae8e2c8324e397632ff", size = 817669, upload-time = "2026-09-10T14:56:50.602Z" },
+]
+
+[[package]]
+name = "pypdf"
+version = "6.18.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4f/c2/e537e78a8282b51ed47031566be118debbc34c04782a9afd8739672eb07d/pypdf-6.18.1.tar.gz", hash = "sha256:2441fc839053745638d9b9ddb38ed66beed8d611f47a3036c9838fc4e3074dea", size = 7027577, upload-time = "2026-09-11T10:10:07.616Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/13/645df3995075112cb3cce15e8797c205f0f88fb50acc11012b84b071bc22/pypdf-6.18.1-py3-none-any.whl", hash = "sha256:ee93a2665670ecf57ee81d197a4ca548f3dc15f9cefc56e59b8140866aaa3de5", size = 394327, upload-time = "2026-09-11T10:10:06.103Z" },
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+]
+
+[[package]]
+name = "rag-ingestion"
+version = "0.1.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "pymongo" },
+ { name = "pypdf" },
+ { name = "python-dotenv" },
+ { name = "sentence-transformers" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "pymongo", specifier = ">=4.18.1" },
+ { name = "pypdf", specifier = ">=6.18.1" },
+ { name = "python-dotenv", specifier = ">=1.2.3" },
+ { name = "sentence-transformers", specifier = ">=6.0.1" },
+]
+
+[[package]]
+name = "regex"
+version = "2026.9.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b9/5c/f403115361de25809e8f785686ec7096e30fef73be9ae35aa51da4e80abb/regex-2026.9.10.tar.gz", hash = "sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d", size = 417072, upload-time = "2026-09-09T21:00:21.521Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/ed/98e9b07d8bb9c765d07774f0b2c19b301b96d51f44630fea48951051c94e/regex-2026.9.10-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd6bd89b9fc06018d35851cab0240adb7dd84d51941b19f6574ac90cd54e3ae5", size = 496662, upload-time = "2026-09-09T20:58:05.118Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/a8/9dfe9be48378b47c5a8f04b0f200ea225f9ee0f8e93f010433f661a37878/regex-2026.9.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ef4c0a9dfdc90581b90b1b95a8c3d1557f8ff8f5a2a53536d26314de699d1468", size = 297115, upload-time = "2026-09-09T20:58:06.822Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/e4/5d1f005a3825ec49842ad349061c1c26e6d42f47ccf105e6e5aa6aeed392/regex-2026.9.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14caa05ce39ec70437af5aac8814c50ee6628f4a90353871c059692f448a164f", size = 291896, upload-time = "2026-09-09T20:58:08.675Z" },
+ { url = "https://files.pythonhosted.org/packages/be/15/44ce83fca50c6058f42b62fa8300a8030eea7e4e5a973a2dd33db0f557fb/regex-2026.9.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3264132d576847ab5f88bb83e7debe67854bf165b3ea613bd467312b6099536a", size = 800534, upload-time = "2026-09-09T20:58:10.339Z" },
+ { url = "https://files.pythonhosted.org/packages/af/6e/a62e070a5a033643b287489576f02ae6a9c584c337d62349e340a2b4d001/regex-2026.9.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5cef9f3d14796500ea834c41dbe688f1f6b23c7024dc23e8a794d7ebaf5d71d0", size = 872038, upload-time = "2026-09-09T20:58:12.186Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/06/8b8e2483949b1329df10c5b615e85d332066deb426b11332b799629b9201/regex-2026.9.10-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d278ad30ec83b6b9202685b0f80b741a51ea3ca7f0595ebda96e7628b6398876", size = 918927, upload-time = "2026-09-09T20:58:13.903Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/64/9b56f69100d3afdbc9c4fa6e302764f9cb717fbc06a9d50558d98ca89cd2/regex-2026.9.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb018cfe7144585fc83882405906ff84994a2d154afc2509ecc7752c51f864", size = 803694, upload-time = "2026-09-09T20:58:15.949Z" },
+ { url = "https://files.pythonhosted.org/packages/07/43/d00d59a7c8fd0e070ae8457a8743597f45ad9682b100f57b9c9c405fbdfd/regex-2026.9.10-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6fd555fc9abef50c530869690b2daca054c8811a7aff632d11f9a7b2590b2742", size = 777770, upload-time = "2026-09-09T20:58:17.628Z" },
+ { url = "https://files.pythonhosted.org/packages/46/6b/a11d0446484efbc9eb67abec133f254c6d66a1568b8f3fb36d39a73a1129/regex-2026.9.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8d5c4518235a2ec1611e57af85fa488d529c1106aacff12adadcedf8687012cd", size = 791234, upload-time = "2026-09-09T20:58:19.476Z" },
+ { url = "https://files.pythonhosted.org/packages/12/09/bcd24e78b373fd4f98090caa43eade439223f6703b909be79b9efd9ab0ab/regex-2026.9.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:175cf49ce7a994c88b8f15e3cb17cdb66a48ebb2d36de736b8205033db950f89", size = 866259, upload-time = "2026-09-09T20:58:21.683Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/c6/c57ba5e94222a813260af4c17ced92d40cdb44737eb6b50979688310b6a3/regex-2026.9.10-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b71649169a9fcf30b395ee01047fa7ad6654a4c900ca75b23c04dedcce6a1f8c", size = 768219, upload-time = "2026-09-09T20:58:23.842Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/a2/3820037587d00901ace96c5864335c2cd1b899d5263ea0dd2261359e0bee/regex-2026.9.10-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8ba1f78bd4fef2d8f84b894ec28ac3481afe6cc07aaa253ad4717ef7b3fe6bcb", size = 858582, upload-time = "2026-09-09T20:58:25.607Z" },
+ { url = "https://files.pythonhosted.org/packages/47/f0/f9a838ca6219ae4821de0175e4548db73ef56de5ec08d032fb427732fa07/regex-2026.9.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:217e98ba5fc8908ed8ffd4ebac04753a0c831067cbfb495b9821b94cc61eaa76", size = 791405, upload-time = "2026-09-09T20:58:27.406Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/bf/67d71cc4e13ae2e0022d21243ca069e0868701a99eb29cdecd13d356e694/regex-2026.9.10-cp314-cp314-win32.whl", hash = "sha256:b298cdc33c5cc6969ff07f0fba19cc73e0fd8576373c50935feadaca2f6b4405", size = 272702, upload-time = "2026-09-09T20:58:29.116Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/38/40a93e72703a741235115ed1b1e5f6b869917677b7643005034ce1611d70/regex-2026.9.10-cp314-cp314-win_amd64.whl", hash = "sha256:c32818b28bcd153b25b63038348a9fe9b9fbcddb60df43f204c3ab55eeb57f77", size = 281170, upload-time = "2026-09-09T20:58:30.899Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/ac/e95387f00617c16bb41b786d900bacd17eab88afa81b4f263df532b3a731/regex-2026.9.10-cp314-cp314-win_arm64.whl", hash = "sha256:75242f44a3e283106077be4ab717bc535e4701c9d54ad69e195945c22f137a1d", size = 281511, upload-time = "2026-09-09T20:58:32.594Z" },
+ { url = "https://files.pythonhosted.org/packages/39/e5/a4b12262edc488a8a7a95b672db317dd8aa9bf2fab98297f9c91bb11ad4d/regex-2026.9.10-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2dd9286093c71afc8f55ef035c5b9d2776641fd72c6535f1febc92d0b0be9666", size = 501128, upload-time = "2026-09-09T20:58:34.306Z" },
+ { url = "https://files.pythonhosted.org/packages/55/c8/9ca31c0fa5197ded8614c8ae0e105bcff2d979025ffa3c75580baa334e2f/regex-2026.9.10-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:71879292c9c7ac67b1680345b16daba1be937cb027362cfa04e68f65db2dcfdd", size = 299428, upload-time = "2026-09-09T20:58:36.296Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/d1/ee7662561735f90475443c3ca1977e5cecaeaa8f29620dec75580aebc839/regex-2026.9.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ccd139b2061132e7b265cfb4b4721baeb9f8928b81415304abf1ec7e3181c26", size = 294494, upload-time = "2026-09-09T20:58:37.964Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/b1/333168e45ed6cfe71f6d17e21e5f54725f44d171f84edd12469a2f739227/regex-2026.9.10-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7327795089ddb44912dce1434e1d7244be2e9fb48fcc2d6782936af7a3062db", size = 814925, upload-time = "2026-09-09T20:58:40.438Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/a5/df1b38536d0a3b24a030eb4130ce98b403cc925220ff530f5313a7c436eb/regex-2026.9.10-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff4d7b14ea19e50c8d9d6d83f45bd9b45cbb624c07ac1fa54db0a019049abed7", size = 873323, upload-time = "2026-09-09T20:58:42.349Z" },
+ { url = "https://files.pythonhosted.org/packages/93/ea/aa71fe62dd63a8336bbdec1ff002a6c53b40ccfca95961c18ee4f09bf03c/regex-2026.9.10-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f0407474ffac8e5e89d93ca41d60891e29f0ab8423eb66ff292d850a86a0843", size = 923080, upload-time = "2026-09-09T20:58:44.488Z" },
+ { url = "https://files.pythonhosted.org/packages/86/38/49f8d6fd34fc1a9c75b5b96364ebdde702a8144e5ed63d2213c58d454c27/regex-2026.9.10-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ad10a135fa0b4e4a462a61d07c6654d7518cfdb5cb8da08f9ff7d61384af1fe", size = 821284, upload-time = "2026-09-09T20:58:46.56Z" },
+ { url = "https://files.pythonhosted.org/packages/32/45/91a977c96d4be13d1ade8208c260c26841c836d4c91745e1828a30589070/regex-2026.9.10-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9fbd2e5d8002dc49a6129fb321ec51c57a025e752ed525ddce0ba9223c4350a7", size = 789256, upload-time = "2026-09-09T20:58:48.473Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/79/4d110bf01bf9651bf9b3f88a6d9fa7e643e0586921a18432b25a478edbfa/regex-2026.9.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4a761ea45f2ad74c575ef5850ea514cef97302a552d3c7c9d1a1a870d4661d6c", size = 803722, upload-time = "2026-09-09T20:58:50.286Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/67/b587a0d3bbac2635309ed9c40c197120c39e1ec0afb8813cbed1338fdd75/regex-2026.9.10-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:75aa39d3f4f1650eea84e46b0d8cefe77dd5478c10e3d0aaf0b0f00493475a7a", size = 870085, upload-time = "2026-09-09T20:58:52.224Z" },
+ { url = "https://files.pythonhosted.org/packages/07/fc/0827bca20ddba1d70fa5111a2a64e6b6b38bfdab43fc435022b54172a111/regex-2026.9.10-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f5c629df03adec31ee505dda3c8988f106c9390e4cbd343600036eb8b3d6724f", size = 776970, upload-time = "2026-09-09T20:58:54.478Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/03/ab9d08d30568ca868791bfb99551db60b947f05e2e65dcd1261e87083c21/regex-2026.9.10-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3a66e40a1a20de96a2fee00ed67e11012b62d85b277688258677fd19997addb7", size = 863611, upload-time = "2026-09-09T20:58:56.432Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/6d/8063ae86b543ae878a7d6e7ba21ebf0af4af06161230e0012e2d652320c6/regex-2026.9.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:968c1e33edd9a104d1bf24c8d476c72de7e3839ae7f894b37e9e4f4739fdeeca", size = 804064, upload-time = "2026-09-09T20:58:59.066Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/63/b19305c4b8d3d7867699f7d83c43550273d91a2f31793452d87edb1d259f/regex-2026.9.10-cp314-cp314t-win32.whl", hash = "sha256:fbc4e2f3cb7ce8436154e6483079e7d35eeb321a952fa936e180300630d8b873", size = 274607, upload-time = "2026-09-09T20:59:00.942Z" },
+ { url = "https://files.pythonhosted.org/packages/32/b8/1695072a512a49060294024e23b945eeb87675b02c06e53a92a1b42b0bfd/regex-2026.9.10-cp314-cp314t-win_amd64.whl", hash = "sha256:c37fa93bf18bf4f90b01c0fa9f11ea567ee4b7dd8bf96e63663e5edc37aa38cf", size = 283944, upload-time = "2026-09-09T20:59:02.876Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/9f/65bac17f39991a67e22f8b3c849fdc02a56147c97029b5351494341959b4/regex-2026.9.10-cp314-cp314t-win_arm64.whl", hash = "sha256:ffc2da104e43db716ce30cef9f28049a1faa6aca385dd8771b033268d0730b07", size = 283780, upload-time = "2026-09-09T20:59:05.009Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ca/1d1f83bc2f8fff4f186266ac82d73254e686565530cec9ab5228fb5c63dc/regex-2026.9.10-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:6afcad14310f1311d077553ed374b42a5e538f85a8c884b4e38e52de091c8077", size = 496869, upload-time = "2026-09-09T20:59:07.051Z" },
+ { url = "https://files.pythonhosted.org/packages/33/42/4217510286501a2ebcd372b781b4754ac961e043fa13ef8dce803c44d89c/regex-2026.9.10-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:3fb4ae8cf83ef4e9addd43b2da31a9f45be816a8036fae8af59c8998b72718e2", size = 297121, upload-time = "2026-09-09T20:59:09.007Z" },
+ { url = "https://files.pythonhosted.org/packages/55/a7/595468ed0bbccd94be92c6b5d67736ba128b204d942429a8485c2693914d/regex-2026.9.10-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:f7d4656e17ab736e9415a6442a345bfc97bb8b7dcce47884bb74a37f70f08d0c", size = 292139, upload-time = "2026-09-09T20:59:10.859Z" },
+ { url = "https://files.pythonhosted.org/packages/29/1c/ac92c123e0ab9bea75a904272171b356940bd6139e4a35de44f2254dca8f/regex-2026.9.10-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ba3bab0c45079735f55ac61526774de1d84bc4a0333cc554e1a4ab74913924", size = 802375, upload-time = "2026-09-09T20:59:12.823Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/16/d349f6fa9f908162359004e4f067353a0146e8074d24989490778244be44/regex-2026.9.10-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff6b3267318661dfddf6b3628663e00e5946bd0a5c8fa678537a1401f0388f91", size = 872328, upload-time = "2026-09-09T20:59:15.352Z" },
+ { url = "https://files.pythonhosted.org/packages/df/81/251b5aef23147057e926346fdb7c8c352d0568f65a492e4e9eb6120f6446/regex-2026.9.10-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:030fa9e23624e39b3b94e46b90a5abd1a1678eb2f58fcdd3fd6c27526bf91c7e", size = 919594, upload-time = "2026-09-09T20:59:17.31Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/6e/1f25319dc1b9cf4b7ffa303f3d16ca53260fe92aff45e81aa1b3c6c7cba2/regex-2026.9.10-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fbc8314436353e097c050e11b01a6c11433579437ed0579730157676ef59e2f", size = 807394, upload-time = "2026-09-09T20:59:19.328Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/fc/3da6b3dffddf12d5e96cbbe6f5e65ebcd64f92e3388a6690a53e0878232d/regex-2026.9.10-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e6c0b5ec6ddee4032247585dc491b0fa58627745b66a705728703a3f0331231", size = 786018, upload-time = "2026-09-09T20:59:21.592Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/6f/1cc86dafddc912ef44c5ccd4f729060be8c6735600932664eb6d0e25469b/regex-2026.9.10-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:bf29611e5376fec8f795879bb5c6153a76c3a292573d173c26784042b01eb840", size = 793504, upload-time = "2026-09-09T20:59:23.734Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/cb/11692e29388d006211163627fcefa0c79917ab9f94d46a1b2254fe5efc81/regex-2026.9.10-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:ec8855f08c17895a26fbf5f19ed829722e19b34a96629e49a43c92974924026b", size = 866766, upload-time = "2026-09-09T20:59:25.798Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/78/3631df969830f94d83fcfc5fc71a7b39caad905e18f7b17350a94135d625/regex-2026.9.10-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:94c5ce3bc41d226b4eb89ca3f842b2e28c031487fb1f34eb2153d98235831325", size = 775825, upload-time = "2026-09-09T20:59:28.51Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/8a/762892a8e3e21d119eacaffde848aa247e6cf4e1d90c46011671e86b1b9e/regex-2026.9.10-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:9ce239acb15843ab03976626af810a4424b0409689ec2bbc52088ab5479ab487", size = 858901, upload-time = "2026-09-09T20:59:30.656Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/f0/78048fada5c2f61d795efb26ea24f820a5564c4aa26574920284d45cd656/regex-2026.9.10-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:c22df8dd6373bbe3898e77429ffc85594300e39d752fd0e68a31e59d37899376", size = 796208, upload-time = "2026-09-09T20:59:32.852Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/58/632681f7b9aaa3d83b40e5862ba46364a453c8eb2bc7d43fece3dc31f972/regex-2026.9.10-cp315-cp315-win32.whl", hash = "sha256:1aa309ab7ba89a62d6cf70dbd38d4176440bce3c7001ab86256704cf4c18c6eb", size = 272704, upload-time = "2026-09-09T20:59:34.927Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/64/81cce28754c37037b1fe740b6d7a556d51d97cf935ea4547bfab104f43f7/regex-2026.9.10-cp315-cp315-win_amd64.whl", hash = "sha256:58da726d3e766c0b3f5a3997dfaf0275898a1107b8191cdd6b0437fe45fd817d", size = 281182, upload-time = "2026-09-09T20:59:36.86Z" },
+ { url = "https://files.pythonhosted.org/packages/33/28/5a13a340c9c759e863a0e7f765d323601d03d6538c8a627ed628662e083b/regex-2026.9.10-cp315-cp315-win_arm64.whl", hash = "sha256:75f9297b16fcb588a1f8d8a55dabef3c0c20b0c7bac43c87ceaaaf1a825c12f4", size = 281512, upload-time = "2026-09-09T20:59:38.875Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/38/a3caebcd5105be90708071db20bd261b0961b8ea4fe5e8be45c2632519b5/regex-2026.9.10-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1270cdec69248592bbe38a0b263ed58d907b891bd2b93703e225c317e421bda1", size = 501336, upload-time = "2026-09-09T20:59:40.987Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/b5/ec8887b2658bf0a5df143c7c1fcd562b0abd2fa208c04ebf15c6607c9bb2/regex-2026.9.10-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:681ed38664b64c6617d3c3c332018d1948c77e139c5ea667c1886efa671e426f", size = 299318, upload-time = "2026-09-09T20:59:43.039Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/58/84724a9eccf6e8cd46f7e4534576093e2476a5eeb55e2453aa6606e86059/regex-2026.9.10-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:8e127d9a80cbf1c3276bb465c6d047e8705e97b58c2b8f2f0c0a69c336b44b37", size = 294847, upload-time = "2026-09-09T20:59:44.954Z" },
+ { url = "https://files.pythonhosted.org/packages/50/03/70ccc5e53905984abf8eab63eebd3ce740522ef8c8d392622b64d79ef290/regex-2026.9.10-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:990797e765d89a423880052c68b61c31afe701de94a8c060f61c40605ca6c727", size = 814359, upload-time = "2026-09-09T20:59:47.194Z" },
+ { url = "https://files.pythonhosted.org/packages/29/53/40f7a11ec547e4a947883c9d5e8a075f6d4f59af2b8dbcaa8bf5b504aca0/regex-2026.9.10-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e5e4a6e0734a685d13b9685622bb503bdbb2927f8b0df025a5085f0ea067475b", size = 875586, upload-time = "2026-09-09T20:59:49.537Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/9d/83f3e022d99ce601727c4ef5f7b527753901ce4509ed89a1bb6a2263380a/regex-2026.9.10-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:63bb62cf62217dc38c8a6b2b61b165b0e4eb8fa93b0aba12139251c0986a8fa3", size = 920990, upload-time = "2026-09-09T20:59:51.776Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/64/dfdb367d8f4f5c9b8ccb4b59789c2ce996f2c69c3b8192aa986fe7d92ec4/regex-2026.9.10-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb76a9c4e07a6a47849726af0ed14c41741a182f097f134a8cf29c1bc0f4dde8", size = 818660, upload-time = "2026-09-09T20:59:54.428Z" },
+ { url = "https://files.pythonhosted.org/packages/88/a6/fb7d0b64487913845834f319aa84f8377d55305958a5db7e19c128798366/regex-2026.9.10-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31e4df2b11d48f61d511019bc1ee9b477055f17c352b68fe72db7a98b14d603c", size = 794976, upload-time = "2026-09-09T20:59:56.799Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/1c/23484edaae387ea0d31f4d414463211e3516c8aa210ce8d0640f5aff3502/regex-2026.9.10-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:cf377960d2ac37d987394a9dbaa75e91338c41a46d41e1d25e90125e7b3ee2dc", size = 804081, upload-time = "2026-09-09T20:59:59.39Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/e1/e30d138f13aecec99ea9aecef7e31563de6ec6a2f5ce1d65fc506aee33fe/regex-2026.9.10-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:c8fbd9cb30c68c1686b94029b9ef845d5870d3d65baf66cb126b676849b9d72b", size = 870430, upload-time = "2026-09-09T21:00:03.304Z" },
+ { url = "https://files.pythonhosted.org/packages/53/dc/81f9ce86f7ae4f57901543597c95751fa01c41a672ec1636dd913f8a000a/regex-2026.9.10-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:53e182b6b04d0011909b47d51a2d72d908de07c7b1c7f16b3adda2204d723bc1", size = 783327, upload-time = "2026-09-09T21:00:05.68Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/1b/d7bf8f91534740f6a8ca17e5ac9c5337903baf527c8de240fbac1bbedfd0/regex-2026.9.10-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:0aa7589394230e0f0a422ab6b90841ff12c87e855e7aaf75d192a54a5f124548", size = 860874, upload-time = "2026-09-09T21:00:08.41Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/1d/52cc88364aca7c9013dfe9abe0fea67f8d394efe384d07798300a6e2f27d/regex-2026.9.10-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:1b891f77554bff991804cee24b78b40789f7d5993a24c7907bc7025fd2a70c8d", size = 806705, upload-time = "2026-09-09T21:00:10.768Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/51/37a194df7707f92f33173260ba7b221d4ec376419a53babaec50019a2804/regex-2026.9.10-cp315-cp315t-win32.whl", hash = "sha256:5bef622850cf760154719d4e0d74b0a855962432995168e250069899ae12fe8f", size = 274780, upload-time = "2026-09-09T21:00:14.003Z" },
+ { url = "https://files.pythonhosted.org/packages/77/12/3227a52970d90908b230f15b2c86df903f49ac72c9eedf4f6e8b5bb5e1a7/regex-2026.9.10-cp315-cp315t-win_amd64.whl", hash = "sha256:07b45ba5c94b8fcb30cb6c56a11f715c57533a3017964504322ea52690a27b72", size = 283936, upload-time = "2026-09-09T21:00:16.275Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/bc/c567c5a61671f04d30e83f20b496b465432879196574d051007285576205/regex-2026.9.10-cp315-cp315t-win_arm64.whl", hash = "sha256:f70b9f0e39c2dba1d9da6bf7ef7c377cad7277f8440e9a69be05ede529ff024c", size = 283747, upload-time = "2026-09-09T21:00:19.113Z" },
+]
+
+[[package]]
+name = "rich"
+version = "15.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
+]
+
+[[package]]
+name = "safetensors"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" },
+ { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" },
+ { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" },
+ { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" },
+ { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" },
+ { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" },
+ { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" },
+ { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" },
+ { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" },
+ { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" },
+]
+
+[[package]]
+name = "scikit-learn"
+version = "1.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "joblib" },
+ { name = "narwhals" },
+ { name = "numpy" },
+ { name = "scipy" },
+ { name = "threadpoolctl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d2/eb/eaf5e07fcc0da7149b0e084f24e54edd7441b9a89ce7e034032ae97fe3a0/scikit_learn-1.9.1.tar.gz", hash = "sha256:629cada3e33e2b9bf376cdc7614a47a4140b8aedc1d836579e359736fbd82977", size = 7786908, upload-time = "2026-09-10T18:34:04.679Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/64/e3/b58e45082dcf3dcf0eb1192ee03545ec43d8c98441dfe20e88afca8442ce/scikit_learn-1.9.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5d117952769b563067656784e03c75a2d8235a7a05cf7fffa78a311e75aac08", size = 8747414, upload-time = "2026-09-10T18:33:10.698Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/ed/d68115577c8b42b0442ebd8180945d4008880a33176094640e00b585128e/scikit_learn-1.9.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:8893bc6331f60f18d4ac75e12ed356e2dcf6a564bf767918b5b7ca54c8c8be49", size = 8279175, upload-time = "2026-09-10T18:33:12.846Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/6c/06c7eb61a438e389cbf3f7210897069bec5a883dbe03c189ae792781e11a/scikit_learn-1.9.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5492cf2df5226691c32611de8734bcf42148c6547ae53c7f4e6b847793addc0", size = 8881706, upload-time = "2026-09-10T18:33:14.741Z" },
+ { url = "https://files.pythonhosted.org/packages/86/4e/0bab75490ca4b85fad8388739c7ebc71d9db553f8c69e39943ee8db0aaae/scikit_learn-1.9.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993d332ff80e62efae9e39603b7e872297c418d780f01a01855269a3489c950f", size = 9152030, upload-time = "2026-09-10T18:33:17.436Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/13/31c6f8ba1b7eecef9dd9558576c752d2ec5785fd0e456bb9fc59305be23f/scikit_learn-1.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:ca9051447455dae341d4d591eece7deb2d8e3d1020298fc87a81fc51e4da8f53", size = 8377024, upload-time = "2026-09-10T18:33:19.941Z" },
+ { url = "https://files.pythonhosted.org/packages/62/e6/6d3cb8a45f5228f915acd66b819dd6b8232ccbe24532f51d278e3991df31/scikit_learn-1.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:90de6573f733a9fb79476ff1371af52a397d41c8b35f9146e20923db010d67b6", size = 8014196, upload-time = "2026-09-10T18:33:22.126Z" },
+ { url = "https://files.pythonhosted.org/packages/66/6e/6befb2d5961490d18d9dbc16a5df37aa121d08bc9893316a6a363977a903/scikit_learn-1.9.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7b5cad1624de8b75e5b9ccb7b0ce1ff1d01306340a3efc56d5529c5ba92392eb", size = 9066066, upload-time = "2026-09-10T18:33:24.396Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/18/11271f2f7db337db01f598e358721b1e83989407131272e5dd64214c28a8/scikit_learn-1.9.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:d137ce8a6142029fb5c35bd82f470c40cd9e760e5e2f7694b362c497c4ab3fa2", size = 8647654, upload-time = "2026-09-10T18:33:26.649Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/04/9c15d201e1b6a2e81b8215865df7646c5a360f560831769c8dc92ac1ab9a/scikit_learn-1.9.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66f852f7325b5070bc28329005aca76055a2def78faac039548ae889aeaa45a6", size = 8889070, upload-time = "2026-09-10T18:33:28.95Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/5a/4cb6c85160af4a639e87a3b7bf8b1c25cfc3b504c5af710ca416a6dcfc5f/scikit_learn-1.9.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:748bcb0a4cc04aec470652c9e5ec68450948e867387e7dfade647107ade68d25", size = 9131227, upload-time = "2026-09-10T18:33:31.008Z" },
+ { url = "https://files.pythonhosted.org/packages/00/0e/361440972ae3d19b90ea88a84791138a51de0e8432a770ba741b2c8d9ced/scikit_learn-1.9.1-cp314-cp314t-win_amd64.whl", hash = "sha256:38cd925e893e5539be704d5edc64dbe081aacdab6b89d8c2977c1f6a7a453ce5", size = 8683001, upload-time = "2026-09-10T18:33:33.188Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/8f/a9f405c5c0e2df6f343a871b40c97fb32969e3ccc38e3033dd118f3c261e/scikit_learn-1.9.1-cp314-cp314t-win_arm64.whl", hash = "sha256:b01e5b01735d38474127ca3f49319b592506225a87793b27559816b5c75cea39", size = 8257996, upload-time = "2026-09-10T18:33:35.343Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/c5/74a83ea39cef7cd07f53e06cc1cf51e79f35df74f81db956835d59ec34b1/scikit_learn-1.9.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8dec64f31a6e0ec826aca6c1b39a51e16d946e400d4c0904316f3ca72ccfb825", size = 8747991, upload-time = "2026-09-10T18:33:37.4Z" },
+ { url = "https://files.pythonhosted.org/packages/64/c9/cc93e8a7fe204e43d70e96e5eb89871643be20025eea05eb4fdaf19afe39/scikit_learn-1.9.1-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:e1b468241f4a7a9a7a0d6479ad3cc47681cc151a4046c530f2777c3d68f08942", size = 8279340, upload-time = "2026-09-10T18:33:39.705Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/61/0c6080f0d356fb966053009e7f25e9bff6cf74b0b16195ccf0c3757d1ae8/scikit_learn-1.9.1-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ca869d0080a5723cde2d5a8b54a2da1ff7e68735a9e9adb3da1243183a0fa01", size = 8881917, upload-time = "2026-09-10T18:33:41.912Z" },
+ { url = "https://files.pythonhosted.org/packages/47/bb/98a31f10fffbd39edcc2f8bf4119b29652248bb110b7d45c84e68aa293ab/scikit_learn-1.9.1-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6754b7cabfc3df0b1f7b38f7a344f559bbae9d82f0ac5e3d48cccbd19fdcefdf", size = 9152805, upload-time = "2026-09-10T18:33:44.23Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/48/858ceff52213cfd97c0a069362071756bcb70b9fac9771b38d87c4cf7f17/scikit_learn-1.9.1-cp315-cp315-win_amd64.whl", hash = "sha256:52cfdb1fed3a34362dbc0bd96f2e761a66fd5724d6901629f5a558f1f3bd9849", size = 8376719, upload-time = "2026-09-10T18:33:46.492Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/1e/5337a871bdea53effbd154b61429df048f2665653251de74a3bd8a6dea9e/scikit_learn-1.9.1-cp315-cp315-win_arm64.whl", hash = "sha256:ae6571a4828c6f5019bcd2b4125e5b18c0af3dbc9c99726c891f45f41335ec8e", size = 8014341, upload-time = "2026-09-10T18:33:48.762Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/35/150383a42d83ec4c7b39f9c50bd68408ecf04c19fc30ea5198fa42e67d9c/scikit_learn-1.9.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:48fefd8eb42bd4eec3e2d348149368ccd6d71987e20c30706a56a24eb86a6e73", size = 9059953, upload-time = "2026-09-10T18:33:50.951Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/dd/aa0d738808540f7eaacfab93e01982db8ef1c1c7473ef0ad38193e6aebd1/scikit_learn-1.9.1-cp315-cp315t-macosx_12_0_arm64.whl", hash = "sha256:09f4d73049cd63575157f6b1060e06a8c83a4bd3488dbfaeedf35ccba7aad712", size = 8646602, upload-time = "2026-09-10T18:33:53.23Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/cc/687ae4214c2f598906c3b9fa5f86fbaf834b35e20360625528ff1b713f06/scikit_learn-1.9.1-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3da53831534214322d9cb240fa6f390b36cf69eba727a6d4bd3238677630d70", size = 8880366, upload-time = "2026-09-10T18:33:55.764Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/03/82215cb78ad1c513a4498777571fb28444621ad26ef636287551767b7732/scikit_learn-1.9.1-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caae15634feceafa2612566b109a3082d3293167fac388eedaf77bff66b51983", size = 9129640, upload-time = "2026-09-10T18:33:58.186Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/90/4b7af4efd7909a4a0524a9f18457e4eb2eb60616eff2c7627eda8e3cdceb/scikit_learn-1.9.1-cp315-cp315t-win_amd64.whl", hash = "sha256:ffbcbbbb44202fbe9bc64bced25a145759adb9ef010b3d37a8064958ac13df2a", size = 8678083, upload-time = "2026-09-10T18:34:00.354Z" },
+ { url = "https://files.pythonhosted.org/packages/31/27/068e484d4b83004302e0d9cfc1faca69bcc010d76fbb66a642446095af1b/scikit_learn-1.9.1-cp315-cp315t-win_arm64.whl", hash = "sha256:800dd22dd87fe97dcea484c24e85dd93cf1734d86bd74e668ad18f7967f4d1b5", size = 8259434, upload-time = "2026-09-10T18:34:02.678Z" },
+]
+
+[[package]]
+name = "scipy"
+version = "1.18.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235, upload-time = "2026-08-21T23:28:50.599Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/06/d5/d8eb4e280ddb56a4ab2c6f02ee49b56b23f6e977cf0802fd6d68dbef14f5/scipy-1.18.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:83de5453a7799afc9048b4616bd085cef126e36412f0ea2f6370c36a2a3a51e7", size = 31090936, upload-time = "2026-08-21T23:25:28.686Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/49/59ea385dc3a62ff498ddf3cfff7c2b41b0f9f9d3c4122b3f1dcb6d6327fe/scipy-1.18.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:9554bcc6d715ee87a633a3cc8e7703c6628b100dd29cb8a2efc4c0533c7ff729", size = 28725221, upload-time = "2026-08-21T23:25:33.244Z" },
+ { url = "https://files.pythonhosted.org/packages/70/e8/6b0c288c50942d78193696c9f15f9a0874f5178aa0ddf40f83d9924b3e8d/scipy-1.18.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:011413b7426b75012840e35649e00fe0a2c3bae89fed433876e3a99251572efc", size = 20466839, upload-time = "2026-08-21T23:25:37.516Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/e0/54fd3793c729e3b936782f181b59cbb1205bf250ab605a16cb1ba61cdd5e/scipy-1.18.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:88f0e784020649f88ea48c9f5ddfa403bf9205820667c0914740b392035afb82", size = 23089121, upload-time = "2026-08-21T23:25:42.019Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/56/030af62bea3cf878e0028515dff78c123b01633606a879b63f42d2db99cc/scipy-1.18.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d3ab0e8c69a17dd3559eab8cbb88f258e285c94d572c2719033f90f83290c89", size = 34053851, upload-time = "2026-08-21T23:25:47.998Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/89/2a844506d49651e9aa1af6ef95b6bd8031cb1d5a4375edec6155037e04cf/scipy-1.18.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac0333bdf38309aa3dcbe7e3fa7ea29e7a2c37c6ea306a757b700ded8e4596ad", size = 35329183, upload-time = "2026-08-21T23:25:53.522Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/56/c7370c3640e92ac9613cbf26cb3f729f9b12ddf1727b55b94b53b24d6f48/scipy-1.18.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:911de823097db8b63f034299d12662db93344e6ffa0b881cbb57748974b70168", size = 35672551, upload-time = "2026-08-21T23:25:59.387Z" },
+ { url = "https://files.pythonhosted.org/packages/24/16/ec8536f351421f8bf60a1120930638f83790f4710b8230446aca3d6159d4/scipy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:95298364e251be3e60249facbeeca03631d3bb7584f85879516ec55ac717b81f", size = 37469416, upload-time = "2026-08-21T23:26:05.432Z" },
+ { url = "https://files.pythonhosted.org/packages/52/94/d73da0d28f16c45bb9b0a5691b91610b0275c5ef0eb5e43c87cf2dc1bf31/scipy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:78a0d7c918e74a232394117160e7e3db503377572a45bcef8826e4ab8a35feba", size = 37362755, upload-time = "2026-08-21T23:26:11.366Z" },
+ { url = "https://files.pythonhosted.org/packages/89/25/e996e4dc74e10e227b1e14db5eaf6608bb6dd33884a64851c38f18dd4249/scipy-1.18.1-cp314-cp314-win_arm64.whl", hash = "sha256:cbf38d043c1aa4ab306e1ada6ab6eddacc3322a20b7af1b30bc93254b366fe09", size = 25036090, upload-time = "2026-08-21T23:26:15.887Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/c9/c00213f92309d753b48903e6a451b87eb52ff5b7a16e789d1568bbf221c4/scipy-1.18.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0fcb3c93519f27bb4f0c4b0f7802cdcaca7fcf93267b75edda2e9f4e8a55cbd7", size = 31485550, upload-time = "2026-08-21T23:26:20.776Z" },
+ { url = "https://files.pythonhosted.org/packages/74/b2/e3067c487982d4eeab2938928529410370c06fea84a4d3f4925e7d96647d/scipy-1.18.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ddef79fb382df40104a19bb7151b3b23e57c1778fcf857c71ceecd9bd264513f", size = 29174642, upload-time = "2026-08-21T23:26:25.395Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ab/374c9fe2d1ec014e576c781a4b5d8e1ba340e8f6b4638c16f711d2b194f0/scipy-1.18.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0e82073ecc7acc6436fac4b31674109c7e1d3e596789767eda01258a8c9e8123", size = 20916357, upload-time = "2026-08-21T23:26:30.112Z" },
+ { url = "https://files.pythonhosted.org/packages/90/38/223915c88a17317cafbf8ca2a42b11c265a9fb1e804aa665544132b5fe8a/scipy-1.18.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8bcf3c1ba5d6456e2effd30fcbd3459b044d683fcdac79a2e6830f0bdf7de487", size = 23482611, upload-time = "2026-08-21T23:26:34.846Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/d1/db0948da8ca57a80b36520ef0a768b967d99f3af65f4b6f1bf6362ad4dd4/scipy-1.18.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cfbf154f2ba187f2ed6cce2639efff7d105f1140573642c0161615b6d91d6a87", size = 34143202, upload-time = "2026-08-21T23:26:40.4Z" },
+ { url = "https://files.pythonhosted.org/packages/87/53/39d046cc7574ed6acacb6bd5723e220107ece80bff12faaf3efc4ddeede4/scipy-1.18.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1d33a7836f7ddc1993427966a0823468ec41bcbdb1a9f9942d1d7e57f803ba3", size = 35380876, upload-time = "2026-08-21T23:26:46.1Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/da/32e0e799d875a85ca57d9bde6c78148afcc0e38276df683d95854eadc8c3/scipy-1.18.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4b8bc363b6d65ee2152bec57568e3c52639bb34c46057b09857a307ed5e21d", size = 35770885, upload-time = "2026-08-21T23:26:51.533Z" },
+ { url = "https://files.pythonhosted.org/packages/88/2e/f97a666d362fee68b18f41c9c30ed502ca5c98b549749bfcb52a8b74d1eb/scipy-1.18.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:11c423f1049c5755ad4409af52a9ada1cff96fe9b50795d4af3619f292901239", size = 37525424, upload-time = "2026-08-21T23:26:56.751Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/d5/a9e765a84654ebba8479a1fd1b059ced1af72b168a3b2a3a46540ea38d20/scipy-1.18.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c24acac1e18912761c4700239bbc1fd32f615af690f1584d49b35859be51324d", size = 37416961, upload-time = "2026-08-21T23:27:01.546Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/16/e79e0d1c63ef698879d85439d37e9fb434e3b804e506a6991038d086ebd9/scipy-1.18.1-cp314-cp314t-win_arm64.whl", hash = "sha256:9f2897bf7737392ad0d5213ea7b6add72a4edf5679b3153106aeb88b6507b3b9", size = 25331848, upload-time = "2026-08-21T23:27:05.884Z" },
+ { url = "https://files.pythonhosted.org/packages/be/4f/1bd37c883b67163e2ca1f60977a399500e6879c15defecac62831c8d078d/scipy-1.18.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:eb0dfcf4e28a99c12c999744a2ff67c9b06200e20401c7c88186e33552a46331", size = 31091484, upload-time = "2026-08-21T23:27:11.051Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/c5/ba929d7feb9b2332f96827c12e0e924b61973b59b4dea383b603372c65ce/scipy-1.18.1-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:30f464bee641fa8e282577c7dce027308403213c6ca8270bba73285c91024bc5", size = 28725057, upload-time = "2026-08-21T23:27:15.9Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/19/68f1c50f609d955d230e66d25d02bd3e1e167ec540232135354fb9a4b9e3/scipy-1.18.1-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:1bca3b943fc2567ea49cd02c99abde49da4d5178ec46f624bd8255cda8755beb", size = 20466734, upload-time = "2026-08-21T23:27:20.044Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/6d/319fa29b73d1802fa80b32a6eaf3f5be456ef81526da2716a9493bcb5501/scipy-1.18.1-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:c9d18a33309122074ea483dd92dd444189166b8b2ec429fe9ed5ac73c7a0aa23", size = 23089664, upload-time = "2026-08-21T23:27:24.345Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/db/30992f9b51a63de671daf3888ffd18378b6cb9ec9f2c972264238ffa7fd6/scipy-1.18.1-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82f201b4c878551d48558337aab270d3c6cca5507b8737c8d8a608d234cccde0", size = 34054035, upload-time = "2026-08-21T23:27:29.409Z" },
+ { url = "https://files.pythonhosted.org/packages/91/d4/bf3e735dc0b9d5a8ff45079d2540e17d3aff7a2f0048dd8f552ffd031d2b/scipy-1.18.1-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ac49ea97594532dd44b7136094d35f5440fa06e6d9c6384a74c01764df388c5", size = 35333883, upload-time = "2026-08-21T23:27:34.293Z" },
+ { url = "https://files.pythonhosted.org/packages/19/93/12d78ce9f871fe945fca588d32644e6e63f553c2a35c564d73f3b22a3313/scipy-1.18.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:ceb30a00ce7c92d459819443d29ca486d882b83fb6738bdcbb2a1cce94ac5daa", size = 35673124, upload-time = "2026-08-21T23:27:39.059Z" },
+ { url = "https://files.pythonhosted.org/packages/70/cd/886219313a1012a48e6ae0ec4f302c837151beb92e1ff0d709ef8fdfc488/scipy-1.18.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f29633129f9fa7e88a3f0fca835de2d030bfc9643f7799e1a0c46cee24d38fc7", size = 37470753, upload-time = "2026-08-21T23:27:44.435Z" },
+ { url = "https://files.pythonhosted.org/packages/17/6c/a776888ce618bee54fbde26172f0f46ac1da70d27b63861797fe78e1904b/scipy-1.18.1-cp315-cp315-win_amd64.whl", hash = "sha256:92c14f5bdbfb6216315ce33e78080474082de8b3830122ba97809bfbe65f75c0", size = 37361483, upload-time = "2026-08-21T23:27:49.334Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/09/97b651691322ebee97999b017ffc18a15a0b815103844c97e8da9d469731/scipy-1.18.1-cp315-cp315-win_arm64.whl", hash = "sha256:e402cf31eb68f453dbb2d36fc6d722b33f24a55d68b2ae1d92fa6305ca71c298", size = 25035883, upload-time = "2026-08-21T23:27:53.596Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/0f/9ec20467bbabd0d44e2a77d0fd3d124f884b4d67df92af82c91d2d6a486f/scipy-1.18.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2a0b02f9fc46f8520330c23d45e6560db7e3a0d927232139427637f98943e11d", size = 31474926, upload-time = "2026-08-21T23:27:57.993Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/58/dcb79161e56efbedc50079fcd2f5fe427a0ebb53022eb476aa73c015ad8f/scipy-1.18.1-cp315-cp315t-macosx_12_0_arm64.whl", hash = "sha256:1d73131e358976663dd969e1fb4ed1404b815cd977eaaedc3b3a133ba2d81c35", size = 29164940, upload-time = "2026-08-21T23:28:03.062Z" },
+ { url = "https://files.pythonhosted.org/packages/71/d3/1eeea80c817fcb8ef7bd4a05a58824977a0e57a375cfc3d7ea7c911c01ad/scipy-1.18.1-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:bff0b729edd992766136b34e39cc76bc2fad905aa58897ee72a9cd000a6d8443", size = 20906742, upload-time = "2026-08-21T23:28:07.642Z" },
+ { url = "https://files.pythonhosted.org/packages/54/46/e59350428b6099301a20128108c995e2eb175a43f383af9a346e38824f9b/scipy-1.18.1-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:10ac20c69d880f77f375db44c22e3e6a644f9fefa291d4cd2fb9790a89fc99fd", size = 23472183, upload-time = "2026-08-21T23:28:12.109Z" },
+ { url = "https://files.pythonhosted.org/packages/89/31/cc91623fa98f0621766a0f0aaaadb2c66de74a7ea7e3837164f6e4354260/scipy-1.18.1-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33a834464fdabc0f26a45508df31b3cc5d028e04dbf6c5ed398541418e0a12fe", size = 34130796, upload-time = "2026-08-21T23:28:17.906Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/3e/8572ef536957ddb8aa81bb4090d9e25f257e3b4e05d97deb54319deb8a3a/scipy-1.18.1-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49023963c193dacee096301452f223ee24d86ec5807f8df93c0f7221d119e305", size = 35374253, upload-time = "2026-08-21T23:28:23.732Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/c6/59fdeffb4f1435299f93d9dc8140b43ad2916e6cfc944be6c3041fcec86d/scipy-1.18.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:d84a09d0dad90ba6525d8ac1c2334b33e64bf3ccfe9e841f02feb867a22681e4", size = 35758543, upload-time = "2026-08-21T23:28:29.431Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/d9/135be205d9de8783193aff9cc3bf483a03a38e4b29432c954e8cb66ac14e/scipy-1.18.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:179ce34a8d0fe273d8883ba59e17e052247d08973dfcb743ca52bb1cce2d60b0", size = 37521946, upload-time = "2026-08-21T23:28:35.245Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/a2/5b7d5270621ab7cfa3f7766067bf95dc360b5efb6394694e8143b4156e2b/scipy-1.18.1-cp315-cp315t-win_amd64.whl", hash = "sha256:5632e3ae3d09197c446310cd5187de63e28448ce22f0f67b2b93d97503c0c230", size = 37408295, upload-time = "2026-08-21T23:28:40.724Z" },
+ { url = "https://files.pythonhosted.org/packages/63/ad/741c19fcb66755ff953daf9243af8480e4bf3d7fbe57583c178c7d2b6b51/scipy-1.18.1-cp315-cp315t-win_arm64.whl", hash = "sha256:eda632a7981f69730d6281f451db9c1c370993a2c0d7ddb43e2a809a2862b83a", size = 25319710, upload-time = "2026-08-21T23:28:45.713Z" },
+]
+
+[[package]]
+name = "sentence-transformers"
+version = "6.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "huggingface-hub" },
+ { name = "numpy" },
+ { name = "scikit-learn" },
+ { name = "scipy" },
+ { name = "tokenizers" },
+ { name = "torch" },
+ { name = "tqdm" },
+ { name = "transformers" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d9/ef/87681678d0aa91a1e77f0ffdd2e5d900aaf358aac644dd14bead8d95ec4c/sentence_transformers-6.0.1.tar.gz", hash = "sha256:1c3b8d9403f87ad0c879638554f36cc85744f38a6a70d150fd7e964ca0e9935b", size = 575395, upload-time = "2026-08-31T07:50:43.518Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/31/ab/dc6031faf1b6bba08076fbf457b21a5c51ba7928a77916915e093c16b8c7/sentence_transformers-6.0.1-py3-none-any.whl", hash = "sha256:b8888d72c707ba33c63aa30845850702dd5acadf1dd0d051436380bcebe4fd0f", size = 739832, upload-time = "2026-08-31T07:50:41.635Z" },
+]
+
+[[package]]
+name = "setuptools"
+version = "84.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" },
+]
+
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
+]
+
+[[package]]
+name = "sympy"
+version = "1.14.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mpmath" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
+]
+
+[[package]]
+name = "threadpoolctl"
+version = "3.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/00/dc/6c58154c1c65f758ea979e7139cb76993a9cfc662d14e9be3c4a667cfb77/threadpoolctl-3.7.0.tar.gz", hash = "sha256:61348cfb77d53b9242e0017029244b559b810c142ced65b4e21eeca1843959a7", size = 31961, upload-time = "2026-09-15T15:46:20.263Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/43/3f/f88a53f60a472b46f4023f56d204dd7de33d34c5d2acbfa0d70a674e639e/threadpoolctl-3.7.0-py3-none-any.whl", hash = "sha256:cd8b60b5641b45c67bbf73c64c843235fc2d8a480c87389f52f5dbee893b86be", size = 26362, upload-time = "2026-09-15T15:46:19.168Z" },
+]
+
+[[package]]
+name = "tokenizers"
+version = "0.23.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "huggingface-hub" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/18/1e/bc6587c5ab643b2e17776cace9070a2ae73549c86bffac9934a600bf3c31/tokenizers-0.23.2.tar.gz", hash = "sha256:7f0f085686b9de0d0079e6f874ae053600db64c5d13049e0bbc0119926d25aac", size = 385745, upload-time = "2026-09-03T08:55:42.89Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4d/ed/8a443528baa6fac8dfe8c3b75b038c63ac92bb539bcabe311e227c718173/tokenizers-0.23.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:85a9a357a3764aecc904ee76bdaf8cf1ad8e5a67a1b929a487c4a39b49ed0e90", size = 3148852, upload-time = "2026-09-03T08:55:30.874Z" },
+ { url = "https://files.pythonhosted.org/packages/67/49/22da045a91732384d3a3771816bf188dc5a1f702c32e635afa7c679c0bef/tokenizers-0.23.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:986670e43691469dcee610ea0f846f91a8f84e91fc6f7a48d4c064414c0ec2bf", size = 3101593, upload-time = "2026-09-03T08:55:28.587Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/4d/8f569ed49372a3ed8e57099bd515055fd48d7c95912c4307cda6973c2168/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a37039b5dfc4af84eb3ef0a92f4307e28936c8f9adccba2629d36f652e9bf7a2", size = 3516830, upload-time = "2026-09-03T08:55:14.741Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/de/e2f14c8919d5bf51874051d00d6c7b7e0e8bde6c6a2dbeddda7f642896ff/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b7e37ba198f24150f523e1242e83c4970de4a525480586be5dcc24d9add32c5", size = 3407975, upload-time = "2026-09-03T08:55:16.842Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/bd/93c69152d02ef06ce47aed8b2bf4952dcf733c935a62791873932b2934d9/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43e4f2071e3cc8d5d86421c874aebc82659bb51a68bcdef5a0da75ee89511ccb", size = 3748165, upload-time = "2026-09-03T08:55:24.769Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/b7/56b84b80bc96942bba8eb23751a9e8a1fce4faaf4390425e7083f721c98c/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:325fee2e0418a9dc6c9ecf736a5f5f0db7875183ace9549ae339da76f7a1fbb7", size = 4024165, upload-time = "2026-09-03T08:55:18.806Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/8a/0175e216f005c2fe08238292663aa41e4c802b216e71047a69a0e9fc6fa3/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:950d7c9426fa72406a0ffeacdbc0bb9985f5db20eb8b263f29c79aaf83105703", size = 3591899, upload-time = "2026-09-03T08:55:22.752Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/ca/ca6b93c7820df123b2662a9469e8facc826ccc94e98fdd0d615f6431e73a/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41c2f84d172449b4dadb9cdc508e3e364076613c35b16e76ecfe47a60d1e3305", size = 3386843, upload-time = "2026-09-03T08:55:26.584Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/a4/4f9106d317b14a80aefea9f0e3a8d07ef25f856a7607eb7f5ab894281fcb/tokenizers-0.23.2-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:12f0835dc2ee694746a76adf7b1567d4346a4a502ebe93fb1f5f80ea49799b78", size = 3577314, upload-time = "2026-09-03T08:55:20.825Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/6a/1552b70fb0d9ab074fd3fc961435d01364e79c9058481822c3af6e8d402c/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb2f9c8a24da020ea8c11a01a19c1c2547912d92121ae4a01cfbca46125dee40", size = 9967367, upload-time = "2026-09-03T08:55:33.188Z" },
+ { url = "https://files.pythonhosted.org/packages/06/01/3ccb3a956c7528b2507b8a9714155c4baf86af593039db6ea375dd0c96c3/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f486f402f6f9abee5bb032553736813af0c710a86b2e0ca592634c55cea1f835", size = 9811886, upload-time = "2026-09-03T08:55:35.642Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/73/7038e612d48bda1599457f712f6bd3854eae1a9dc9c13aa47f835349db48/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:bef235815a067b2648caf6dcc7a71091b0b0fff9ee8057f6451eb9335fae52ef", size = 10146224, upload-time = "2026-09-03T08:55:38.391Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/d8/8e9e4e0b287a338d8f88976729628c9d22e8a54cfaf9777018a7f7cb58a0/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5c56bda1511921587789163e524d196ed8284174ac23abd7685d5ea8da6c4718", size = 10256304, upload-time = "2026-09-03T08:55:40.977Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/1f/c79a01f671a49728ebb0b61f7ff9ea45663b66cab40bc0858e9859b25c16/tokenizers-0.23.2-cp310-abi3-win32.whl", hash = "sha256:debf978920d93ba9c219bd67cc4bbfaf912c9039e41e7a28b91ec15e3728c95a", size = 2592809, upload-time = "2026-09-03T08:55:48.02Z" },
+ { url = "https://files.pythonhosted.org/packages/db/f7/0a69ac6b82dbccf3f71add938a161c497952749294b8dd6dfe03a819dc40/tokenizers-0.23.2-cp310-abi3-win_amd64.whl", hash = "sha256:2e96f5699d5249c9c64aa8412e044f727aae3a4098cf830f9901ec1afc361cde", size = 2863236, upload-time = "2026-09-03T08:55:46.193Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/b0/dee84cb44175be1b4c35bd2f770727494e78f0bb38e571a623ade94dbebb/tokenizers-0.23.2-cp310-abi3-win_arm64.whl", hash = "sha256:e49c394456dd9985787fec76132438ba3fb8911f857b1bf3d40119f9292d41aa", size = 2729352, upload-time = "2026-09-03T08:55:44.345Z" },
+]
+
+[[package]]
+name = "torch"
+version = "2.14.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cuda-bindings", marker = "python_full_version < '3.15' and sys_platform == 'linux'" },
+ { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" },
+ { name = "filelock" },
+ { name = "fsspec" },
+ { name = "jinja2" },
+ { name = "networkx" },
+ { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" },
+ { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" },
+ { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" },
+ { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" },
+ { name = "setuptools" },
+ { name = "sympy" },
+ { name = "triton", marker = "python_full_version < '3.15' and sys_platform == 'linux'" },
+ { name = "typing-extensions" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ee/90/1241e7db5ccc2455f8735bd6b1becfad39916206ad18001c4c0014d139e2/torch-2.14.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:860423e970f2ce02c4476e8e2d1350131b1c5c5a5e4912180e78b50b53241efa", size = 127321431, upload-time = "2026-09-02T13:44:38.236Z" },
+ { url = "https://files.pythonhosted.org/packages/68/f0/bbeba65e3fbb4b8f61ea19cf4ebea9f4268cc6fe64e6f7d38bfb6cc152eb/torch-2.14.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:b985c7defeb8d28691b7513ebaecc64946c5f68ec770e9f4ddba39688864f46a", size = 454027631, upload-time = "2026-09-02T13:45:43.73Z" },
+ { url = "https://files.pythonhosted.org/packages/50/75/8d2b9a7e724759470c209489b79260cac537f091cc9aac8001c8d2bc845c/torch-2.14.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b2cd92bce63d40bf6fc2e5d840fc2f0063bd180a242daa761cb6088cc2f46e27", size = 554623549, upload-time = "2026-09-02T13:46:04.252Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/1e/a5475c00b0555e686333e6b4036f2213e7cbea021a772ce6f9ced4dcbd2f/torch-2.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:44b044b9f6f633d982839422a57433d6a1da520037fd88e0c8a47efde589b3b8", size = 124110863, upload-time = "2026-09-02T13:45:12.764Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/49/bbaee76337742a42d2c5b0296ea62252567050e1d821bc2283b2e72ba6fb/torch-2.14.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:553aec938d37d77b783bcf801e638cad068870e7643c47eadac21eb180f551ed", size = 127653463, upload-time = "2026-09-02T13:45:17.1Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/6cc7a511bab384fbe8f4b8ddeecdd22e724b2162d6f15076f07a0a7ef15b/torch-2.14.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cd8cd8f714d511ccdca907282d1da3d9be8322d4e1520b9c3bce39a5c1318a4b", size = 454009927, upload-time = "2026-09-02T13:46:19.637Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/da/0f04fe15fd05bf3f613a359b14acb44c35ae06fe69da0aefa8b5cdb4f9f9/torch-2.14.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d2526f71e6638133b97b3cd2881ece3df521460a4727030e8ae2a72a7d3ae31d", size = 554580530, upload-time = "2026-09-02T13:46:34.818Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/c3/72ae1f02747b1f012e1975743e48cd608f83095d7f9ce58de78b79248b35/torch-2.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:731784e3914843c6bcc7aba3987ff7610ac57dbbc816a5d6b9b62e04c240a641", size = 124400194, upload-time = "2026-09-02T13:45:53.555Z" },
+]
+
+[[package]]
+name = "tqdm"
+version = "4.70.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz", hash = "sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4", size = 171846, upload-time = "2026-09-11T07:25:16.601Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199, upload-time = "2026-09-11T07:25:14.599Z" },
+]
+
+[[package]]
+name = "transformers"
+version = "5.17.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "huggingface-hub" },
+ { name = "numpy" },
+ { name = "packaging" },
+ { name = "pyyaml" },
+ { name = "regex" },
+ { name = "safetensors" },
+ { name = "tokenizers" },
+ { name = "tqdm" },
+ { name = "typer" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/9e/750649904a065007a838981785b2bd8d9ff26154c6c341ac67d0b7f82c68/transformers-5.17.0.tar.gz", hash = "sha256:a153be279169b55b92d8000bf4af294aed684503d091cca7804da2dd8a9de000", size = 9817878, upload-time = "2026-09-09T15:39:56.886Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", hash = "sha256:78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", size = 12295140, upload-time = "2026-09-09T15:39:53.746Z" },
+]
+
+[[package]]
+name = "triton"
+version = "3.8.0"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b8/59/bf0e9493118bb353ab59a5d6a65db3618d9b314417cc1459f0121e0ec5c9/triton-3.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f6b48d0591929a3867973acac3dccd4e058585f91bfb41022de496c9ffab304", size = 226488654, upload-time = "2026-08-28T16:08:47.141Z" },
+ { url = "https://files.pythonhosted.org/packages/93/d9/08c75f3459f19ad00425b564058e40efa4bcd79b816064cf27499303ea42/triton-3.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:387dae4cb0089a7b6ba1a428ae0782b65c4c58f57d94617cb22ca8593d8ccbca", size = 247972313, upload-time = "2026-08-28T15:56:14.007Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/34/429c5592181cfb7361a0a8e0bff218e7224b726709d75da2472b3e819f70/triton-3.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b84e7d512490ba529111260fa6f7cad8b254a6bb5fbdf41d5ef9a5e57f52d0a", size = 226591133, upload-time = "2026-08-28T16:09:02.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/d1/aa8a3e935c37efee7945984fdb64d7e0851bf6d920afd97b2d21f9d23360/triton-3.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74217bb56ed8692759227758e4c4b3bd2d608a209c1a7a081bf361fb4c2c1bf9", size = 248077577, upload-time = "2026-08-28T15:56:24.94Z" },
+]
+
+[[package]]
+name = "typer"
+version = "0.27.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-doc" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "rich" },
+ { name = "shellingham" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
+]
diff --git a/RAG/rename_db_rag_embeddings.py b/RAG/rename_db_rag_embeddings.py
deleted file mode 100644
index b91318f..0000000
--- a/RAG/rename_db_rag_embeddings.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from dotenv import load_dotenv
-import os
-from pymongo import MongoClient
-
-load_dotenv()
-
-MONGO_URI = os.getenv("MONGO_URI")
-DB_NAME = os.getenv("MONGO_DB")
-COLLECTION_NAME = os.getenv("COLLECTION_NAME")
-
-if not MONGO_URI:
- raise ValueError("MONGO_URI not found in environment variables.")
-
-client = MongoClient(MONGO_URI)
-db = client[DB_NAME]
-collection = db[COLLECTION_NAME]
-
-original_field = "embedding"
-new_field = "_vector"
-result = collection.update_many(
- {original_field: {"$exists": True}},
- {"$rename": {original_field: new_field}}
-)
-
-print(f"Modified {result.modified_count} documents.")
diff --git a/backend/.python-version b/backend/.python-version
new file mode 100644
index 0000000..a6d9ada
--- /dev/null
+++ b/backend/.python-version
@@ -0,0 +1 @@
+3.14.5
diff --git a/backend/Dockerfile b/backend/Dockerfile
index 9a390d6..d25a04a 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -1,16 +1,20 @@
-# Use a lightweight Python base image (includes PyTorch runtime)
-FROM pytorch/pytorch:2.9.0-cuda12.8-cudnn9-runtime
+FROM python:3.14.5-slim
-WORKDIR /app
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
-# Install dependencies first for caching
-COPY requirements.txt .
-RUN pip install --no-cache-dir -r requirements.txt
+WORKDIR /backend
-# Copy app code
-COPY . .
+ENV PYTHONUNBUFFERED=1 \
+ PYTHONDONTWRITEBYTECODE=1
+
+COPY pyproject.toml uv.lock ./
+
+RUN uv sync --locked --no-install-project --no-dev
+
+COPY app ./app
+
+ENV PATH="/backend/.venv/bin:$PATH"
EXPOSE 5000
-# Run Gunicorn with 2 workers + sane timeouts
-CMD ["gunicorn", "-w", "2", "-b", "0.0.0.0:5000", "--timeout", "120", "--graceful-timeout", "30", "--max-requests", "200", "--max-requests-jitter", "50", "app:app"]
\ No newline at end of file
+CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT:-5000} --workers 1 --timeout 120 app.app:app"]
\ No newline at end of file
diff --git a/backend/README.md b/backend/README.md
index 56d7dd6..09450b4 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -1,234 +1,477 @@
-# SLRmentor App Backend
+# SLRmentor Backend
-This is the backend for the **SEALL** application — a tool for generating and refining academic search strings and inclusion/exclusion criteria using AI (Gemini & GPT), MongoDB, and Flask.
+[](https://hub.docker.com/r/seallabuofc/slrmentor-backend/tags)
----
+[Docker Hub repository](https://hub.docker.com/r/seallabuofc/slrmentor-backend)
-## Requirements
+SLRmentor is a SEALL application that supports systematic literature reviews with AI-assisted academic search string generation and conversion, inclusion/exclusion criteria refinement, and research guidance.
-Make sure you have the following installed:
+The backend uses **Flask**, **MongoDB Atlas**, **Google Gemini**, and **OpenAI**. Retrieval-Augmented Generation (RAG) uses MongoDB Atlas Vector Search with Automated Embedding, so the backend does not need to host an embedding model locally.
-- [Python](https://www.python.org/downloads/)
-- [Anaconda](https://www.anaconda.com/download)
-- [MongoDB Community Edition](https://www.mongodb.com/try/download/community)
-- Create .env file
---
-## Project Structure
+## 1. Requirements
+
+### Local development
+
+- [Python](https://www.python.org/downloads/) **3.14.5 or later**, as declared in `pyproject.toml`.
+- [uv](https://docs.astral.sh/uv/) for Python environment and dependency management.
+- Access to a MongoDB Atlas cluster with the required collections and Vector Search index.
+- API credentials for the LLM providers you plan to use.
+
+For Docker-based execution, install [Docker](https://docs.docker.com/get-docker/). You do not need Conda or a local MongoDB Community Server for the Atlas-based configuration.
+
+## 2. Project Structure
+
+```text
+BackendRefactor/
+├── .env
+├── .dockerignore
+├── .gitignore
+├── Dockerfile
+├── README.md
+├── pyproject.toml
+├── uv.lock
+└── app/
+ ├── __init__.py
+ ├── app.py
+ └── helpers/
+ ├── RAG/
+ │ └── RetrieverManager.py
+ ├── db/
+ │ └── mongodbhelpers.py
+ ├── llm/
+ │ ├── promptBuilder.py
+ │ ├── llmPromptingUtils.py
+ │ ├── llmReturnFormat.py
+ │ └── prompts/
+ ├── cryptographic_helpers.py
+ └── scholarlyapi.py
+```
+
+Commands in this README assume you are in the **project root**, where `pyproject.toml` and `Dockerfile` are located.
+
+## 3. Environment Configuration
+Create `.env` in the backend folder:
+
+```env
+# MongoDB Atlas Configuration
+MONGO_URI=mongodb+srv://your-username:your-password@your-cluster.mongodb.net/
+MONGO_USERNAME=your-mongodb-username
+MONGO_USERNAME_PASSWORD=your-mongodb-password
+MONGO_DB_NAME=your-database-name
+MONGO_DB_RAG_COLLECTION_NAME=your-rag-collection-name
+VECTOR_SEARCH_INDEX=autoembed_index
+
+# LLM API Keys
+GPT_API_KEY=your-openai-api-key
+GEMINI_API_KEY=your-gemini-api-key
+
+# Other Configurations
+RAG_doc_limit=5
+message_history_limit=30
+chat_hash_length=8
+hash_byte_length=16
```
-SEALL/
-├── backend/
-│ ├── .env
-│ ├── app.py
-│ ├── environment.yml
-│ └── helpers/
-│ ├── llm/
-│ ├── xploreapi/
-│ └── ...
+
+Replace the placeholders with your actual configuration. **Do not add surrounding quotation marks or spaces around `=` in this `.env` file.** Docker's `--env-file` can include literal quotation marks in environment variable values, which causes PyMongo to reject a quoted `MONGO_URI` with `Invalid URI scheme`. The unquoted format above works with both Docker's `--env-file` and `python-dotenv` during local Flask development.
+
+The current `app.py` and `RetrieverManager.py` use `MONGO_URI` directly; the separate username/password variables are retained from the project's environment template. If your credentials include reserved URL characters, encode them correctly in `MONGO_URI`.
+
+The backend includes Gemini-to-OpenAI fallback behavior. Configure both API keys for fallback to work; check individual routes if you intend to use only one provider.
+
+| Variable | Purpose |
+|---|---|
+| `MONGO_URI` | MongoDB Atlas connection string |
+| `MONGO_USERNAME` | Optional separate MongoDB username setting retained in the project configuration |
+| `MONGO_USERNAME_PASSWORD` | Optional separate MongoDB password setting retained in the project configuration |
+| `MONGO_DB_NAME` | Database name |
+| `MONGO_DB_RAG_COLLECTION_NAME` | Collection containing RAG documents |
+| `VECTOR_SEARCH_INDEX` | Atlas Vector Search index name |
+| `GPT_API_KEY` | OpenAI API key |
+| `GEMINI_API_KEY` | Gemini API key |
+| `RAG_doc_limit` | Maximum RAG documents to retrieve |
+| `message_history_limit` | Maximum recent messages used in conversation context |
+| `chat_hash_length` | Chat hash length configuration |
+| `hash_byte_length` | Hash byte length configuration |
+
+**Security:** Keep `.env` in both `.gitignore` and `.dockerignore`. Do not commit secrets or copy `.env` into the Docker image. Other helper modules may require additional environment variables.
+
+## 4. Local Development with uv
+
+Install uv following the [official instructions](https://docs.astral.sh/uv/getting-started/installation/), then verify:
+
+```bash
+uv --version
```
----
+Install dependencies from the lockfile:
-## .env File
+```bash
+uv sync --locked
+```
-Create a file called `.env` inside the `backend/` directory with the following content:
+Run the Flask development server:
-```env
-GPT_API_KEY="your-gpt-api-key"
-GEMINI_API_KEY="your-gemini-api-key"
-ENCRYPTION_KEY="your-encryption-key"
-MONGO_URI="mongodb://localhost:27017/seall"
-MONGO_USER="sealluser"
-MONGO_PASSWORD="securepassword123"
-MONGO_DATABASE="SEALLDB"
-```
-
-Change the placeholder values to your actual keys, encryption key, and MongoDB credentials if different.
-You will need to aquire the AI API keys by yourself. You only really need one, either Gemini free tier key or the OPENAI key.
-Both are recommended.
----
+```bash
+uv run flask --app app.app run --debug
+```
+
+Open . The root endpoint returns:
+
+```text
+Server is live ;)
+```
+
+You do not need to activate `.venv` when using `uv run`. Use the Flask development server only for local development.
+
+## 5. MongoDB Atlas and RAG
+
+The backend performs semantic retrieval using **MongoDB Atlas Vector Search with Automated Embedding**:
+
+1. A Flask route receives a query and passes it to `RetrieverManager`.
+2. MongoDB Atlas generates the query embedding remotely.
+3. Atlas Vector Search returns the most relevant document chunks.
+4. The backend formats the retrieved text and includes it in the LLM prompt.
+
+No local embedding model is loaded by this workflow.
+
+### Vector Search index
-## Setting Up the Conda Environment
+The project uses the following index configuration:
-Open a terminal and navigate to the `backend/` directory:
+```json
+{
+ "fields": [
+ {
+ "modality": "text",
+ "model": "voyage-4-lite",
+ "numDimensions": 256,
+ "path": "content",
+ "similarity": "cosine",
+ "type": "autoEmbed"
+ }
+ ]
+}
+```
+
+Create the index on the collection designated by `MONGO_DB_NAME` and `MONGO_DB_RAG_COLLECTION_NAME`. Its name must match `VECTOR_SEARCH_INDEX`. Verify that the selected Automated Embedding configuration is supported by your Atlas deployment.
+
+### Retrieval interface
+
+```python
+rm.get_relevant_documents(query)
+```
+
+This method returns native Python dictionaries rather than LangChain document objects. Results may include `content`, `paper_id`, `source`, `page`, `chunk_index`, and `score`. The `RAG_doc_limit` setting controls the maximum number of documents retrieved.
+
+Ensure the Atlas cluster is reachable, its user has the necessary permissions, and the index is ready before testing RAG routes.
+
+## 6. API Endpoints
+
+| Endpoint | Method | Description |
+|---|---|---|
+| `/` | GET | Server health check |
+| `/createchat` | POST | Create a chat session |
+| `/getchathistory` | POST | Retrieve search string chat history |
+| `/getcriteriachathistory` | POST | Retrieve criteria chat history |
+| `/getmentorchathistory` | POST | Retrieve mentor chat history |
+| `/getsystemcontext` | POST | Retrieve system context |
+| `/setsystemcontext` | POST | Update system context |
+| `/prompt` | POST | Generate or refine academic search strings |
+| `/conversionformats` | GET | List supported search string formats |
+| `/convertsearchstring` | POST | Convert a search string into another format |
+| `/criteria` | POST | Generate or refine inclusion/exclusion criteria |
+| `/mentor` | POST | Provide AI-assisted SLR guidance |
+| `/ragquery` | POST | Query retrieved RAG documents without an LLM call |
+
+### Example: test RAG retrieval
+
+Send a `POST` request to `http://127.0.0.1:5000/ragquery` with JSON:
+
+```json
+{
+ "query": "What is a systematic literature review?"
+}
+```
+
+The response contains the query, the number of results, and the retrieved document data. This endpoint tests retrieval independently of LLM generation.
+
+## 7. Docker Configuration
+
+The Dockerfile uses Python 3.14.5 slim, uv, and Gunicorn:
+
+```dockerfile
+FROM python:3.14.5-slim
+
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
+
+WORKDIR /backend
+
+ENV PYTHONUNBUFFERED=1 \
+ PYTHONDONTWRITEBYTECODE=1
+
+COPY pyproject.toml uv.lock ./
+
+RUN uv sync --locked --no-install-project --no-dev
+
+COPY app ./app
+
+ENV PATH="/backend/.venv/bin:$PATH"
+
+EXPOSE 5000
+
+CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT:-5000} --workers 1 --timeout 120 app.app:app"]
+```
+
+The server listens on port 5000 unless `PORT` is set to a different value. The Dockerfile does **not** include the `.env` file; configure variables when starting the container.
+
+## 8. Build and Run Docker Locally
+
+Build the image:
```bash
-cd backend
+docker build -t slrmentor-backend .
```
-Create the environment from the existing `environment.yml` file:
+Run it with the environment file:
```bash
-conda env create -f environment.yml
+docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env slrmentor-backend
```
-Activate the environment:
+Although `.env` is excluded by `.dockerignore`, `docker run --env-file .env` still reads it from your local machine at **runtime**. The `.env` file must use unquoted `KEY=value` entries, as shown in Section 3. If you change `.env`, stop and remove the existing container, then create a new container with `--env-file .env`; **rebuilding the image is not required** for environment-only changes.
+
+Verify:
```bash
-conda activate seallapp
+docker ps
+docker logs slrmentor-backend
```
-Install heavier packages from `requirements.txt` file:
+Then open .
+
+Stop and remove the container:
```bash
-pip install --no-cache-dir -r requirements.txt
+docker stop slrmentor-backend
+docker rm slrmentor-backend
```
----
+## 9. Publish a Docker Image to Docker Hub
+
+**Docker Hub repository:** [seallabuofc/slrmentor-backend](https://hub.docker.com/r/seallabuofc/slrmentor-backend)
+
+**Latest published version:**
-# Running MongoDB
+[](https://hub.docker.com/r/seallabuofc/slrmentor-backend/tags)
-You must have MongoDB running **before** starting the Flask app. Here's how:
+The badge queries Docker Hub through Shields.io and updates as its cache refreshes. It shows the highest semantic-version tag recognized by the badge service. It does **not** guarantee that the `latest` tag points to the same version.
-### 🔸 Linux (Ubuntu/Debian)
-You can figure out how to install it for your distro....
-Start MongoDB using systemd:
+Replace **`vX.X`** below with your new release version (for example, `v2.3`).
+
+### 1. Build the versioned image
```bash
-sudo systemctl start mongod
+docker build -t seallabuofc/slrmentor-backend:vX.X .
```
-Check status with:
+
+### 2. Tag the image as latest
+
+```bash
+docker tag seallabuofc/slrmentor-backend:vX.X seallabuofc/slrmentor-backend:latest
```
-sudo systemctl status mongod
+
+### 3. Test the image locally
+
+```bash
+docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:vX.X
```
----
-### 🔸 Windows
+Check logs:
-1. **Install MongoDB** from the official MSI installer:
- https://www.mongodb.com/try/download/community
+```bash
+docker logs slrmentor-backend
+```
-2. By default, MongoDB is installed as a **Windows service** and starts automatically.
- If it's not running, open **Command Prompt as Administrator** and run:
+When finished:
- ```cmd
- net start MongoDB
- ```
+```bash
+docker stop slrmentor-backend
+docker rm slrmentor-backend
+```
-3. If you prefer to run it manually (not as a service), use:
+If a container named `slrmentor-backend` already exists, stop and remove it before starting the test container.
- ```cmd
- "C:\Program Files\MongoDB\Server\7.0\bin\mongod.exe" --dbpath C:\data\db
- ```
+### 4. Log in to Docker Hub
- > Replace `7.0` with your installed version, and make sure `C:\data\db` exists.
- You can create it with:
+```bash
+docker login
+```
- ```cmd
- mkdir C:\data\db
- ```
+### 5. Push the versioned image and latest tag
----
+```bash
+docker push seallabuofc/slrmentor-backend:vX.X
+docker push seallabuofc/slrmentor-backend:latest
+```
+
+Publishing an image does not automatically update running containers on other machines.
-## Running the Flask App
+## 10. Run from Docker Hub on Another Machine (Including AWS EC2)
-From the activated Conda environment in the `backend/` directory, run:
+Ensure Docker is installed and the target machine has an `.env` file containing its runtime credentials. MongoDB Atlas must allow connections from the deployment environment.
+
+### 1. Stop and remove the existing container
```bash
-python -m flask run
+docker stop slrmentor-backend
+docker rm slrmentor-backend
```
-If everything is working, you’ll see something like:
+Skip these commands on the first deployment.
+
+### 2. Pull the image tagged latest
+```bash
+docker pull seallabuofc/slrmentor-backend:latest
```
-* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
+
+The `latest` tag is maintained by the publisher. For reproducible deployment and rollbacks, prefer an explicit version tag such as `vX.X` (replaced with an actual release number).
+
+### 3. Run without a memory limit
+
+```bash
+docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:latest
```
-You can visit [http://127.0.0.1:5000/](http://127.0.0.1:5000/) to check if the server is live.
+### Or run with a 1 GiB memory limit
----
+```bash
+docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env --memory=1g --memory-swap=1g seallabuofc/slrmentor-backend:latest
+```
-## API Endpoints Overview
+Choose **one** of the two `docker run` commands, not both. Adjust memory according to observed resource usage.
-| Endpoint | Method | Description |
-|-----------------------------|--------|------------------------------------------|
-| `/` | GET | Test if server is live |
-| `/createchat` | POST | Creates a new chat session |
-| `/getchathistory` | POST | Retrieves search string chat history |
-| `/prompt` | POST | Sends a message and gets LLM response. This is for search strings |
-| `/conversionformats` | GET | Lists available search string formats |
-| `/convertsearchstring` | POST | Converts search string to another format |
-| `/criteria` | POST | Generates/refines inclusion criteria |
-| `/getcriteriachathistory` | POST | Retrieves criteria chat history |
+### 4. Verify the deployment
----
+```bash
+docker ps
+docker logs -f slrmentor-backend
+```
-## 🛠 Notes
+To see which image reference was used to create the running container:
-- MongoDB must be running **before** starting the Flask app.
-- The `.env` file is critical — missing keys will cause the app to fail.
--
+```bash
+docker inspect slrmentor-backend --format '{{.Config.Image}}'
+```
----
+On AWS EC2, configure the instance's security group and any reverse proxy to expose only necessary traffic; use HTTPS for public deployments.
-## ✅ Example Workflow
+## 11. Manage Dependencies with uv
-1. Start MongoDB
-2. Navigate to backend: `cd backend`
-3. Activate Conda env: `conda activate seallapp`
-4. Run: `python -m flask run`
-5. Access at: `http://127.0.0.1:5000/`
+Add a package:
----
-## Running backend with docker, and Deployment to dockerhub
+```bash
+uv add package-name
+```
+Remove a package:
-1️⃣ Build the image from the current directory in the backend
+```bash
+uv remove package-name
+```
-`docker build -t seallabuofc/slrmentor-backend:v2.2 .`
+Sync dependencies:
-Optionally, also tag it as latest:
+```bash
+uv sync
+```
-`docker tag seallabuofc/slrmentor-backend:v2.2 seallabuofc/slrmentor-backend:latest`
+Update the lockfile if necessary:
+```bash
+uv lock
+```
-2️⃣ Run the container with the .env file to test if needed
+Commit `pyproject.toml` and `uv.lock` together after dependency changes. Rebuild and republish the Docker image when application code or dependencies change.
-`docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:v2.2`
+The native RAG retriever does not require a locally hosted embedding model or LangChain for its retrieval path.
-stop container: `docker stop slrmentor-backend`
+## 12. Troubleshooting
-3️⃣ Log in to Docker Hub (if not already)
+### Flask fails to start
-`docker login`
+Check Python compatibility, dependencies, `.env`, and the application package layout:
-4️⃣ Push the new image to Docker Hub
+```bash
+uv sync --locked
+uv run flask --app app.app run --debug
+```
-Push the version tag:
+### MongoDB connection fails
-`docker push seallabuofc/slrmentor-backend:v2.2`
+Check the `MONGO_URI`, database user permissions, Atlas cluster availability, and Atlas network access settings.
-`docker push seallabuofc/slrmentor-backend:latest`
+### RAG retrieval fails
+Check that `VECTOR_SEARCH_INDEX` matches the Atlas index, the correct collection is selected, the index uses the `content` field, Automated Embedding is configured, and `RAG_doc_limit` is positive. Test `/ragquery` separately from LLM requests.
-6️⃣ Pull the updated image on other machines
+### LLM requests fail
-If you want to run this updated image elsewhere:
+Check provider API keys, quotas, rate limits, and logs for authentication or timeout errors.
-`docker pull seallabuofc/slrmentor-backend:latest`
+### Docker container exits immediately
-`docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:latest`
+```bash
+docker logs slrmentor-backend
+```
+Verify that the runtime `.env` file was passed using `--env-file`.
-## Run App from Docker hub on other machines from docker hub (includes ec2)
+### Docker build fails after dependency edits
-Stop any currently running containers:
+```bash
+uv lock
+docker build -t slrmentor-backend .
+```
-`docker stop slrmentor-backend`
+## 13. Quick Start
-Remove container:
+### Local development
-`docker rm slrmentor-backend`
+```bash
+uv sync --locked
+uv run flask --app app.app run --debug
+```
-Pull latest image build:
+### Docker on your computer
-`docker pull seallabuofc/slrmentor-backend:latest`
+```bash
+docker build -t slrmentor-backend .
+docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env slrmentor-backend
+docker logs slrmentor-backend
+```
-run with no memory limit:
+### Update an existing Docker Hub deployment
-`docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:latest`
+```bash
+docker stop slrmentor-backend
+docker rm slrmentor-backend
+docker pull seallabuofc/slrmentor-backend:latest
+docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env seallabuofc/slrmentor-backend:latest
+```
+
+For a first-time deployment, skip stop/remove. For production, consider using an explicit numbered release tag and a restart policy.
+
+---
-with memory limit(1gib example):
+## Notes
-`docker run -d --name slrmentor-backend -p 5000:5000 --env-file .env --memory=1g --memory-swap=1g seallabuofc/slrmentor-backend:latest`
\ No newline at end of file
+- MongoDB Atlas hosts query embeddings and vector retrieval; the backend does not load a local embedding model.
+- The application runs with Flask during local development and Gunicorn inside the production Docker image.
+- `.env` is excluded from the Docker build but passed to the container with `--env-file`.
+- The Docker Hub badge displays the latest recognized numbered version automatically, subject to caching.
+- Publishing commands use `vX.X` as a placeholder; deployment examples use the publisher-maintained `:latest` tag.
+- Existing containers must be recreated to run an updated image.
diff --git a/backend/app.py b/backend/app/app.py
similarity index 88%
rename from backend/app.py
rename to backend/app/app.py
index 4181fc8..01eb8de 100644
--- a/backend/app.py
+++ b/backend/app/app.py
@@ -4,13 +4,13 @@
from flask_cors import CORS
from flask_pymongo import PyMongo
import os, json, datetime
-import helpers.scholarlyapi as sa #This is for querying and getting paper abstracts
-import helpers.cryptographic_helpers as ch #Generating chat hashes
-from helpers.llm.promptBuilder import Prompt #for building prompts
-import helpers.llm.llmPromptingUtils as pu #for actually calling the AI API
-from helpers.RAG.RetrieverManager import RetrieverManager
-from helpers.db.mongodbhelpers import MongoHelper
-from helpers.llm.llmReturnFormat import Response
+import app.helpers.scholarlyapi as sa #This is for querying and getting paper abstracts
+import app.helpers.cryptographic_helpers as ch #Generating chat hashes
+from app.helpers.llm.promptBuilder import Prompt #for building prompts
+import app.helpers.llm.llmPromptingUtils as pu #for actually calling the AI API
+from app.helpers.RAG.RetrieverManager import RetrieverManager
+from app.helpers.db.mongodbhelpers import MongoHelper
+from app.helpers.llm.llmReturnFormat import Response
# from google.genai.types import HttpOptions
@@ -23,17 +23,16 @@
mh = MongoHelper(mongo)
CORS(app)
-hash_byte_length = 16
-chat_hash_length = 8
-message_history_limit = 30
-RAG_doc_limit = 30
+hash_byte_length = int(os.getenv("hash_byte_length", 16))
+chat_hash_length = int(os.getenv("chat_hash_length", 8))
+message_history_limit = int(os.getenv("message_history_limit", 30))
gemini_key = os.getenv('GEMINI_API_KEY')
gpt_key = os.getenv('GPT_API_KEY')
rm = RetrieverManager(
mongo_uri=os.getenv("MONGO_URI"),
- namespace="SLRMentor.document_rag",
- index_name="vector_index",
- top_k=RAG_doc_limit
+ namespace= os.getenv("MONGO_DB_NAME")+ "." + os.getenv("MONGO_DB_RAG_COLLECTION_NAME"),
+ index_name=(os.getenv("VECTOR_SEARCH_INDEX")),
+ top_k=int(os.getenv("RAG_doc_limit"))
)
@@ -111,8 +110,7 @@ def create_chat():
print(e)
return_request["message"] = str(e)
- finally:
- return jsonify(return_request), status_code
+ return jsonify(return_request), status_code
@app.route("/getsystemcontext", methods=['POST'])
def get_system_context():
@@ -150,8 +148,7 @@ def get_system_context():
status_code = 500
return_request["message"] = str(e)
- finally:
- return jsonify(return_request), status_code
+ return jsonify(return_request), status_code
@app.route("/getchathistory", methods=['POST'])
def get_chat_history():
@@ -193,8 +190,7 @@ def get_chat_history():
status_code = 500
return_request["message"] = str(e)
- finally:
- return jsonify(return_request), status_code
+ return jsonify(return_request), status_code
@app.route("/getcriteriachathistory", methods=['POST'])
def get_criteria_chat_history():
@@ -236,8 +232,7 @@ def get_criteria_chat_history():
status_code = 500
return_request["message"] = str(e)
- finally:
- return jsonify(return_request), status_code
+ return jsonify(return_request), status_code
@app.route("/getmentorchathistory", methods=['POST'])
def get_mentor_chat_history():
@@ -279,8 +274,7 @@ def get_mentor_chat_history():
status_code = 500
return_request["message"] = str(e)
- finally:
- return jsonify(return_request), status_code
+ return jsonify(return_request), status_code
@app.route("/setsystemcontext", methods=['POST'])
def set_system_context():
@@ -323,8 +317,7 @@ def set_system_context():
status_code = 500
return_request["message"] = str(e)
- finally:
- return jsonify(return_request), status_code
+ return jsonify(return_request), status_code
@app.route("/prompt", methods=['POST'])
def prompt():
@@ -363,7 +356,7 @@ def prompt():
raise ValueError("Chat with given hash doesnt exsist")
sys_context = chat_doc2.get("system_context", "")
#RAG
- top_docs = rm.get_relevant_documents_safe(data["user_message"])
+ top_docs = rm.get_relevant_documents(data["user_message"])
formatted_docs = rm.format_docs(top_docs)
rag_context_formated_docs = rm.format_docs_prompt(formatted_docs, prescript="Contex", count=1)
@@ -371,30 +364,30 @@ def prompt():
paper_context = ""
base_prompt = ""
# Read all prompt components using UTF-8 to prevent decode issues
- with open("helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt", "r", encoding="utf-8") as f:
identify_kw_prompt = f.read()
- with open("helpers/llm/prompts/searchstrings/userInputPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/searchstrings/userInputPrompt.txt", "r", encoding="utf-8") as f:
user_input_prompt = f.read()
- with open("helpers/llm/prompts/searchstrings/specificationFollowup.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/searchstrings/specificationFollowup.txt", "r", encoding="utf-8") as f:
end_specification = f.read()
user_input = f'User Input: {data["user_message"]} \n \n'
if current_search_string.strip() == "":
- with open("helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt", "r", encoding="utf-8") as f:
base_prompt = f.read()
paper_context = ""
current_search_string_format = "General"
else:
- with open("helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt", "r", encoding="utf-8") as f:
base_prompt = f.read()
paper_context = f'Current search string: {current_search_string} \n \n'
- with open("helpers/llm/prompts/conversion/3_formatContext.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/conversion/3_formatContext.txt", "r", encoding="utf-8") as f:
user_input_context = f.read()
- with open("helpers/llm/prompts/conversion/" + str(valid_ss_conversions.get(current_search_string_format, valid_ss_conversions["General"])), "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/conversion/" + str(valid_ss_conversions.get(current_search_string_format, valid_ss_conversions["General"])), "r", encoding="utf-8") as f:
current_format = f.read()
paper_context = paper_context + user_input_context + current_format
@@ -477,8 +470,7 @@ def prompt():
status_code = 500
return_request["message"] = str(e)
- finally:
- return jsonify(return_request), status_code
+ return jsonify(return_request), status_code
def check_missing_or_blank_fields(data, request_required_fields):
@@ -567,21 +559,21 @@ def convert():
# -------------------------------------------------
try:
with open(
- "helpers/llm/prompts/conversion/1_BasePrompt.txt",
+ "app/helpers/llm/prompts/conversion/1_BasePrompt.txt",
"r",
encoding="utf-8"
) as f:
base_prompt = f.read()
with open(
- "helpers/llm/prompts/conversion/2_userInputPrompt.txt",
+ "app/helpers/llm/prompts/conversion/2_userInputPrompt.txt",
"r",
encoding="utf-8"
) as f:
user_input_prompt = f.read()
with open(
- "helpers/llm/prompts/conversion/3_formatContext.txt",
+ "app/helpers/llm/prompts/conversion/3_formatContext.txt",
"r",
encoding="utf-8"
) as f:
@@ -589,14 +581,14 @@ def convert():
current_format_filename = valid_ss_conversions[current_search_string_format]
with open(
- "helpers/llm/prompts/conversion/" + str(current_format_filename),
+ "app/helpers/llm/prompts/conversion/" + str(current_format_filename),
"r",
encoding="utf-8"
) as f:
current_format = f.read()
with open(
- "helpers/llm/prompts/conversion/5_conversionContext.txt",
+ "app/helpers/llm/prompts/conversion/5_conversionContext.txt",
"r",
encoding="utf-8"
) as f:
@@ -604,14 +596,14 @@ def convert():
convert_to_format_filename = valid_ss_conversions[requested_conversion_format]
with open(
- "helpers/llm/prompts/conversion/" + str(convert_to_format_filename).strip(),
+ "app/helpers/llm/prompts/conversion/" + str(convert_to_format_filename).strip(),
"r",
encoding="utf-8"
) as f:
convert_to_format = f.read()
with open(
- "helpers/llm/prompts/conversion/specificationFollowup.txt",
+ "app/helpers/llm/prompts/conversion/specificationFollowup.txt",
"r",
encoding="utf-8"
) as f:
@@ -781,7 +773,7 @@ def criteria():
raise ValueError("Chat with given hash doesnt exsist")
sys_context = chat_doc2.get("system_context", "")
#RAG
- top_docs = rm.get_relevant_documents_safe(data["user_message"])
+ top_docs = rm.get_relevant_documents(data["user_message"])
formatted_docs = rm.format_docs(top_docs)
rag_context_formated_docs = rm.format_docs_prompt(formatted_docs, prescript="Contex", count=1)
@@ -789,20 +781,20 @@ def criteria():
paper_context = ""
base_prompt = ""
# Read all prompt components using UTF-8 to prevent decode issues
- with open("helpers/llm/prompts/criteria/userInputPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/criteria/userInputPrompt.txt", "r", encoding="utf-8") as f:
user_input_prompt = f.read()
- with open("helpers/llm/prompts/criteria/specificationFollowup.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/criteria/specificationFollowup.txt", "r", encoding="utf-8") as f:
end_specification = f.read()
user_input = f'User Input: {data["user_message"]} \n \n'
if current_criteria.strip() == "":
- with open("helpers/llm/prompts/criteria/baseQuestionPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/criteria/baseQuestionPrompt.txt", "r", encoding="utf-8") as f:
base_prompt = f.read()
paper_context = ""
else:
- with open("helpers/llm/prompts/criteria/baseFollowupPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/criteria/baseFollowupPrompt.txt", "r", encoding="utf-8") as f:
base_prompt = f.read()
paper_context = f"Current inclusion/exclusion criteria:\n{current_criteria}\n\n"
@@ -873,45 +865,38 @@ def criteria():
status_code = 500
return_request["message"] = str(e)
- finally:
- return jsonify(return_request), status_code
+ return jsonify(return_request), status_code
@app.route("/ragquery", methods=["POST"])
def rag_query():
- request_required_fields = ["user_message"]
- return_request = {
- "status": False,
- "message": "",
- "user_message": "",
- "top_documents": []
- }
- status_code = 401
try:
- data = request.json
- if check_missing_or_blank_fields(data, request_required_fields):
- raise ValueError("Request missing required fields")
+ data = request.get_json(silent=True)
- user_message = data["user_message"]
+ if not isinstance(data, dict):
+ return jsonify({"error": "Invalid JSON body"}), 400
- # Safely retrieve documents
- top_docs = rm.get_relevant_documents_safe(user_message)
- formatted_docs_plaintext = rm.format_docs(top_docs)
+ query = data.get("query")
- return_request.update({
- "status": True,
- "message": "Successfully retrieved documents",
- "user_message": user_message,
- "top_documents": formatted_docs_plaintext
- })
- status_code = 200
+ if not isinstance(query, str) or not query.strip():
+ return jsonify({"error": "Query is required"}), 400
- except Exception as e:
- print(e)
- return_request["message"] = str(e)
- status_code = 500
+ query = query.strip()
+
+ # Native MongoDB retrieval (no LangChain).
+ documents = rm.get_relevant_documents(query)
+
+ return jsonify({
+ "query": query,
+ "total_results": len(documents),
+ "documents": documents
+ }), 200
+
+ except Exception:
+ app.logger.exception("RAG query failed")
- finally:
- return jsonify(return_request), status_code
+ return jsonify({
+ "error": "RAG query failed"
+ }), 500
@app.route("/mentor", methods=["POST"])
def mentor():
@@ -964,8 +949,8 @@ def mentor():
raise ValueError("Chat with given hash doesnt exsist")
sys_context = chat_doc2.get("system_context", "")
- # Safely retrieve documents
- top_docs = rm.get_relevant_documents_safe(user_message)
+ # Retrieve native dictionaries using MongoDB-hosted embeddings
+ top_docs = rm.get_relevant_documents(user_message)
formatted_docs = rm.format_docs(top_docs)
rag_context_formated_docs = rm.format_docs_prompt(formatted_docs, prescript="Contex", count=1)
@@ -973,16 +958,16 @@ def mentor():
paper_context = ""
base_prompt = ""
# Read all prompt components using UTF-8 to prevent decode issues
- with open("helpers/llm/prompts/mentor/userInputPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/mentor/userInputPrompt.txt", "r", encoding="utf-8") as f:
user_input_prompt = f.read()
- with open("helpers/llm/prompts/mentor/specificationFollowup.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/mentor/specificationFollowup.txt", "r", encoding="utf-8") as f:
end_specification = f.read()
user_input = f'User Input: {data["user_message"]} \n \n'
- with open("helpers/llm/prompts/mentor/baseQuestionPrompt.txt", "r", encoding="utf-8") as f:
+ with open("app/helpers/llm/prompts/mentor/baseQuestionPrompt.txt", "r", encoding="utf-8") as f:
base_prompt = f.read()
rag_context = "The following is some relevant context and information in no perticular order to help you answer the users question: \n" + "\n".join(rag_context_formated_docs) + "\n\n"
@@ -1045,5 +1030,4 @@ def mentor():
return_request["message"] = str(e)
status_code = 500
- finally:
- return jsonify(return_request), status_code
\ No newline at end of file
+ return jsonify(return_request), status_code
\ No newline at end of file
diff --git a/backend/app/helpers/RAG/RetrieverManager.py b/backend/app/helpers/RAG/RetrieverManager.py
new file mode 100644
index 0000000..fc9b8b0
--- /dev/null
+++ b/backend/app/helpers/RAG/RetrieverManager.py
@@ -0,0 +1,386 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from pymongo import MongoClient
+
+if TYPE_CHECKING:
+ from langchain_core.documents import Document
+
+
+class RetrieverManager:
+ """
+ Manages document retrieval from MongoDB Atlas Vector Search
+ using MongoDB's hosted Automated Embedding functionality.
+
+ Query embeddings are generated remotely by MongoDB Atlas.
+ No local embedding model is loaded or executed.
+
+ Two retrieval interfaces are provided:
+
+ 1. get_relevant_documents()
+ New retrieval method that returns native Python dictionaries.
+ Does not depend on LangChain for retrieval or formatting.
+
+ 2. get_relevant_documents_safe()
+ Legacy retrieval method that returns LangChain Document objects.
+ Maintained for backward compatibility with existing RAG code.
+
+ Both methods share the same MongoDB retrieval pipeline.
+
+ The class also provides utilities for formatting retrieved
+ documents and managing the MongoDB connection.
+ """
+
+ def __init__(
+ self,
+ mongo_uri: str,
+ namespace: str,
+ index_name: str = "autoembed_index",
+ top_k: int = 5
+ ):
+ """
+ Initialize the MongoDB Atlas retriever.
+
+ Args:
+ mongo_uri (str):
+ MongoDB Atlas connection string.
+
+ namespace (str):
+ MongoDB database and collection in the format
+ "database.collection".
+
+ index_name (str):
+ Name of the MongoDB Atlas Vector Search index.
+ Defaults to "autoembed_index".
+
+ top_k (int):
+ Maximum number of relevant documents to retrieve.
+ Defaults to 5.
+
+ Raises:
+ ValueError:
+ If top_k is invalid or namespace is incorrectly formatted.
+
+ Notes:
+ Initializes the MongoDB client and selects the collection.
+ Does not load any embedding model locally.
+
+ MongoClient manages connection pooling automatically.
+ The connection is established lazily when needed.
+ """
+
+ self.mongo_uri = mongo_uri
+ self.namespace = namespace
+ self.index_name = index_name
+ self.top_k = top_k
+
+ if not isinstance(top_k, int) or isinstance(top_k, bool) or top_k < 1:
+ raise ValueError("top_k must be a positive integer.")
+
+ if not isinstance(namespace, str) or "." not in namespace:
+ raise ValueError(
+ "namespace must have the format 'database.collection'."
+ )
+
+ database_name, collection_name = namespace.split(".", 1)
+
+ if not database_name or not collection_name:
+ raise ValueError("Invalid MongoDB namespace.")
+
+ # Initialize MongoDB client.
+ self.client = MongoClient(
+ self.mongo_uri,
+ serverSelectionTimeoutMS=10000
+ )
+
+ # Select the target database and collection.
+ self.collection = self.client[database_name][collection_name]
+
+ # ============================================================
+ # New Retrieval Method (Native MongoDB)
+ # ============================================================
+
+ def get_relevant_documents(self, query: str) -> list[dict]:
+ """
+ Retrieve relevant documents directly from MongoDB Atlas.
+
+ This is the NEW retrieval method and should be preferred
+ for new implementations.
+
+ Uses MongoDB Atlas Automated Embedding to convert the
+ query into a vector remotely and retrieve semantically
+ similar document chunks.
+
+ Unlike the legacy method, this method returns native
+ Python dictionaries and does not use LangChain.
+
+ Args:
+ query (str):
+ Natural-language search query.
+
+ Returns:
+ list[dict]:
+ List of MongoDB documents containing:
+
+ - content: Retrieved document text.
+ - paper_id: Associated paper identifier.
+ - source: Original document source.
+ - page: Page number.
+ - chunk_index: Position of the text chunk.
+ - score: MongoDB vector similarity score.
+
+ Only fields present in the collection are returned.
+
+ Raises:
+ ValueError:
+ If the query is empty or invalid.
+
+ pymongo.errors.PyMongoError:
+ If the MongoDB query fails.
+
+ Notes:
+ - No local embedding generation occurs.
+ - MongoDB Atlas handles query embedding remotely.
+ - Results are limited by self.top_k.
+ - The Vector Search index must support autoEmbed.
+ - The indexed text field must be "content".
+ """
+
+ if not isinstance(query, str) or not query.strip():
+ raise ValueError("Query must be a non-empty string.")
+
+ pipeline = [
+ {
+ "$vectorSearch": {
+ "index": self.index_name,
+ "path": "content",
+ "query": {
+ "text": query.strip()
+ },
+ "numCandidates": min(
+ max(self.top_k * 10, 50),
+ 10000
+ ),
+ "limit": self.top_k
+ }
+ },
+ {
+ "$project": {
+ "_id": 0,
+ "paper_id": 1,
+ "source": 1,
+ "page": 1,
+ "chunk_index": 1,
+ "content": 1,
+ "score": {
+ "$meta": "vectorSearchScore"
+ }
+ }
+ }
+ ]
+
+ return list(self.collection.aggregate(pipeline))
+
+ # ============================================================
+ # Legacy Retrieval Method (LangChain Compatibility)
+ # ============================================================
+
+ def get_relevant_documents_safe(self, query: str) -> list[Document]:
+ """
+ Retrieve relevant documents as LangChain Document objects.
+
+ LEGACY METHOD:
+ Maintained for backward compatibility with code that
+ expects LangChain Document objects.
+
+ Internally calls the new get_relevant_documents() method
+ and converts the returned MongoDB dictionaries into
+ LangChain Document objects.
+
+ This method does not use LangChain for vector retrieval.
+ LangChain is only used for the Document data structure.
+
+ Args:
+ query (str):
+ Natural-language search query.
+
+ Returns:
+ list[Document]:
+ List of LangChain Document objects containing:
+
+ - page_content: Retrieved text.
+ - metadata: Source information, page number,
+ chunk index, paper ID, and similarity score.
+
+ Raises:
+ ValueError:
+ If the query is empty or invalid.
+
+ pymongo.errors.PyMongoError:
+ If MongoDB retrieval fails.
+
+ Notes:
+ - No local embedding model is loaded.
+ - Retrieval is performed using MongoDB Atlas.
+ - Conversion adds LangChain compatibility overhead.
+ - Prefer get_relevant_documents() for new code.
+ """
+
+ # Only legacy callers need the optional LangChain Document type.
+ from langchain_core.documents import Document
+
+ results = self.get_relevant_documents(query)
+
+ documents = []
+
+ for result in results:
+ content = result.get("content", "")
+
+ if not content:
+ continue
+
+ metadata = {
+ key: value
+ for key, value in result.items()
+ if key != "content"
+ }
+
+ documents.append(
+ Document(
+ page_content=content,
+ metadata=metadata
+ )
+ )
+
+ return documents
+
+ # ============================================================
+ # Document Formatting Helpers
+ # ============================================================
+
+ @staticmethod
+ def format_docs(docs: list) -> list[str]:
+ """
+ Extract text content from retrieved documents.
+
+ Supports both retrieval formats:
+
+ 1. Native MongoDB dictionaries returned by
+ get_relevant_documents().
+
+ 2. LangChain Document objects returned by
+ get_relevant_documents_safe().
+
+ Args:
+ docs (list):
+ List of MongoDB dictionaries or LangChain Documents.
+
+ Returns:
+ list[str]:
+ List containing only the text content of each document.
+
+ Notes:
+ - Empty document content is ignored.
+ - Document metadata is not included in the output.
+ - Useful for preparing context for LLM prompts.
+ """
+
+ texts = []
+
+ for doc in docs:
+ if isinstance(doc, dict):
+ text = (
+ doc.get("content")
+ or doc.get("text")
+ or doc.get("page_content")
+ )
+ else:
+ text = (
+ getattr(doc, "page_content", None)
+ or getattr(doc, "text", None)
+ )
+
+ if text:
+ texts.append(text)
+
+ return texts
+
+ @staticmethod
+ def format_docs_prompt(
+ docs: list[str],
+ prescript: str = "",
+ count: int = 0
+ ) -> list[str]:
+ """
+ Add a prefix before each document's text.
+
+ Useful when preparing retrieved content for inclusion
+ in an LLM prompt.
+
+ Args:
+ docs (list[str]):
+ List of document text strings.
+
+ prescript (str):
+ Prefix to place before each document.
+ Defaults to an empty string.
+
+ count (int):
+ If 1, appends a sequential number to each prefix.
+ Otherwise, uses the same prefix for all documents.
+
+ Returns:
+ list[str]:
+ List of formatted text strings.
+
+ Example:
+ Input:
+ docs = ["First text", "Second text"]
+ prescript = "Document"
+ count = 1
+
+ Output:
+ [
+ "Document 1: First text",
+ "Document 2: Second text"
+ ]
+
+ Notes:
+ If prescript is empty, the original list is returned
+ unchanged.
+ """
+
+ if prescript == "":
+ return docs
+
+ texts = []
+
+ for i, d in enumerate(docs):
+ if count == 1:
+ pres = f"{prescript} {i + 1}: "
+ else:
+ pres = f"{prescript}: "
+
+ texts.append(pres + d)
+
+ return texts
+
+ # ============================================================
+ # Connection Management
+ # ============================================================
+
+ def close(self) -> None:
+ """
+ Close the MongoDB client and its connection pool.
+
+ Should be called when the RetrieverManager is no longer
+ needed, typically during application shutdown.
+
+ Notes:
+ - Releases MongoDB connection resources.
+ - Does not delete documents or modify the database.
+ - A closed MongoClient should not be reused.
+ """
+
+ self.client.close()
diff --git a/backend/helpers/cryptographic_helpers.py b/backend/app/helpers/cryptographic_helpers.py
similarity index 100%
rename from backend/helpers/cryptographic_helpers.py
rename to backend/app/helpers/cryptographic_helpers.py
diff --git a/backend/helpers/db/mongodbhelpers.py b/backend/app/helpers/db/mongodbhelpers.py
similarity index 100%
rename from backend/helpers/db/mongodbhelpers.py
rename to backend/app/helpers/db/mongodbhelpers.py
diff --git a/backend/helpers/lenzapi.py b/backend/app/helpers/lenzapi.py
similarity index 100%
rename from backend/helpers/lenzapi.py
rename to backend/app/helpers/lenzapi.py
diff --git a/backend/helpers/llm/llmPromptingUtils.py b/backend/app/helpers/llm/llmPromptingUtils.py
similarity index 98%
rename from backend/helpers/llm/llmPromptingUtils.py
rename to backend/app/helpers/llm/llmPromptingUtils.py
index 7b5c0d8..173b7b1 100644
--- a/backend/helpers/llm/llmPromptingUtils.py
+++ b/backend/app/helpers/llm/llmPromptingUtils.py
@@ -2,7 +2,7 @@
from pydantic import BaseModel, ValidationError
from openai import OpenAI
from google import genai
-from helpers.llm.llmReturnFormat import Response, ResponseCriteria, ResponseMentor
+from app.helpers.llm.llmReturnFormat import Response, ResponseCriteria, ResponseMentor
import json
diff --git a/backend/helpers/llm/llmReturnFormat.py b/backend/app/helpers/llm/llmReturnFormat.py
similarity index 100%
rename from backend/helpers/llm/llmReturnFormat.py
rename to backend/app/helpers/llm/llmReturnFormat.py
diff --git a/backend/helpers/llm/promptBuilder.py b/backend/app/helpers/llm/promptBuilder.py
similarity index 100%
rename from backend/helpers/llm/promptBuilder.py
rename to backend/app/helpers/llm/promptBuilder.py
diff --git a/backend/helpers/llm/prompts/conversion/1_BasePrompt.txt b/backend/app/helpers/llm/prompts/conversion/1_BasePrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/conversion/1_BasePrompt.txt
rename to backend/app/helpers/llm/prompts/conversion/1_BasePrompt.txt
diff --git a/backend/helpers/llm/prompts/conversion/2_userInputPrompt.txt b/backend/app/helpers/llm/prompts/conversion/2_userInputPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/conversion/2_userInputPrompt.txt
rename to backend/app/helpers/llm/prompts/conversion/2_userInputPrompt.txt
diff --git a/backend/helpers/llm/prompts/conversion/3_formatContext.txt b/backend/app/helpers/llm/prompts/conversion/3_formatContext.txt
similarity index 100%
rename from backend/helpers/llm/prompts/conversion/3_formatContext.txt
rename to backend/app/helpers/llm/prompts/conversion/3_formatContext.txt
diff --git a/backend/helpers/llm/prompts/conversion/4_generalformat.txt b/backend/app/helpers/llm/prompts/conversion/4_generalformat.txt
similarity index 100%
rename from backend/helpers/llm/prompts/conversion/4_generalformat.txt
rename to backend/app/helpers/llm/prompts/conversion/4_generalformat.txt
diff --git a/backend/helpers/llm/prompts/conversion/4_googlescholarformat.txt b/backend/app/helpers/llm/prompts/conversion/4_googlescholarformat.txt
similarity index 100%
rename from backend/helpers/llm/prompts/conversion/4_googlescholarformat.txt
rename to backend/app/helpers/llm/prompts/conversion/4_googlescholarformat.txt
diff --git a/backend/helpers/llm/prompts/conversion/4_ieeeXploreformat.txt b/backend/app/helpers/llm/prompts/conversion/4_ieeeXploreformat.txt
similarity index 100%
rename from backend/helpers/llm/prompts/conversion/4_ieeeXploreformat.txt
rename to backend/app/helpers/llm/prompts/conversion/4_ieeeXploreformat.txt
diff --git a/backend/helpers/llm/prompts/conversion/5_conversionContext.txt b/backend/app/helpers/llm/prompts/conversion/5_conversionContext.txt
similarity index 100%
rename from backend/helpers/llm/prompts/conversion/5_conversionContext.txt
rename to backend/app/helpers/llm/prompts/conversion/5_conversionContext.txt
diff --git a/backend/helpers/llm/prompts/conversion/specificationFollowup.txt b/backend/app/helpers/llm/prompts/conversion/specificationFollowup.txt
similarity index 100%
rename from backend/helpers/llm/prompts/conversion/specificationFollowup.txt
rename to backend/app/helpers/llm/prompts/conversion/specificationFollowup.txt
diff --git a/backend/helpers/llm/prompts/criteria/baseFollowupPrompt.txt b/backend/app/helpers/llm/prompts/criteria/baseFollowupPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/criteria/baseFollowupPrompt.txt
rename to backend/app/helpers/llm/prompts/criteria/baseFollowupPrompt.txt
diff --git a/backend/helpers/llm/prompts/criteria/baseQuestionPrompt.txt b/backend/app/helpers/llm/prompts/criteria/baseQuestionPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/criteria/baseQuestionPrompt.txt
rename to backend/app/helpers/llm/prompts/criteria/baseQuestionPrompt.txt
diff --git a/backend/helpers/llm/prompts/criteria/specificationFollowup.txt b/backend/app/helpers/llm/prompts/criteria/specificationFollowup.txt
similarity index 100%
rename from backend/helpers/llm/prompts/criteria/specificationFollowup.txt
rename to backend/app/helpers/llm/prompts/criteria/specificationFollowup.txt
diff --git a/backend/helpers/llm/prompts/criteria/userInputPrompt.txt b/backend/app/helpers/llm/prompts/criteria/userInputPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/criteria/userInputPrompt.txt
rename to backend/app/helpers/llm/prompts/criteria/userInputPrompt.txt
diff --git a/backend/helpers/llm/prompts/mentor/baseQuestionPrompt.txt b/backend/app/helpers/llm/prompts/mentor/baseQuestionPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/mentor/baseQuestionPrompt.txt
rename to backend/app/helpers/llm/prompts/mentor/baseQuestionPrompt.txt
diff --git a/backend/helpers/llm/prompts/mentor/specificationFollowup.txt b/backend/app/helpers/llm/prompts/mentor/specificationFollowup.txt
similarity index 100%
rename from backend/helpers/llm/prompts/mentor/specificationFollowup.txt
rename to backend/app/helpers/llm/prompts/mentor/specificationFollowup.txt
diff --git a/backend/helpers/llm/prompts/mentor/userInputPrompt.txt b/backend/app/helpers/llm/prompts/mentor/userInputPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/mentor/userInputPrompt.txt
rename to backend/app/helpers/llm/prompts/mentor/userInputPrompt.txt
diff --git a/backend/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt b/backend/app/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt
rename to backend/app/helpers/llm/prompts/searchstrings/baseFollowupPrompt.txt
diff --git a/backend/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt b/backend/app/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt
rename to backend/app/helpers/llm/prompts/searchstrings/baseQuestionPrompt.txt
diff --git a/backend/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt b/backend/app/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt
rename to backend/app/helpers/llm/prompts/searchstrings/identifyingKeyWordsPrompt.txt
diff --git a/backend/helpers/llm/prompts/searchstrings/specificationFollowup.txt b/backend/app/helpers/llm/prompts/searchstrings/specificationFollowup.txt
similarity index 100%
rename from backend/helpers/llm/prompts/searchstrings/specificationFollowup.txt
rename to backend/app/helpers/llm/prompts/searchstrings/specificationFollowup.txt
diff --git a/backend/helpers/llm/prompts/searchstrings/userInputPrompt.txt b/backend/app/helpers/llm/prompts/searchstrings/userInputPrompt.txt
similarity index 100%
rename from backend/helpers/llm/prompts/searchstrings/userInputPrompt.txt
rename to backend/app/helpers/llm/prompts/searchstrings/userInputPrompt.txt
diff --git a/backend/helpers/scholarlyapi.py b/backend/app/helpers/scholarlyapi.py
similarity index 100%
rename from backend/helpers/scholarlyapi.py
rename to backend/app/helpers/scholarlyapi.py
diff --git a/backend/helpers/xploreapi/xploreapi.py b/backend/app/helpers/xploreapi/xploreapi.py
similarity index 100%
rename from backend/helpers/xploreapi/xploreapi.py
rename to backend/app/helpers/xploreapi/xploreapi.py
diff --git a/backend/environment.yml b/backend/environment.yml
deleted file mode 100644
index 5682fa9..0000000
--- a/backend/environment.yml
+++ /dev/null
@@ -1,130 +0,0 @@
-name: seallapp
-channels:
- - defaults
-dependencies:
- - python=3.10
- - pip
- - setuptools
- - wheel
- - ca-certificates
- - openssl
- - zlib
- - sqlite
- - tk
- - libffi
- - bzip2
- - xz
- - pip:
- - aiohappyeyeballs==2.6.1
- - aiohttp==3.12.13
- - aiosignal==1.4.0
- - alabaster==1.0.0
- - annotated-types==0.7.0
- - anyio==4.9.0
- - arrow==1.3.0
- - async-timeout==5.0.1
- - attrs==25.3.0
- - babel==2.17.0
- - bcrypt==4.3.0
- - beautifulsoup4==4.13.4
- - bibtexparser==1.4.3
- - blinker==1.9.0
- - cachetools==5.5.2
- - certifi==2025.4.26
- - cffi==1.17.1
- - charset-normalizer==3.4.2
- - click==8.2.1
- - colorama==0.4.6
- - deprecated==1.2.18
- - distro==1.9.0
- - dnspython==2.7.0
- - docutils==0.21.2
- - dotenv==0.9.9
- - et-xmlfile==2.0.0
- - exceptiongroup==1.3.0
- - fake-useragent==2.2.0
- - flask==3.1.1
- - flask-cors==6.0.0
- - flask-pymongo==3.0.1
- - free-proxy==1.1.3
- - frozenlist==1.7.0
- - google==3.0.0
- - google-ai-generativelanguage==0.6.15
- - google-api-core==2.25.1rc0
- - google-api-python-client==2.172.0
- - google-auth==2.40.3
- - google-auth-httplib2==0.2.0
- - google-genai==1.19.0
- - google-generativeai==0.8.5
- - googleapis-common-protos==1.70.0
- - grpcio==1.73.0
- - grpcio-status==1.71.0
- - gunicorn==23.0.0
- - h11==0.16.0
- - httpcore==1.0.9
- - httplib2==0.22.0
- - httpx==0.28.1
- - idna==3.10
- - imagesize==1.4.1
- - itsdangerous==2.2.0
- - jinja2==3.1.6
- - jiter==0.10.0
- - lxml==6.0.0
- - markupsafe==3.0.2
- - multidict==6.6.3
- - numpy==2.2.6
- - openai==1.93.0
- - openpyxl==3.1.5
- - outcome==1.3.0.post0
- - packaging==25.0
- - pandas==2.3.0
- - propcache==0.3.2
- - proto-plus==1.26.1
- - protobuf==5.29.5
- - pyasn1==0.6.1
- - pyasn1-modules==0.4.2
- - pycparser==2.22
- - pycurl==7.45.6
- - pydantic==2.11.5
- - pydantic-core==2.33.2
- - pygments==2.19.2
- - pymongo==4.13.0
- - pyparsing==3.2.3
- - pysocks==1.7.1
- - python-dateutil==2.9.0.post0
- - python-dotenv==1.1.0
- - pytz==2025.2
- - requests==2.32.4
- - rsa==4.9.1
- - scholarly==1.7.11
- - selenium==4.34.0
- - six==1.17.0
- - sniffio==1.3.1
- - snowballstemmer==3.0.1
- - sortedcontainers==2.4.0
- - soupsieve==2.7
- - sphinx==8.1.3
- - sphinx-rtd-theme==3.0.2
- - sphinxcontrib-applehelp==2.0.0
- - sphinxcontrib-devhelp==2.0.0
- - sphinxcontrib-htmlhelp==2.1.0
- - sphinxcontrib-jquery==4.1
- - sphinxcontrib-jsmath==1.0.1
- - sphinxcontrib-qthelp==2.0.0
- - sphinxcontrib-serializinghtml==2.0.0
- - tomli==2.2.1
- - tqdm==4.67.1
- - trio==0.30.0
- - trio-websocket==0.12.2
- - types-python-dateutil==2.9.0.20250516
- - typing-extensions==4.14.0
- - typing-inspection==0.4.1
- - tzdata==2025.2
- - uritemplate==4.2.0
- - urllib3==2.4.0
- - websocket-client==1.8.0
- - websockets==15.0.1
- - werkzeug==3.1.3
- - wrapt==1.17.2
- - wsproto==1.2.0
- - yarl==1.20.1
diff --git a/backend/helpers/RAG/RetrieverManager.py b/backend/helpers/RAG/RetrieverManager.py
deleted file mode 100644
index 8c4bdb3..0000000
--- a/backend/helpers/RAG/RetrieverManager.py
+++ /dev/null
@@ -1,103 +0,0 @@
-from langchain_community.embeddings import HuggingFaceEmbeddings
-from langchain_community.vectorstores import MongoDBAtlasVectorSearch
-import os
-
-class RetrieverManager:
- """
- Manages initialization and use of a MongoDB Atlas vector retriever,
- along with helper formatting utilities for retrieved documents.
- """
-
- def __init__(
- self,
- mongo_uri: str,
- namespace: str = "SLRMentor.document_rag",
- index_name: str = "vector_index",
- top_k: int = 5
- ):
- """
- Initialize the retriever.
- """
- self.mongo_uri = mongo_uri
- self.namespace = namespace
- self.index_name = index_name
- self.top_k = top_k
-
- # Initialize embedding model
- self.embedding_model = HuggingFaceEmbeddings(
- model_name="sentence-transformers/all-MiniLM-L6-v2"
- )
-
- # Initialize vector store
- self.vector_store = MongoDBAtlasVectorSearch.from_connection_string(
- connection_string=self.mongo_uri,
- namespace=self.namespace,
- embedding=self.embedding_model,
- index_name=self.index_name
- )
-
- # Initialize retriever
- self.retriever = self.vector_store.as_retriever(
- search_kwargs={"k": self.top_k}
- )
-
- # ---------------------------
- # Retriever Access Helpers
- # ---------------------------
- def get_relevant_documents_safe(self, query: str):
- """
- Safely call whichever method the retriever provides.
- """
- if hasattr(self.retriever, "get_relevant_documents"):
- return self.retriever.get_relevant_documents(query)
- elif hasattr(self.retriever, "retrieve"):
- return self.retriever.retrieve(query)
- elif hasattr(self.retriever, "invoke"):
- return self.retriever.invoke(query)
- else:
- raise AttributeError("Retriever does not support any known query method.")
-
- # ---------------------------
- # Document Formatting Helpers
- # ---------------------------
- @staticmethod
- def format_docs(docs: list) -> list:
- """
- Extract text from list of documents (dicts or LangChain Document objects).
- """
- texts = []
-
- for doc in docs:
- if isinstance(doc, dict): # MongoDB dict
- text = doc.get("text") or doc.get("page_content")
- else: # LangChain Document
- text = (
- getattr(doc, "page_content", None)
- or getattr(doc, "text", None)
- )
- if text:
- texts.append(text)
-
- return texts
-
- @staticmethod
- def format_docs_prompt(
- docs: list,
- prescript: str = "",
- count: int = 0
- ) -> list:
- """
- Same as format_docs but also injects a prescript before each item.
- """
- if prescript == "":
- return docs
-
- texts = []
- for i, d in enumerate(docs):
- if count == 1:
- pres = f"{prescript} {i+1}: "
- else:
- pres = f"{prescript}: "
- texts.append(pres + d)
-
- return texts
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
new file mode 100644
index 0000000..1e8cfa8
--- /dev/null
+++ b/backend/pyproject.toml
@@ -0,0 +1,18 @@
+[project]
+name = "SLRmentor_Backend"
+version = "0.1.0"
+description = "SLRmentor backend is the api server that servers the frontends requests"
+readme = "README.md"
+requires-python = ">=3.14.5"
+dependencies = [
+ "flask>=3.1.3",
+ "flask-cors>=6.0.5",
+ "flask-pymongo>=2.3.0",
+ "google-genai>=2.10.0",
+ "gunicorn>=26.1.0",
+ "openai>=2.44.0",
+ "pydantic>=2.13.4",
+ "pymongo[srv]>=4.6.1",
+ "python-dotenv>=1.2.4",
+ "requests>=2.34.2",
+]
diff --git a/backend/requirements.txt b/backend/requirements.txt
deleted file mode 100644
index f72a736..0000000
--- a/backend/requirements.txt
+++ /dev/null
@@ -1,167 +0,0 @@
-# Heavy / core ML and data packages first
-torch==2.9.0
-transformers==4.57.1
-sentence-transformers==5.1.2
-#numpy==2.2.6
-scipy==1.15.3
-#pandas==2.3.0
-scikit-learn==1.7.2
-
-# Remaining packages (alphabetical-ish, non-GPU)
-aiohappyeyeballs==2.6.1
-aiohttp==3.12.13
-aiosignal==1.4.0
-alabaster==1.0.0
-annotated-types==0.7.0
-anyio==4.9.0
-arrow==1.3.0
-async-timeout==4.0.3
-attrs==25.3.0
-babel==2.17.0
-bcrypt==4.3.0
-beautifulsoup4==4.13.4
-bibtexparser==1.4.3
-blinker==1.9.0
-cachetools==5.5.2
-certifi==2025.4.26
-cffi==1.17.1
-charset-normalizer==3.4.2
-click==8.2.1
-colorama==0.4.6
-dataclasses-json==0.6.7
-Deprecated==1.2.18
-distro==1.9.0
-dnspython==2.7.0
-docutils==0.21.2
-dotenv==0.9.9
-et_xmlfile==2.0.0
-exceptiongroup==1.3.0
-fake-useragent==2.2.0
-filelock==3.20.0
-Flask==3.1.1
-flask-cors==6.0.0
-Flask-PyMongo==3.0.1
-free_proxy==1.1.3
-frozenlist==1.7.0
-fsspec==2025.9.0
-google==3.0.0
-google-ai-generativelanguage==0.6.15
-google-api-core==2.25.1rc0
-google-api-python-client==2.172.0
-google-auth==2.40.3
-google-auth-httplib2==0.2.0
-google-genai==1.19.0
-google-generativeai==0.8.5
-googleapis-common-protos==1.70.0
-#gpt4all==2.8.2
-greenlet==3.2.4
-grpcio==1.73.0
-grpcio-status==1.71.0
-gunicorn==23.0.0
-h11==0.16.0
-httpcore==1.0.9
-httplib2==0.22.0
-httpx==0.28.1
-httpx-sse==0.4.3
-huggingface-hub==0.36.0
-idna==3.10
-imagesize==1.4.1
-itsdangerous==2.2.0
-Jinja2==3.1.6
-jiter==0.10.0
-joblib==1.5.2
-jsonpatch==1.33
-jsonpointer==3.0.0
-langchain==1.0.2
-langchain-classic==1.0.0
-langchain-community==0.4
-langchain-core==1.0.1
-langchain-huggingface==1.0.0
-langchain-mongodb==0.7.1
-langchain-text-splitters==1.0.0
-langchainhub==0.1.21
-langgraph==1.0.1
-langgraph-checkpoint==3.0.0
-langgraph-prebuilt==1.0.1
-langgraph-sdk==0.2.9
-langsmith==0.4.38
-lark==1.3.0
-lxml==6.0.0
-MarkupSafe==3.0.2
-marshmallow==3.26.1
-mpmath==1.3.0
-multidict==6.6.3
-mypy_extensions==1.1.0
-networkx==3.4.2
-openai==1.93.0
-openpyxl==3.1.5
-orjson==3.11.4
-ormsgpack==1.11.0
-outcome==1.3.0.post0
-packaging==24.2
-pillow==12.0.0
-propcache==0.3.2
-proto-plus==1.26.1
-protobuf==5.29.5
-pyasn1==0.6.1
-pyasn1_modules==0.4.2
-pycparser==2.22
-pycurl==7.45.6
-pydantic==2.11.5
-pydantic-settings==2.11.0
-pydantic_core==2.33.2
-Pygments==2.19.2
-pymongo==4.15.3
-pyparsing==3.2.3
-pypdf==6.1.3
-PySocks==1.7.1
-python-dateutil==2.9.0.post0
-python-dotenv==1.1.0
-pytz==2025.2
-PyYAML==6.0.3
-regex==2025.10.23
-requests==2.32.5
-requests-toolbelt==1.0.0
-rsa==4.9.1
-safetensors==0.6.2
-scholarly==1.7.11
-selenium==4.34.0
-six==1.17.0
-sniffio==1.3.1
-snowballstemmer==3.0.1
-sortedcontainers==2.4.0
-soupsieve==2.7
-Sphinx==8.1.3
-sphinx-rtd-theme==3.0.2
-sphinxcontrib-applehelp==2.0.0
-sphinxcontrib-devhelp==2.0.0
-sphinxcontrib-htmlhelp==2.1.0
-sphinxcontrib-jquery==4.1
-sphinxcontrib-jsmath==1.0.1
-sphinxcontrib-qthelp==2.0.0
-sphinxcontrib-serializinghtml==2.0.0
-SQLAlchemy==2.0.44
-sympy==1.14.0
-tenacity==9.1.2
-threadpoolctl==3.6.0
-tokenizers==0.22.1
-tomli==2.2.1
-tqdm==4.67.1
-trio==0.30.0
-trio-websocket==0.12.2
-types-python-dateutil==2.9.0.20250516
-types-requests==2.32.4.20250913
-typing-inspect==0.9.0
-typing-inspection==0.4.1
-typing_extensions==4.14.0
-tzdata==2025.2
-uritemplate==4.2.0
-urllib3==2.4.0
-websocket-client==1.8.0
-websockets==15.0.1
-Werkzeug==3.1.3
-wrapt==1.17.2
-wsproto==1.2.0
-xxhash==3.6.0
-yarl==1.20.1
-zstandard==0.25.0
diff --git a/backend/uv.lock b/backend/uv.lock
new file mode 100644
index 0000000..830daad
--- /dev/null
+++ b/backend/uv.lock
@@ -0,0 +1,975 @@
+version = 1
+revision = 3
+requires-python = ">=3.14.5"
+
+[[package]]
+name = "annotated-types"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.15.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "typing-extensions", marker = "python_full_version < '3.15'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
+]
+
+[[package]]
+name = "blinker"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.7.22"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
+]
+
+[[package]]
+name = "cffi"
+version = "2.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" },
+ { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" },
+ { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" },
+ { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" },
+ { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" },
+ { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" },
+ { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" },
+ { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" },
+ { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" },
+ { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" },
+ { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" },
+ { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.5.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/33/1c/f41d4e74c28ab327ff3acd36053f7ea506c55872d7a90b0fa71aa3ab0c89/charset_normalizer-3.5.2.tar.gz", hash = "sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef", size = 172659, upload-time = "2026-09-30T04:39:23.398Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/ca/7aa91362a2f77ac8e9e28a9b902a74f7d0e11a851ef0d27a74308da8cd90/charset_normalizer-3.5.2-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187", size = 226010, upload-time = "2026-09-30T04:36:27.669Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/cf/ac8878d0322cf88a1aad4c7b147db32ca0bd806eb0060957b2e31486dbe6/charset_normalizer-3.5.2-cp314-cp314-android_24_x86_64.whl", hash = "sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad", size = 239266, upload-time = "2026-09-30T04:36:29.434Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/6d/9a08d7e0b29b7208e2c6c01dc56c8e0520e7c7beadbbfb024b58fd69c8a5/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf", size = 206783, upload-time = "2026-09-30T04:36:30.872Z" },
+ { url = "https://files.pythonhosted.org/packages/82/44/b0aa350280e6ff5a5492d17cf10460dd39d5ee848f872f7ba2df10607f60/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995", size = 210449, upload-time = "2026-09-30T04:36:32.625Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/8a/40db9aa9f5907bb0e6f8b6d64064bf8852fb33d4b813ff9414911df7647c/charset_normalizer-3.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424", size = 367815, upload-time = "2026-09-30T04:36:34.197Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/72/9c5e7707b57c8ddfa9ddf7b0b1d009d7fbab9e9e887d5b721060f37e307d/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13", size = 247095, upload-time = "2026-09-30T04:36:35.803Z" },
+ { url = "https://files.pythonhosted.org/packages/83/09/71e453691e927de4ddf792770cfaab3f49d494e222f66ea5e404bbd5e39c/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d", size = 233942, upload-time = "2026-09-30T04:36:37.407Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/86/85c84e4da8b27dd409577d9437926ff581c5f9d3c66038dc68c1a526de51/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4", size = 273762, upload-time = "2026-09-30T04:36:38.904Z" },
+ { url = "https://files.pythonhosted.org/packages/92/08/564955a4b5f2ccb410ab480bbe8c6a18063ff27f2d35458731c4a5335df9/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438", size = 269514, upload-time = "2026-09-30T04:36:40.469Z" },
+ { url = "https://files.pythonhosted.org/packages/18/24/bad3ac4271589df29cf5ce2f5ae490518a5739358052bd0d61209e6fea54/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a", size = 255056, upload-time = "2026-09-30T04:36:42.02Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/3e/350d89ad49916b86554d6f5f2d03ec1152148f87e5ff735106c6a03b1a36/charset_normalizer-3.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56", size = 251311, upload-time = "2026-09-30T04:36:43.577Z" },
+ { url = "https://files.pythonhosted.org/packages/56/5b/4970a2d154df502e133402906dd04e3ae7cada7b3011283c88d0479a2585/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd", size = 250190, upload-time = "2026-09-30T04:36:45.185Z" },
+ { url = "https://files.pythonhosted.org/packages/88/8c/f1a91bddc8fb47c2889e29ea7ea49a194eb0d9868675d786806519c00d76/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204", size = 237098, upload-time = "2026-09-30T04:36:46.689Z" },
+ { url = "https://files.pythonhosted.org/packages/24/0e/bb5dace3cc7e79068425386a6589c19b5a2ab5fefc2a46abea6919683332/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7", size = 274228, upload-time = "2026-09-30T04:36:48.31Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/79/b849ad523017ea9f5a45581bbebed91439e0cf42fd2860a6f64e358eb5a6/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd", size = 252401, upload-time = "2026-09-30T04:36:50.091Z" },
+ { url = "https://files.pythonhosted.org/packages/89/8c/75469d690cf47200bce8f6cad7655724fc23148e147abfc5ce78b5f65863/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc", size = 271846, upload-time = "2026-09-30T04:36:51.719Z" },
+ { url = "https://files.pythonhosted.org/packages/26/cd/6d52d3c7437cdcf2e310ce9f28f282e733d4ef60ed19105d1819c356255f/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874", size = 258151, upload-time = "2026-09-30T04:36:53.234Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/4c/070b38bdb5f49a70199fce923ec0726a49536a63ab262abbfcaaf351110b/charset_normalizer-3.5.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655", size = 143570, upload-time = "2026-09-30T04:36:54.816Z" },
+ { url = "https://files.pythonhosted.org/packages/81/84/9ebfc8ed6c8c4fcd8e726ff6bf220cc8deb3966e31dce9be8dd8aa017e64/charset_normalizer-3.5.2-cp314-cp314-win32.whl", hash = "sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0", size = 186412, upload-time = "2026-09-30T04:36:56.643Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/78/5ed86f743d4bc350db307e7636419a0a5ee1d91806d30c7f667bd5c80dae/charset_normalizer-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c", size = 210965, upload-time = "2026-09-30T04:36:58.205Z" },
+ { url = "https://files.pythonhosted.org/packages/53/94/a3a7698e9b1a395e1eb99ccd9a324be9347973bff4e72db2a06496d7cd27/charset_normalizer-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253", size = 200406, upload-time = "2026-09-30T04:36:59.764Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/48/c5dd00d5ef7791f02666de250a5bb6071e29b7e133cf4b835800b6d3bc27/charset_normalizer-3.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709", size = 396871, upload-time = "2026-09-30T04:37:01.543Z" },
+ { url = "https://files.pythonhosted.org/packages/12/c8/8379554b42e8368161d898476686947a0fdbd3e8865170d7909dcabfdee8/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084", size = 249711, upload-time = "2026-09-30T04:37:03.111Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/eb/2ddb1035d17320caa9f41682935123a9a250277b261c3efc86b2d2a21343/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb", size = 231146, upload-time = "2026-09-30T04:37:04.721Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/24/2ecb4bde104322cd7859d6594fcfa74649f8d90b3221c9feecbef149875b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f", size = 271552, upload-time = "2026-09-30T04:37:06.295Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/98/9d5f6ebc3aee9fef5d30b4aff11fb2ab7a1222b4064f8ef2c7c87cde217a/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09", size = 269810, upload-time = "2026-09-30T04:37:07.905Z" },
+ { url = "https://files.pythonhosted.org/packages/09/e1/a3b06a10461b1b7628853c934c644e03bc28e42767116afb52f19a56519b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80", size = 259845, upload-time = "2026-09-30T04:37:09.554Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/d3/6f561f74a296cf27d61775a1dc665ad13f3bff6a798810ca05907f37a7c4/charset_normalizer-3.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c", size = 252825, upload-time = "2026-09-30T04:37:11.274Z" },
+ { url = "https://files.pythonhosted.org/packages/26/9f/69e13ca3b18f43e0eafcd34c04a45b732ae22a43b54a5fc9e119103356eb/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f", size = 252659, upload-time = "2026-09-30T04:37:12.941Z" },
+ { url = "https://files.pythonhosted.org/packages/73/a9/ace29806a0dae18939919c76ba526472d83214afa101105fabff2cf30625/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03", size = 238819, upload-time = "2026-09-30T04:37:14.659Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/c1/6116d52a2e3311ec80f21f5fb5e17b27405f10b9608af8f6e69516841a1b/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604", size = 272973, upload-time = "2026-09-30T04:37:16.346Z" },
+ { url = "https://files.pythonhosted.org/packages/19/aa/9955c7e93bba10a9c7e8f7a5031b7ced66f3a1883a55c00712b8d5850ff3/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8", size = 253692, upload-time = "2026-09-30T04:37:18.212Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/33/2a6ae7fdc1b10cb581cef91addd8cdfc5f40d50abb5702309369d5834579/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93", size = 268757, upload-time = "2026-09-30T04:37:19.877Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/22/80992720a0282cd39bba1db35868e6b9c22f41281160143a836544bc1d8a/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915", size = 262220, upload-time = "2026-09-30T04:37:21.583Z" },
+ { url = "https://files.pythonhosted.org/packages/92/9f/181fd07e1bffea1d95cd80c84ac537354f50699c22cfc4d3c02b6fc16208/charset_normalizer-3.5.2-cp314-cp314t-win32.whl", hash = "sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5", size = 197797, upload-time = "2026-09-30T04:37:23.235Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1c/25d8415ec1c4f2f41f1680435e4c87cfb378ff2f677d950946f2a45d0632/charset_normalizer-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc", size = 222765, upload-time = "2026-09-30T04:37:24.891Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/b4/46b48f013dadfc0d0d33b375438e31bdf5a989dc68389c6bf627054d4df9/charset_normalizer-3.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105", size = 210985, upload-time = "2026-09-30T04:37:26.634Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/e9/34e597dee616d0b8ee4b34d29399e85c2204ade174157a48505d42baa4ff/charset_normalizer-3.5.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26", size = 367676, upload-time = "2026-09-30T04:37:28.329Z" },
+ { url = "https://files.pythonhosted.org/packages/60/9f/a5d1c91c0263745e2cd344c5a4415d787c575501ab1d449f1148ac6b495d/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364", size = 248414, upload-time = "2026-09-30T04:37:30.167Z" },
+ { url = "https://files.pythonhosted.org/packages/26/79/e697f77464748a3ee3cf490c83d592459400d4898380d66c38366b03080c/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253", size = 244775, upload-time = "2026-09-30T04:37:31.964Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/87/3d42a42e18ea066e2513936fd678a00696e77878b5ae04528976abdbcb83/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0", size = 273877, upload-time = "2026-09-30T04:37:33.661Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/76/8a28136f3938ba9836f84280ce0c4d61ed1cf15a036b2034900c62634162/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc", size = 269455, upload-time = "2026-09-30T04:37:35.573Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/a1/4fbf5d0f0f1b2a080474c1cf9a2f12c4c6531bb0e8ba591055e846d2b4e9/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229", size = 257336, upload-time = "2026-09-30T04:37:37.397Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/a2/8b50aa320adb880ad579518e6f718f24944804b42a88b83d267d5d444125/charset_normalizer-3.5.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5", size = 257584, upload-time = "2026-09-30T04:37:39.522Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/57/50e3fed84e175f40349bd0da7a4fce94c87f0378f52d74f511d89e0bdc20/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98", size = 251854, upload-time = "2026-09-30T04:37:41.23Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/54/f7fbb3493c9f49091213b9c2d6dd65800696f1ce1a3f196a4205f50417b1/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3", size = 248767, upload-time = "2026-09-30T04:37:42.883Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/37/b3a6385acc5a1e45b39ae9c90bfb9cf838a09b9dd37ef2740ab4c6b4a2eb/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2", size = 274479, upload-time = "2026-09-30T04:37:44.658Z" },
+ { url = "https://files.pythonhosted.org/packages/89/44/809913e2cfd279e635a9294fdbbfb1b1dc62a8189d473d561f649fce98d8/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf", size = 258310, upload-time = "2026-09-30T04:37:46.529Z" },
+ { url = "https://files.pythonhosted.org/packages/af/a2/f28400ab13359d91bd39179df8e149376b9bf36588e739a3a4f9de2b84b2/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95", size = 271100, upload-time = "2026-09-30T04:37:48.399Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/89/9bab37955edf0adb3b66f8a3a6617d9f2f487e0d56f295a6a286cb640aa6/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d", size = 259936, upload-time = "2026-09-30T04:37:50.023Z" },
+ { url = "https://files.pythonhosted.org/packages/23/b5/4459e08d45a679f903d50fea08bc52cfa728cca4d7bd02c757b5e5abda2e/charset_normalizer-3.5.2-cp315-cp315-win32.whl", hash = "sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847", size = 186301, upload-time = "2026-09-30T04:37:51.722Z" },
+ { url = "https://files.pythonhosted.org/packages/98/e8/55d5fd3935b4bce6da4fe0df61898e8c82653e317e677bd58aceb9c60f13/charset_normalizer-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8", size = 210858, upload-time = "2026-09-30T04:37:53.427Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/5b/974423c2fd8e524c7a7f64318c1e02240ef954912fa2b4d70344107b9c68/charset_normalizer-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a", size = 200309, upload-time = "2026-09-30T04:37:55.015Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/f9/00ee0195db1013d8f7c416fd770fbeb560bb46eb2e36b054d05cb56f6cfa/charset_normalizer-3.5.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1", size = 395343, upload-time = "2026-09-30T04:37:56.743Z" },
+ { url = "https://files.pythonhosted.org/packages/04/3a/c00b50e94c964cf934c7899cd47c97952fc11dad71cc5884b3c61795b09b/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b", size = 250114, upload-time = "2026-09-30T04:37:58.607Z" },
+ { url = "https://files.pythonhosted.org/packages/50/27/d102dc880bbcffd0479ab64dfc1fb96777a854355a55e2bda72a71efadcb/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f", size = 239997, upload-time = "2026-09-30T04:38:00.511Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/4a/bf7ef45794dd293fab5f98a9309817977fbb845b9998f171b8cc5d8437a3/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3", size = 272313, upload-time = "2026-09-30T04:38:02.509Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/ee/008a2837737991474c5754bb3191010007663860979701990982a502cbaf/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e", size = 269986, upload-time = "2026-09-30T04:38:04.435Z" },
+ { url = "https://files.pythonhosted.org/packages/93/ad/bd74a283940dc910c5b14f8e4f80a248082bc9c0fcbe1f54530cb6d9cc5e/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9", size = 260202, upload-time = "2026-09-30T04:38:06.549Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/7b/ed341c66f69f688723501fac752be3d63c7159ca0d0d4174fc611e5710bb/charset_normalizer-3.5.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a", size = 259083, upload-time = "2026-09-30T04:38:08.311Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/9d/e41588b777965e5031a43128a1e96173ebb35ac75fc53ec3b517e7c21cd4/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115", size = 253081, upload-time = "2026-09-30T04:38:10.402Z" },
+ { url = "https://files.pythonhosted.org/packages/81/35/b761eb6d8c1eb218b9b42b9b4d5ac902afdc399fb6dac6f9a9aac7bda589/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c", size = 248712, upload-time = "2026-09-30T04:38:12.317Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/2c/147169a041b747759f37405c0a97157e8e92de967968373101ff14915cba/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d", size = 274176, upload-time = "2026-09-30T04:38:14.138Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/2d/0ff8db0d373ba8538db686db11cd7e8912031490b9e4f383b41912e8d594/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d", size = 259413, upload-time = "2026-09-30T04:38:15.841Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/8e/b4a085fb47c9d3a7e43576a4784fdd8fe23f907514a972de8086edaf7a48/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4", size = 269434, upload-time = "2026-09-30T04:38:17.626Z" },
+ { url = "https://files.pythonhosted.org/packages/83/1c/d8d8d7322a7c3eecdf3237a4a419cf41d2eaad8e006ce7dfdd9d4c8fa2eb/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b", size = 262716, upload-time = "2026-09-30T04:38:19.214Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/16/0e4c6ba9b44e97a2da150e52d331e8f9c968b21b358fbffa6c856cebcd89/charset_normalizer-3.5.2-cp315-cp315t-win32.whl", hash = "sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800", size = 197159, upload-time = "2026-09-30T04:38:21.037Z" },
+ { url = "https://files.pythonhosted.org/packages/be/33/e90bc2b1374f7f36ef106f56620de5a783907e19ca857efe2277e31cac3e/charset_normalizer-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21", size = 221322, upload-time = "2026-09-30T04:38:22.886Z" },
+ { url = "https://files.pythonhosted.org/packages/66/89/dfa6dcb08c200b7830ab56439e8c1890f2971d51aafbb3937894a2e7fcfc/charset_normalizer-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58", size = 209192, upload-time = "2026-09-30T04:38:24.648Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/ab/176fbfd5b64939c55d652366aa5b9ef1d767af207a3aa6ebeb0d226c484d/charset_normalizer-3.5.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd", size = 331815, upload-time = "2026-09-30T04:38:26.216Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/84/371eac6b30bdbcbf2d632a1a01809103459216fcaae61b8b8d922c1bfb8a/charset_normalizer-3.5.2-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7", size = 253276, upload-time = "2026-09-30T04:38:28.032Z" },
+ { url = "https://files.pythonhosted.org/packages/43/6f/c4fbae58febff71709c51bc7e18fdfa55341dc382704740f9f0cbf03817b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f", size = 241239, upload-time = "2026-09-30T04:38:29.732Z" },
+ { url = "https://files.pythonhosted.org/packages/61/71/458c3f42164a07d0c5210798e9e704b39e540a6793b05aba67f3a35243a9/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93", size = 231121, upload-time = "2026-09-30T04:38:31.462Z" },
+ { url = "https://files.pythonhosted.org/packages/09/54/ab9e89367076f6331bb6c65c4bf14a5361fa5191cb6561bf534f18504e1b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade", size = 260350, upload-time = "2026-09-30T04:38:33.239Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/c1/061431ecc688d9d76602502cb57cc01e691e682c18f1beb45f9673b5bbd2/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0", size = 255430, upload-time = "2026-09-30T04:38:34.865Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/1f/20c8949f0676f7ab811abdeb7f4d7f1cbc6e61ff20bef08b44edeb092bc8/charset_normalizer-3.5.2-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26", size = 250612, upload-time = "2026-09-30T04:38:36.649Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/9e/46f2fa4c431fc98c4ae76a8cb5bdca54e0341e3cfc3fcfd8e82740250818/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011", size = 242083, upload-time = "2026-09-30T04:38:38.26Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/39/559be29a0c0f086e0bba6922babd38916cc5e0b58ced4de13ee01ea05508/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621", size = 232738, upload-time = "2026-09-30T04:38:39.81Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/6c/387b0e4f756a282831c1d9fc6aeb6c51ca4507ca202767c8de15ce9b12e2/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4", size = 260703, upload-time = "2026-09-30T04:38:41.346Z" },
+ { url = "https://files.pythonhosted.org/packages/96/92/1fdf015f09ef449f50d3ac4b67c90887c9c318b727daa95cc4f866e6521d/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e", size = 247622, upload-time = "2026-09-30T04:38:42.937Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/3c/8e7b8a5671ad5d433669fb2a76f1a0164df2d9b1718b0206bc2a16d840cc/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c", size = 257500, upload-time = "2026-09-30T04:38:44.604Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/f0/45b579df5cabc1d5d53ea1cc35e8437d3ca768c0acccc7041517cb6fbb32/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0", size = 255100, upload-time = "2026-09-30T04:38:46.289Z" },
+ { url = "https://files.pythonhosted.org/packages/31/68/fdec18a343f5fb3f310588dd478b09ac4799e0b187dbade3a8cd776f03ef/charset_normalizer-3.5.2-cp37-abi3-win32.whl", hash = "sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf", size = 174499, upload-time = "2026-09-30T04:38:47.999Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8a/b618149cc5207943a0242068d7a27897f56a62947b5a039085f2a22029f8/charset_normalizer-3.5.2-cp37-abi3-win_amd64.whl", hash = "sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036", size = 200092, upload-time = "2026-09-30T04:38:49.707Z" },
+ { url = "https://files.pythonhosted.org/packages/03/cf/4c66866fa9e2b1c78e3c911516d1de497a677b7ac60f1eceda74ce777ca3/charset_normalizer-3.5.2-cp37-abi3-win_arm64.whl", hash = "sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e", size = 294363, upload-time = "2026-09-30T04:38:51.312Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/ad/d07d7862a62ffa6d79d68074d14823243dd235a77c45262acbf6adeb28bf/charset_normalizer-3.5.2-py3-none-any.whl", hash = "sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685", size = 68872, upload-time = "2026-09-30T04:39:21.828Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
+]
+
+[[package]]
+name = "cryptography"
+version = "50.0.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/56/d194340cc4a57535e82e1bee9e89667ac4b7c13b5d3f59686deae3094dd5/cryptography-50.0.2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fa8f5efb344d6908a1ce62f4a24e2e5780f825d6f53f5f50ec5ffacac72936cb", size = 3914904, upload-time = "2026-09-30T14:43:44.339Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" },
+ { url = "https://files.pythonhosted.org/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" },
+ { url = "https://files.pythonhosted.org/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/2e/7212ca32fd43dc91f2f41db20160b268098874b4c9a0e7be94d6835f5b2e/cryptography-50.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a582ab2ae1d34f67112cadc86702774c9ea4374df6bca6afe672817203c99134", size = 5367029, upload-time = "2026-09-30T14:43:52.911Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" },
+ { url = "https://files.pythonhosted.org/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/30/e86d7d518489b0ae2497091a35287abcb1a2ce4037837a34afbe9b1d6964/cryptography-50.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f265528741e048bce55c3463ed721fb0aa45a5888d8add8cfeccb3035451bbdc", size = 5324103, upload-time = "2026-09-30T14:44:01.901Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" },
+ { url = "https://files.pythonhosted.org/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/4f/adfc442765721292fff86d314ce385d3249d22db42295c0dd057727b60f3/cryptography-50.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:7afa5a6602a9f29af1f3a2965f831bae7c9d5d597b7cbb716d41ab3b7d89879c", size = 3824692, upload-time = "2026-09-30T14:44:11.671Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/cb/52eb3770c0d0be2702a98c6e96065ddc0a2877cf0845aa9c23397c142cd4/cryptography-50.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f785f6161f202ab04d8ca194158968798e480ca058943907972da5f12e2881e8", size = 3892731, upload-time = "2026-09-30T14:44:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/37/cce70f150c432914460157a6ecc161752e053aa5ec0ef3b3f7dc6e31039a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:94e5e9f108ee10471288214d3d233fbfbb492840a8457eb85178d643ddeb32c7", size = 5328676, upload-time = "2026-09-30T14:44:21.744Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" },
+ { url = "https://files.pythonhosted.org/packages/30/96/01546c7f69ea0e2ab790a2e4f0934a4052fb9b388147fbf83c2fd72f1e57/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:84f964e537f916e2cc85199e5a88742e964939b575ac8598b3f9d6cc416cdaf1", size = 5285085, upload-time = "2026-09-30T14:44:30.704Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" },
+ { url = "https://files.pythonhosted.org/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/cc/1d33befb3cd7ea7e77d2d73f43f2066471da1b21f24a6156efcaabf6d2e8/cryptography-50.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:78198641e5be9521beea5aa782bb551a58068d10e6eb04c9c680c1b69f2e7d45", size = 3795868, upload-time = "2026-09-30T14:44:39.71Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/49/93f6a6e7a87c9aa68d44d3e1cdb5fe8f60c90d5d2f46acae9a56892816b8/cryptography-50.0.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:edc3342adf8f697fc5f59c887a304356f147b397809440ed64e2fa6af2f50f37", size = 4133708, upload-time = "2026-09-30T14:44:41.807Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" },
+ { url = "https://files.pythonhosted.org/packages/92/20/9ded6d51ddd9897f6b6e81fb9ebea7951d7cc5d6c890b0ed8abf77a51a80/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_ppc64le.whl", hash = "sha256:b13478603dcd0a2479ff8e87e2c19a7d525734686fe3c49542472293a204212d", size = 5548822, upload-time = "2026-09-30T14:44:50.86Z" },
+ { url = "https://files.pythonhosted.org/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/20/196f112617fb08eb4d608a2a6c422373d46f9cc2857f38fc0667033c0899/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_ppc64le.whl", hash = "sha256:cbc8738fd8526d80f35cb3a40d41f41a2e7030bb3b18b09a6778ef63d291c2fd", size = 5506351, upload-time = "2026-09-30T14:45:02.267Z" },
+ { url = "https://files.pythonhosted.org/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" },
+ { url = "https://files.pythonhosted.org/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" },
+ { url = "https://files.pythonhosted.org/packages/39/d1/55f8a3f2ef5d1529e16835ef10cf0fe3d559ce237b46dddc440c0bba3649/cryptography-50.0.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:c423ab384a46c4dff7217b2ea5ba2e11cffdeab6441acd04cf65a369caf0366c", size = 4111557, upload-time = "2026-09-30T15:29:20.155Z" },
+ { url = "https://files.pythonhosted.org/packages/23/ad/ac987755d00e1e64273760228d2635ae38dae2be83e3c6e0d3289d91dec3/cryptography-50.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0ec5f09541743261e66e291b4a0cbf0fb2997aeaab6d9e9c740b9dba1b58d1c2", size = 3943588, upload-time = "2026-09-30T15:29:22.265Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/35/b345bdfa40c9126df1a9d33236aa98418367931b8725f84fc3ae2b98dc59/cryptography-50.0.2-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:58ddb5a8e3179d12f19e4ea34d2d32e9d63a4baa142c875c1eb59f41b7243acd", size = 5382217, upload-time = "2026-09-30T15:29:30.589Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" },
+ { url = "https://files.pythonhosted.org/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/3f/86ff33ce34cc0de6847fb96e035a1a760d81652e38643f617c02ad32ef7a/cryptography-50.0.2-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a6557e5f38e065ca9fbdaf7cfc7435ecb1d113aa81a022d1b51921ee7432e227", size = 5338560, upload-time = "2026-09-30T15:29:39.053Z" },
+ { url = "https://files.pythonhosted.org/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" },
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
+]
+
+[[package]]
+name = "dnspython"
+version = "2.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ef/4a/50822184bd67cc6493f0fb6a880749158fcd31ab3fa07409acfd91f9fc85/dnspython-2.9.0.tar.gz", hash = "sha256:b44dc6b18f07a8b1c56676a19fbfdb5209415b046a9cece286baafa87ff3f7f1", size = 423560, upload-time = "2026-10-09T00:07:24.352Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/10/02/cdcc9b7c051786a103c3b09e1003a82fa0c66bcb91ffbdabcfbf7b4163b9/dnspython-2.9.0-py3-none-any.whl", hash = "sha256:9a4aedb833c3c1b49214d04d44d3032ab7a9135f7c1d29a549b4ff78fd82fda9", size = 354822, upload-time = "2026-10-09T00:07:22.622Z" },
+]
+
+[[package]]
+name = "flask"
+version = "3.1.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "blinker" },
+ { name = "click" },
+ { name = "itsdangerous" },
+ { name = "jinja2" },
+ { name = "markupsafe" },
+ { name = "werkzeug" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" },
+]
+
+[[package]]
+name = "flask-cors"
+version = "6.0.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "flask" },
+ { name = "werkzeug" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/47/03/4e464a50860f9adf08b5c1d3479cb8ea1f12af2aa69535c7042c6e628135/flask_cors-6.0.5.tar.gz", hash = "sha256:30c5031552cd59f620ac0c8211dac45b345d3b2df310e7721879e4f46ef9c601", size = 101386, upload-time = "2026-06-08T20:20:17.765Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/55/5bb1a2d918e9f02f131e47a59032bae70e48050e986e941511fd737a935c/flask_cors-6.0.5-py3-none-any.whl", hash = "sha256:68fcf75693e961f3af26683b23c4b9a8fb6b64de17d20d0c37b95e8de7ab2ed8", size = 16692, upload-time = "2026-06-08T20:20:16.247Z" },
+]
+
+[[package]]
+name = "flask-pymongo"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "flask" },
+ { name = "pymongo" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/89/22/0b3dd3989ab1636e2fb97f4760ffd858ef77eaa0e84779e2c997f6da9c04/flask_pymongo-3.0.1.tar.gz", hash = "sha256:d225b51c21ceca2e670e6cca79b5c584ad17b96252b48e84e3b423ddb73304cc", size = 7280, upload-time = "2025-01-29T23:45:43.991Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6a/0b/5142f1b76332c0121c85a9fb5a10dce40bb10d586eea4fda3711abb6c2a4/flask_pymongo-3.0.1-py3-none-any.whl", hash = "sha256:c75a4a9d7f4dc41ddefcfe75d2a0a259049071d285242c1338c42c64dd1223d9", size = 11061, upload-time = "2025-01-29T23:45:41.841Z" },
+]
+
+[[package]]
+name = "google-auth"
+version = "2.62.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cryptography" },
+ { name = "pyasn1-modules" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f5/b9/4b2528f30114b106e3c3a7298ba38d85e682fd1f83fe7c4a5b77c7677082/google_auth-2.62.0.tar.gz", hash = "sha256:0bef0ce54bdf9ce226c5d66e4264413bd918141c31bbe49fb52eac882f513d69", size = 398180, upload-time = "2026-10-12T19:20:48.328Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/eb/a3/87fe9b7203ec2e56ea6a57c75e6d632eaf9487c7f7978cdad1d801bee0af/google_auth-2.62.0-py3-none-any.whl", hash = "sha256:4ff4319aeb4ad128409759d397a9fcafad126d0031d241cc0dd6b9a00b43e3f3", size = 267911, upload-time = "2026-10-12T19:20:46.355Z" },
+]
+
+[package.optional-dependencies]
+requests = [
+ { name = "requests" },
+]
+
+[[package]]
+name = "google-genai"
+version = "2.31.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "google-auth", extra = ["requests"] },
+ { name = "httpx" },
+ { name = "pydantic" },
+ { name = "requests" },
+ { name = "sniffio" },
+ { name = "tenacity" },
+ { name = "typing-extensions" },
+ { name = "websockets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/34/69/6a3d3839ab56ec17d7923c8679b38955e286e0a9793df10ce6d13bc5f122/google_genai-2.31.0.tar.gz", hash = "sha256:71518537de2ba967a61960cba844691dca3dd4ff07726c01a6917d132213541e", size = 745679, upload-time = "2026-10-14T23:52:24.836Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1c/64/1e9ab799830101ccbdcb67376a3f12b59e51f57643862999fd31af6938a1/google_genai-2.31.0-py3-none-any.whl", hash = "sha256:c87b9d495a24b09dedb29b852a201bea69ecd0b3f6c2561dc68f4055451b1b90", size = 1208245, upload-time = "2026-10-14T23:52:22.766Z" },
+]
+
+[[package]]
+name = "gunicorn"
+version = "26.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d9/8a/e4ef6ee11701b6cd64702848415ffb69eeff85cb388a3c6c7fe86f22f3f8/gunicorn-26.2.0.tar.gz", hash = "sha256:62b864895d9ebff0b2f9867ba04fe811c93121596540830c9c916d0769668447", size = 787921, upload-time = "2026-08-24T15:05:59.3Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fe/85/7522a52e5e2f42faf1a129113ab63e548c42e103e9af395b7bfe65e403e2/gunicorn-26.2.0-py3-none-any.whl", hash = "sha256:bd249d0b3f7972f7432f0a6b6ff3b3ee2d129f70cd1ff6c09a9dd9e29a2b88e3", size = 228389, upload-time = "2026-08-24T15:05:57.67Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpcore2"
+version = "2.13.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "h11" },
+ { name = "truststore" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "httpx2"
+version = "2.13.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio", marker = "sys_platform != 'emscripten'" },
+ { name = "httpcore2", marker = "sys_platform != 'emscripten'" },
+ { name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" },
+ { name = "idna" },
+ { name = "truststore", marker = "sys_platform != 'emscripten'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" },
+]
+
+[[package]]
+name = "httpx2-jsfetch"
+version = "1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.20"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
+]
+
+[[package]]
+name = "itsdangerous"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "jiter"
+version = "0.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9c/1f/8176d92e001f86505424b41664032ae26a882bc9ca41a32c803f373f9195/jiter-0.17.0.tar.gz", hash = "sha256:03e432f226a453851079fb84cd17c6da9991eab723e28d716f14ae3d906e0c12", size = 229037, upload-time = "2026-09-12T15:14:14.253Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/01/9e/23065f8e2c7a4c372c1b6f6622e4cfab4dc786cb5150052b1527e6a6a840/jiter-0.17.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:00d783a779c5664e16dbad5e3a3c3a75e128b07dd5f4765159658d9210a50ca5", size = 292210, upload-time = "2026-09-12T15:12:35.613Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/81/67b58647560bc82a4490d722caa8561d7a86a9f45d4fa620b7e5fe282c7a/jiter-0.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0619d806e260ecf0c2a64521942c94af5d547c9ec99b55ae4f51b538b5576a76", size = 321512, upload-time = "2026-09-12T15:12:36.907Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/07/6658359a25f55927f7f8bf0e16465dee2ccd0b2a1a5208acc0df8972e074/jiter-0.17.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc0288ce39190ee33fe6e4ec73161eed34e7e2da509b525546ca061778d62b64", size = 343897, upload-time = "2026-09-12T15:12:38.189Z" },
+ { url = "https://files.pythonhosted.org/packages/46/04/5d50a9f0319cbdc37fd53c27f8c313d46afc34f1b048219ae6d8ea068da4/jiter-0.17.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a52a430d04225ffde633e6840bf2381d34c019ff98526b5929755b9052fb199", size = 326519, upload-time = "2026-09-12T15:12:39.532Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/c7/d02517832b29eb8275fdd0f4ce0f17b80f58cc4c3ebecd4d9ace990d633d/jiter-0.17.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37f33d327900bf2879613b3363fd48df97b4232d0c41f54bcf2e790c2fc40a71", size = 341369, upload-time = "2026-09-12T15:12:41.486Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/07/499b5f5603501cdd93a73a6a176dfad9c96555a3ae58ca9f8e3acba63dc9/jiter-0.17.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cf564d43c4388149ca58ee571d0f5ccf875e20d1fd4662fd94cc0d1ea3b10ef", size = 352160, upload-time = "2026-09-12T15:12:42.721Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/75/b04013c7743269d4533ef4e746fc0ed678a143968dd7448658e3f51daad2/jiter-0.17.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:523c499235fb65add25d4bb01b1c4709ce695efdc7deb6c0a7bc515b5c44e0fb", size = 345018, upload-time = "2026-09-12T15:12:44.192Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/96/cbb6fd1e42a77c8412ec4643db95059b30cdfc635e387cc9193e098ce268/jiter-0.17.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:455e4ab35cb2a4a91a8404e08fd3c621bae433922e59bf1c494fe20a426b013b", size = 329244, upload-time = "2026-09-12T15:12:45.491Z" },
+ { url = "https://files.pythonhosted.org/packages/15/67/d3be402f398566a379bf40ae65be5c3505b14d9e95e0802a597ddde7ddee/jiter-0.17.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6871973bfbd4408f7f1c632b30bbb5bbd9671c1bc8650af6823e24b7be13709b", size = 335693, upload-time = "2026-09-12T15:12:46.935Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/8d/98e2c4130b93d64f1d67c89060b928d04102549bf05e64451c9e6024f9ca/jiter-0.17.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:77f6aac0137309b31448c1bdcda4c6c77077664a6d018ece8d94019c68a5a5b9", size = 484329, upload-time = "2026-09-12T15:12:48.361Z" },
+ { url = "https://files.pythonhosted.org/packages/78/5e/8da91e49f0fbca37c3489fb4cf3ad6676d4965f00ae5468bca3a2513737a/jiter-0.17.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:93946d89fa04d5ba64dd323a8dd8d901676cb8a3c81d99ae4f6c051a9b4c3f2f", size = 521358, upload-time = "2026-09-12T15:12:49.856Z" },
+ { url = "https://files.pythonhosted.org/packages/be/21/5388684a5a38af3557cd9c2424b9827c71809cff24373c75ef9d0d3dfba9/jiter-0.17.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:70f19a2ca8429f91e82eeffb2f51cb87bc2d6e953b009b91a92d29c3a16ccb03", size = 110459, upload-time = "2026-09-12T15:12:51.747Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/ad/58b3a93525d2ffca7f54d9dee441381990082bd1172fbeb8d6a3f72a4dc3/jiter-0.17.0-cp314-cp314-win32.whl", hash = "sha256:71dbd74314c5df52a1bccf7b8bca46d14e943af7a2012e73b23f49977ef194c8", size = 185043, upload-time = "2026-09-12T15:12:54.477Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/4a/1aa520eb6c359b262c14ff995ca7283837208ddfb1202082ce9d73cf214d/jiter-0.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:ac3c6ee3264d6f5c44c617f90bc7e8b9e1587e7d6708c9d8f811cb65582ee312", size = 227163, upload-time = "2026-09-12T15:12:55.931Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/e4/5997f648794bd9b499491d0ff480b096cc9a9c65bdba29f57568e6aa1705/jiter-0.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:6219adaf59711ba7063a52496e8ec6d3fa3e209d7827d83eee3b2abc780a1744", size = 183505, upload-time = "2026-09-12T15:12:58.196Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/4a/84a5ec271d09f7590b6073af5ee4abb44eab4ccace453b7e2c5ce45234ca/jiter-0.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:59bddbe6f9ffecc68d641e1e2d619ce64cf8a9e9eeb74e5c518f74fc87abf1b0", size = 321527, upload-time = "2026-09-12T15:12:59.394Z" },
+ { url = "https://files.pythonhosted.org/packages/39/71/9e1fd0045f5920b4c36be35c3f0f0dfd123668684f8ad352619d7aa44183/jiter-0.17.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cb41cd1432f1dc19a231cf70b54d42b2c9f05085155859263fce06fa4d41388", size = 340865, upload-time = "2026-09-12T15:13:00.756Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/2b/14627fd2bc377f3dd09491bcace6b90e34b4d7fea2f1f3295031ff91f528/jiter-0.17.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd7790aa79c8b518e512ebcdfce9f11d8ef5f30efd43720c8a19a548b39fa489", size = 325412, upload-time = "2026-09-12T15:13:02.152Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/f3/8d5808f7bf0f456bde79e6393587183a0cee5f83d179fe1f7f1eff2ba067/jiter-0.17.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbbfe4e3c21c8166980cddc5bee1a315df082454f007947dfb6fb73800768165", size = 340473, upload-time = "2026-09-12T15:13:03.485Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/da/1d8c7c6c4ae6b2423b94a81b6b907d37b28f87664e077427b531bf1b5313/jiter-0.17.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c286860abfe8b100cac1c02e225e5776eb9216edd71ba17cdb237da4af32bc9", size = 350757, upload-time = "2026-09-12T15:13:04.828Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/96/c1813dcca15c5a370145a448aaea7d1f83f6f0228a5f1130e79340ee385f/jiter-0.17.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f753eb70b1474a29e635e7542ff7312e6d6b951e0b25e8a2e8c34eeb1ddcd478", size = 345203, upload-time = "2026-09-12T15:13:06.131Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/f7/fc61cbcf2992d169ede13648fc3fd8e2d3171a3669dde43cd4db556549ac/jiter-0.17.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eae86b1f027031e39db2e0e9c4842221edb7b8cd474d23f87a79b3bd4b651768", size = 328322, upload-time = "2026-09-12T15:13:07.392Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/88/46418a3abbdffb7dc41b314200360f24f75faaeb35573e81c92de322cce9/jiter-0.17.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bf350452a43173e69e1fc74847c57a60e3d7515807287f29849baa2a85d8718", size = 336570, upload-time = "2026-09-12T15:13:08.666Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/28/b8a55b949be6306df8888e365a8df05441de8a7b11289f6957004302e41e/jiter-0.17.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:da139721f4b7cafdbff580a4f511ea24cb91f4909330c6b926a1ca53836c0a59", size = 482879, upload-time = "2026-09-12T15:13:10.037Z" },
+ { url = "https://files.pythonhosted.org/packages/75/3b/21d0afa53ba0680962c39f3eb95ed2946f8793369ed44b0c82b490723081/jiter-0.17.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8079849db9a1371bfd90bad088458a8fb836261879df2233cc9632464ecf64e1", size = 520406, upload-time = "2026-09-12T15:13:11.456Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/03/bcbaf8b6b9ea23c2c074411f8ecfbb02d820abac5d0cb8f4e280209174a2/jiter-0.17.0-cp314-cp314t-win32.whl", hash = "sha256:8f770b0c77e5fac482e1ba03ca1a7e18286bfb213d749932a00a7e4cd5de5e06", size = 184434, upload-time = "2026-09-12T15:13:13.037Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b5/5d6ce2c93ef6fe1241b37a9005547f9b6d58db1f07f39fe95807d4b98f51/jiter-0.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c4289293e5278d9314b00f15c37f2120fa51d3d68565292e715524c750e775a9", size = 227392, upload-time = "2026-09-12T15:13:14.933Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/4b/1e52baf90187606e33a7b8cfa8f96f5829acd7f01870077eb01059ab76d0/jiter-0.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4dfbfe5a6e1e80a7082af559f66386405025ec278833e0c649f69cbc6e1004cc", size = 182776, upload-time = "2026-09-12T15:13:16.239Z" },
+ { url = "https://files.pythonhosted.org/packages/05/fc/efe3ac75564ab10f53517958f5ccdc231fc7334af66c76776cb554a88967/jiter-0.17.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:84963d3f395ef5e9a32ce47155e08a7962fa292c159a10cb98b931cef1416925", size = 292143, upload-time = "2026-09-12T15:13:17.502Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/4c/46982118d91f9ffe9714319d21ec4f98d9b7e0cfd9062826c524a54de24e/jiter-0.17.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ffa0380ad091de7d3fc33e17a97ff479851ee18a0a2a3ee56ff3215cdc886656", size = 321341, upload-time = "2026-09-12T15:13:19.133Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/12/9b1ac6ecc6307049913db54839ddba1c11c1ef72c5a8bbb5514bc3b50d1b/jiter-0.17.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755079792868ce5d4938e83b91a0939b34fb858a1ca65a104f2d771bea57faa1", size = 344383, upload-time = "2026-09-12T15:13:20.508Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/b6/527cc72af836d824e9d4d666e64f0a1ca7eafd662a8da9657b78592172ba/jiter-0.17.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3bf4dc2b84a464117fb097d15a25c58d100d2692888e3b0d92df5b48ed16b7c0", size = 326841, upload-time = "2026-09-12T15:13:21.83Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/41/567f98617e88005b249503b933803f633ec6ba2d427cf4cc35e5c832125c/jiter-0.17.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02a360707033d8cef53f7f3480817a1489177a259ec6ec01e98c37e0b922ddca", size = 341354, upload-time = "2026-09-12T15:13:23.323Z" },
+ { url = "https://files.pythonhosted.org/packages/40/da/b29cda895b785f7d426e224638a885b6145a08ce853b381f34afe3e88c5d/jiter-0.17.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:300ce01ab0215e3dea4d00090143c909aedc65c0f809b3c07983e1d038f291b9", size = 351985, upload-time = "2026-09-12T15:13:26.526Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/5c/8a73829e7389e72ea298a450f2b3cb58e71a3e464b45f6d8753740f1c4f5/jiter-0.17.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746243a080b4ca790b8499af3d7cf9825d5f5987933950cd818e767ee353d826", size = 346052, upload-time = "2026-09-12T15:13:27.887Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/2f/98d6001026932c095ba440925570123043bed29f5ff56158dfe729a9e81b/jiter-0.17.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:b550585523339b71cb852b811aae49d08d7601ad8ffe9f5dc1562f4c3d22fd87", size = 329159, upload-time = "2026-09-12T15:13:31.569Z" },
+ { url = "https://files.pythonhosted.org/packages/94/2e/708dc1d2678f092c31c12754e860cd8353e6a85ecbdb1010157edca0da9e/jiter-0.17.0-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0239520085cac678e77a606fd7e3f1c60c371d719790c5e3807388d3da4354c2", size = 336001, upload-time = "2026-09-12T15:13:32.846Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f0/75a5ae38862f4eaf0fe2f8a9fbf6484c4890df04c06dcdffc45e36bca61a/jiter-0.17.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:eb2295da7c3769f6719b227a237aa6a5cfa6550e478bc838001b592c57e16575", size = 484281, upload-time = "2026-09-12T15:13:35.333Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/32/6636fae811c27c7f93e1b11fb5800de6a5c9e4269a27cf718e0b31218ad1/jiter-0.17.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:e088612ff90ebc9247e1a43074b72835804261c47e6a6c01cb3ddcb55360d688", size = 521300, upload-time = "2026-09-12T15:13:37.101Z" },
+ { url = "https://files.pythonhosted.org/packages/61/aa/12df7e0b0b1a2602e3d5a5a7104d7d9700f254b400f134a9b50955c4d231/jiter-0.17.0-cp315-cp315-win32.whl", hash = "sha256:0b52d52035b3907c5b1f6277857b29c1cbfc965e24e0f27330dbed83edb591ec", size = 185138, upload-time = "2026-09-12T15:13:38.901Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/ec/3dd2e495032cddde05723c1f4c743b67a23e55d2af244692a7f58f0cdae3/jiter-0.17.0-cp315-cp315-win_amd64.whl", hash = "sha256:10f5558eed511b830488003449d942bd75829ad6257dc58cb9a03e596a7777b1", size = 226950, upload-time = "2026-09-12T15:13:40.17Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/c7/ef85704e0a57e9cadb2babc05f6d7c5df4a1c75da1a6ee31e1986b0099a5/jiter-0.17.0-cp315-cp315-win_arm64.whl", hash = "sha256:fa13acf1046f95df808c64b1310705e143fab87aee73ae00cc42d640867fd2c1", size = 183618, upload-time = "2026-09-12T15:13:41.432Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/9a/a4b348349de68762b58d6713973d363ad80a1c741d0bf8def7975f0ecb26/jiter-0.17.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:af2f7501580f274b63c4b2283bc425f5df7edf06ae5b171e5f87d912ff359a20", size = 321155, upload-time = "2026-09-12T15:13:42.716Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/70/aebd6d0b5f0677de3a3d0bdc4a05fac949b97c4ede454c8809f180ac7b17/jiter-0.17.0-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10c5349312e5cb02b7a21e123a57665afa895953f05bf252a9dd4c13a572b7ab", size = 340985, upload-time = "2026-09-12T15:13:44.115Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/82/4c3b49796b5eb62f3f5046f957683f4ba0135fe1a60957c11180512460df/jiter-0.17.0-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:86f3f9343a288eb85a81ef20a752b2f84564296636db54a9fff0b5c8deaf1df2", size = 325670, upload-time = "2026-09-12T15:13:45.901Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/43/f6341ecb4872202a4ef150486fcee0e1ace4aa3da39b71b82061452cdd3a/jiter-0.17.0-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4607ec7d93355fbc25b8dc5189153cf21d66063b9f9cd04dd2774e6e783f9b6a", size = 340339, upload-time = "2026-09-12T15:13:47.442Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/c4/bc2c86e08fa065e03cb2fbc53b367c3640a7d257ef9d877b29118ea636b7/jiter-0.17.0-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10cd64a5720ad7f809ac5466ff1705813f1b6b510f195a73acafba0ac0e1f675", size = 350705, upload-time = "2026-09-12T15:13:48.848Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/67/91f12aa111cca6e3a197c3e36bf60a034bf9f122f6d41112a639e44217d8/jiter-0.17.0-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe9f61bb30174d2f5c8396445c360c96c44e78164d0815dfe627ccf57849574", size = 345011, upload-time = "2026-09-12T15:13:50.215Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/cb/9f5556e8f6ec89755fb5a709d8eb8270c9a324e31079eda0dfbeca451b6e/jiter-0.17.0-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:370d8fe5bf201dc6925e8a84c81ac7291f74d9fd1778234fc79d517064a5c76b", size = 328268, upload-time = "2026-09-12T15:13:51.809Z" },
+ { url = "https://files.pythonhosted.org/packages/22/98/153f20680fb75781a490fb849940e2b00f95035c7aa054df592f36ed33fc/jiter-0.17.0-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b303d88e6a0bda789ec4b7801c7bad68e27230ba1fe4baffc756d1fbd32dc9d", size = 337024, upload-time = "2026-09-12T15:13:53.095Z" },
+ { url = "https://files.pythonhosted.org/packages/af/59/b16c9be3a5035df4466cc72e888188c027562de90a723d290ab6814cb9d4/jiter-0.17.0-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:30793a24a31e968969757c9e08d830cbb15a2cd3c4959b4498b38f4b1c2258eb", size = 482766, upload-time = "2026-09-12T15:13:55.713Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/55/667dea313094024bef082175d6bfe8976f90d1c00c926af9df1d8e0eab48/jiter-0.17.0-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:686c93d86f2b426c803024b805bd161a6cd10e9627c23e901640eab646c0ad8a", size = 520367, upload-time = "2026-09-12T15:13:57.674Z" },
+ { url = "https://files.pythonhosted.org/packages/21/e3/4b1a43501fb9ed17b01d137e380cb0e8fdcb39a254ce31aa2ab95bc861ac/jiter-0.17.0-cp315-cp315t-win32.whl", hash = "sha256:86d703d9faa1ffc8ae4e9de0fa007712ed2171b5c0d93811a8e2e105ac729b0d", size = 184603, upload-time = "2026-09-12T15:13:59.27Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/f2/b8ee0372b6ebdf1bde5cc44495d5291d17f961065f5b48f8616cc67cac2e/jiter-0.17.0-cp315-cp315t-win_amd64.whl", hash = "sha256:42b0260445251b1bc520a63baa94a32d88e0f931fba234f1764db7feb7c72174", size = 227936, upload-time = "2026-09-12T15:14:00.472Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/b4/923a1215daba959aed8355973315cb3f81f53e0d01c5b211870a27b41f45/jiter-0.17.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d47687806f9c54c84ea38733507081337922beca90ce819c7d852dd485bc0f23", size = 182977, upload-time = "2026-09-12T15:14:01.799Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/38/9b/e422a865e1d5d57d0e509b4e0bf1c1a70a7f6382c29a5aa428df994c8bc8/markupsafe-3.0.4.tar.gz", hash = "sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6", size = 153777, upload-time = "2026-10-02T23:07:22.29Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/c3/a944f3b0df22bd129e96915b9f4e98d2eeca6516687d7618304a966c3c74/markupsafe-3.0.4-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed", size = 12787, upload-time = "2026-10-02T23:05:30.971Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/d6/a44863f69d88b6c7e27889108f70d47aed259edf89d5df3c5fca1eac87d6/markupsafe-3.0.4-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59", size = 12525, upload-time = "2026-10-02T23:05:32.263Z" },
+ { url = "https://files.pythonhosted.org/packages/17/8f/168ba80e532dd6a93f96f8f706f1ad41d7990b6e1aeedc1cc0d211a33497/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453", size = 11589, upload-time = "2026-10-02T23:05:33.251Z" },
+ { url = "https://files.pythonhosted.org/packages/32/b3/aa2c95a574d3af39403a469b295886eb9b6d448da568cbebb5a2cbfdc2e5/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b", size = 11801, upload-time = "2026-10-02T23:05:34.315Z" },
+ { url = "https://files.pythonhosted.org/packages/60/d0/34b810107d83840e768bf485de795893ebbae35b26ab061b487adfa0a692/markupsafe-3.0.4-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6", size = 11447, upload-time = "2026-10-02T23:05:35.302Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/ab/2f8488f0f817a39fca068d2b17daf446bf5cdb3eae28c3720af534d873b4/markupsafe-3.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634", size = 11722, upload-time = "2026-10-02T23:05:36.363Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/40/e2d117b048d47282ade906fbfd92814cbee5647afc13fda88a3406039372/markupsafe-3.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f", size = 12061, upload-time = "2026-10-02T23:05:37.397Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a8/73a81135e85ba66217f5af7facb03bbb386807e1a729ab64532e4c802652/markupsafe-3.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9", size = 24434, upload-time = "2026-10-02T23:05:38.407Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ca/fa9216dd01efee2dfdacafe7df32b4d0170fbac694b0c258a193d6e53999/markupsafe-3.0.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f", size = 27183, upload-time = "2026-10-02T23:05:39.581Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/4e/a469509e538d37af51103b17b073126973f2b1cbf197ff32c7ddf025cfe5/markupsafe-3.0.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c", size = 24813, upload-time = "2026-10-02T23:05:40.671Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/db/d7282caf7ab03af44d5d6fdbaa019b35c7d7f1c90588b839c07cba640d6a/markupsafe-3.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300", size = 23049, upload-time = "2026-10-02T23:05:41.864Z" },
+ { url = "https://files.pythonhosted.org/packages/30/f3/b6a425206e6964efda6acee544d0eb01d1501784d0b8e2dcc74986f33b17/markupsafe-3.0.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0", size = 22088, upload-time = "2026-10-02T23:05:43.014Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/8a/84d3582fc1f0d5bd466cdf2eebf175e172158a6e70701aacec1de1b35430/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977", size = 23846, upload-time = "2026-10-02T23:05:44.098Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/65/db101cce51b7ba4864ac491a9859d297dd1adf0e55b103fee9db9c47c527/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7", size = 22674, upload-time = "2026-10-02T23:05:45.23Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/49/ddee9813d71db0c7a5c9d97c832125e6758a0c844777f1cf076569bb0e22/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17", size = 24104, upload-time = "2026-10-02T23:05:46.398Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/0e/7d8518d726726870a2399d69fd30d0fa36c5e57a2132c336b58d7c491073/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c", size = 21637, upload-time = "2026-10-02T23:05:47.48Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/b0/b505e8a361ba557dbf3b3aa7331ea39b00d2022a26e925ff8463b9714bb3/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4", size = 23081, upload-time = "2026-10-02T23:05:48.611Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/ea/9cc3cea873f980c75cbdb6f4277ce30ee955de38be0b3d02f14c108e0698/markupsafe-3.0.4-cp314-cp314-win32.whl", hash = "sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c", size = 14272, upload-time = "2026-10-02T23:05:49.707Z" },
+ { url = "https://files.pythonhosted.org/packages/80/f0/5792ff768a410f93ee3f84fc19345295ffc352d2c936b424cb37e514714c/markupsafe-3.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe", size = 14501, upload-time = "2026-10-02T23:05:50.788Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/cf/3d074a8edffcc6899355232ff2543ae8d929733239596423b7db79698bc9/markupsafe-3.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a", size = 14364, upload-time = "2026-10-02T23:05:51.857Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/31/87ce42159aae2163cf3bbbd0c44bc87780510eecab1ea3859099aed95dcb/markupsafe-3.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2", size = 11759, upload-time = "2026-10-02T23:05:52.951Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/53/b047207eeb7752e960aca3eb1df5fb7eefa7dd4c62ac49bb156456c8a702/markupsafe-3.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977", size = 12096, upload-time = "2026-10-02T23:05:54.066Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/51/4326c88a13c7b755657d44b4bb986f8c3d9843ecba7e22d98661d87f9a57/markupsafe-3.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289", size = 25699, upload-time = "2026-10-02T23:05:55.15Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/bb/990581b7474bfcf2cf34bed6ba5ea23bd87adb9d671213d68e88620e7a6b/markupsafe-3.0.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe", size = 27804, upload-time = "2026-10-02T23:05:56.29Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/89/89491878c28e8291f5aa2fffe2c2d57230d10ae366d55dd810b840513d78/markupsafe-3.0.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a", size = 25912, upload-time = "2026-10-02T23:05:57.416Z" },
+ { url = "https://files.pythonhosted.org/packages/30/77/680998b54efdea06fc114565cd739b6d059f826a0279219b218dfa750d29/markupsafe-3.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733", size = 23623, upload-time = "2026-10-02T23:05:58.557Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/75/2709f5ac5de9467b40b10e2bb8f89cc63dfb74582e09aa734b1124a217de/markupsafe-3.0.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34", size = 23301, upload-time = "2026-10-02T23:05:59.94Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/c8/39eadc6c5b14c9c7679bfb98f4d4c6a97863b5beb91839aca4d2d6e16e55/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978", size = 24950, upload-time = "2026-10-02T23:06:01.289Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/5e/01037f8a43e8ccb0bffb4fbdc5212db05bf080fdd7286cd392332d58128a/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc", size = 23330, upload-time = "2026-10-02T23:06:02.441Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/f4/23e83ce0596bb0cbe670502d31df8f757bbd01a392aa486fa3b40d1ed399/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc", size = 25239, upload-time = "2026-10-02T23:06:03.579Z" },
+ { url = "https://files.pythonhosted.org/packages/88/5b/3708897368073cc683d524750474f41a77d2986152c380dcc55b20fdf340/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932", size = 22767, upload-time = "2026-10-02T23:06:04.699Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/61/ebda1307864b409e6b3115757a3d4a09cca46cfb6cc65191b5de226b424b/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6", size = 23668, upload-time = "2026-10-02T23:06:05.9Z" },
+ { url = "https://files.pythonhosted.org/packages/09/15/98075cceac3b5ba0dbb8e4762a847be967d2befc349a2cf2d0ac77f62c9d/markupsafe-3.0.4-cp314-cp314t-win32.whl", hash = "sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691", size = 14301, upload-time = "2026-10-02T23:06:07.109Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/a3/768b560fcc4156685cb563d922b217810cfa7bc135773367f62f1f9d2078/markupsafe-3.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464", size = 14526, upload-time = "2026-10-02T23:06:08.276Z" },
+ { url = "https://files.pythonhosted.org/packages/93/63/da554b4c97a6b0ea3229ca7fe8cbfb620be81613d517f482e85958550537/markupsafe-3.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c", size = 14421, upload-time = "2026-10-02T23:06:09.402Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/30/54d11c8ca027114898cab97421fb39e4ffd9ddf47cdbc44df2ec76722da9/markupsafe-3.0.4-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65", size = 12791, upload-time = "2026-10-02T23:06:10.485Z" },
+ { url = "https://files.pythonhosted.org/packages/10/6d/97c913e253a14bd3cd0e15a5c56d13203b823fa7ee32498342896a072dc4/markupsafe-3.0.4-cp315-cp315-android_24_x86_64.whl", hash = "sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163", size = 12527, upload-time = "2026-10-02T23:06:11.834Z" },
+ { url = "https://files.pythonhosted.org/packages/26/f9/b86d032042a4d597d9e1997f0e5f63a3eedaf11258e0a05760b0a0a826ea/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92", size = 11602, upload-time = "2026-10-02T23:06:13.122Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/dc/73c14c1eedf0ac5fa3292ba43435e6c49d2c2050f33cebde541f8f4807f1/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a", size = 11817, upload-time = "2026-10-02T23:06:14.227Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/69/2c2fcaa5fcee22d72c7819c0d536fd181c74a688e6143845419579cd2863/markupsafe-3.0.4-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429", size = 11446, upload-time = "2026-10-02T23:06:15.574Z" },
+ { url = "https://files.pythonhosted.org/packages/88/54/9e5ec76c62e6e2834d5a93623018c943e8b3bb41d663e3fd4c03303b9b85/markupsafe-3.0.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8", size = 11719, upload-time = "2026-10-02T23:06:16.701Z" },
+ { url = "https://files.pythonhosted.org/packages/96/24/3ec292b44064c16229e064d770b2625bd8ea941aa61f44905a9fa44942c0/markupsafe-3.0.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97", size = 12075, upload-time = "2026-10-02T23:06:17.855Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/85/b64fdb1f304848518742136983c24e96d967bfb59a0ea160e92736901ab0/markupsafe-3.0.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b", size = 24656, upload-time = "2026-10-02T23:06:18.963Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/18/23997d4c65b355da6390d61cd56e0ab3befd6ba8dda25cb40c602bd0fa6b/markupsafe-3.0.4-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9", size = 27737, upload-time = "2026-10-02T23:06:20.117Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/36/35998dead3c6af88c38265a56e58100211f036234ab88eb2283fd4cbce44/markupsafe-3.0.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653", size = 25003, upload-time = "2026-10-02T23:06:21.284Z" },
+ { url = "https://files.pythonhosted.org/packages/82/96/ef49135ce260db4ca4a12b119ed468449cd248db6b1468e2112b546d7a2e/markupsafe-3.0.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369", size = 23111, upload-time = "2026-10-02T23:06:22.524Z" },
+ { url = "https://files.pythonhosted.org/packages/50/7d/83126e338bd88c17a220668235368ad719fd4638e426739858cbb8508f77/markupsafe-3.0.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19", size = 22403, upload-time = "2026-10-02T23:06:23.785Z" },
+ { url = "https://files.pythonhosted.org/packages/83/dd/daf7e420de23c8206c365204e7b85e1251d8e19d34196a56336f316e5ed2/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e", size = 24083, upload-time = "2026-10-02T23:06:25.037Z" },
+ { url = "https://files.pythonhosted.org/packages/19/3c/11eecdc06bc44ad5570350085b572ebf049e8f9a38d1ece6d76640b739cd/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811", size = 22931, upload-time = "2026-10-02T23:06:26.328Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/9e/ac0fd77f2a726e56ecc3ca0235d095feace1358d1b822406c2a2ef26a4dc/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea", size = 24299, upload-time = "2026-10-02T23:06:27.742Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/09/c6bd842ad58ff5b3bc76eeed7e9a42a6f11adc5d090ec697b72c9672731e/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916", size = 21948, upload-time = "2026-10-02T23:06:29.274Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/46/82f586711fed61e86faa1ee1bc317d68cd45a10c8bdbe3f7d1fdf9026ad8/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741", size = 23150, upload-time = "2026-10-02T23:06:30.583Z" },
+ { url = "https://files.pythonhosted.org/packages/19/2d/2dfdce99318abbfa26925195fbc17db188c46a1ec6457be121b6f9cfeb42/markupsafe-3.0.4-cp315-cp315-win32.whl", hash = "sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b", size = 14269, upload-time = "2026-10-02T23:06:31.949Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/ec/6000fd82e8791e58fcd0456ec20f098957e2b03d5ed02eb73241a577c0ba/markupsafe-3.0.4-cp315-cp315-win_amd64.whl", hash = "sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214", size = 14496, upload-time = "2026-10-02T23:06:33.258Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/66/e73bd5016421d5d6e2fb6de7dd609f9de020942ac8c626526bd8c6eeaf82/markupsafe-3.0.4-cp315-cp315-win_arm64.whl", hash = "sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67", size = 14361, upload-time = "2026-10-02T23:06:34.539Z" },
+ { url = "https://files.pythonhosted.org/packages/90/df/cb8c3dc98d313a951df2f8968f44e4cb5643df6d3cab749a530ce2f7d972/markupsafe-3.0.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad", size = 11760, upload-time = "2026-10-02T23:06:35.807Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/bb/4af9b3ca0753d654ac75f9531d5bd741bb77ca6e696f36807c475ffc099a/markupsafe-3.0.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99", size = 12108, upload-time = "2026-10-02T23:06:37.089Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/d4/b56429313aee5fd59b079c3df5615299959e25e7113eb6d8caadbdd7d38a/markupsafe-3.0.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002", size = 25762, upload-time = "2026-10-02T23:06:38.419Z" },
+ { url = "https://files.pythonhosted.org/packages/65/f5/34c181e891aa4f7d59c918584672e0c5eb7fffe76c1387d1246008bf4081/markupsafe-3.0.4-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e", size = 28400, upload-time = "2026-10-02T23:06:39.819Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/b5/ad14694fd0ac9a5ce30bc6498f2999378f418583dd1679cca5a1b512957e/markupsafe-3.0.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c", size = 25946, upload-time = "2026-10-02T23:06:41.381Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/a8/26b606445387d0ceb1eb1f21840094b84e4e3c3c3983d80d10b89823b490/markupsafe-3.0.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8", size = 23634, upload-time = "2026-10-02T23:06:42.748Z" },
+ { url = "https://files.pythonhosted.org/packages/39/a2/b8814de672f1f0094d498bf646f2fec9d6356b503d28ef500b71c5095377/markupsafe-3.0.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe", size = 23488, upload-time = "2026-10-02T23:06:44.176Z" },
+ { url = "https://files.pythonhosted.org/packages/db/c7/287223376fb73335a3cc5d6eb22c6ab01358cf33945a9c39c06b9dac3f4b/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2", size = 25037, upload-time = "2026-10-02T23:06:45.646Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/29/4df8355e313426d19e62ba33e0253c009ca12a0894ee77d67fa67255361c/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38", size = 23543, upload-time = "2026-10-02T23:06:47.264Z" },
+ { url = "https://files.pythonhosted.org/packages/71/e5/8377731e8495668dcc768f645e717df18318c841edaf023a99395f6da9b4/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494", size = 25282, upload-time = "2026-10-02T23:06:48.795Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/5f/373456e37ceb1478d657d6fe769cbe0a39f0a8dfc1548eeb19c471eefdd9/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d", size = 22993, upload-time = "2026-10-02T23:06:50.31Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/93/2cbd5628435afb6f541bbaced4bce0c2edac4b09a142e6e928b8b0da9858/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894", size = 23712, upload-time = "2026-10-02T23:06:51.759Z" },
+ { url = "https://files.pythonhosted.org/packages/81/99/157e10966b033b363aeda5263e82596ee232a0b1d082fdbf90aa417ff083/markupsafe-3.0.4-cp315-cp315t-win32.whl", hash = "sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78", size = 14300, upload-time = "2026-10-02T23:06:53.241Z" },
+ { url = "https://files.pythonhosted.org/packages/33/05/55884815414c9706a23deca150b72c25a62109e65b0b6ce232077802c719/markupsafe-3.0.4-cp315-cp315t-win_amd64.whl", hash = "sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c", size = 14522, upload-time = "2026-10-02T23:06:54.729Z" },
+ { url = "https://files.pythonhosted.org/packages/92/f9/ecbde7149e95b8a0f18e16d5d747f7dc06049d5da2e4f77f6f5e4a1f46a8/markupsafe-3.0.4-cp315-cp315t-win_arm64.whl", hash = "sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba", size = 14417, upload-time = "2026-10-02T23:06:56.246Z" },
+]
+
+[[package]]
+name = "openai"
+version = "3.32.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "httpx2" },
+ { name = "jiter" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/01/f7/17bcb5033c79721d11b8815a7822575e6dedb5af65149575b56c21eb43ae/openai-3.32.0.tar.gz", hash = "sha256:6feb4a50aa653df6d2cd5e9118d5f079a51cb653d652746d344164a3e8419c89", size = 2227852, upload-time = "2026-10-16T20:18:14.688Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/2d/3acb5ea3c158ecb205d73d951d8b3df263bc7c927c25a687e06918fd8c07/openai-3.32.0-py3-none-any.whl", hash = "sha256:dac6033390174ee85a3115e312a3a4fabc71824711ded5b3a79d5b670dd6b562", size = 2256256, upload-time = "2026-10-16T20:18:12.544Z" },
+]
+
+[[package]]
+name = "pyasn1"
+version = "0.6.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" },
+]
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyasn1" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/da/a8/c5fdbeee588bb8ada9458774f43adf1bdd30bd59157055142183e769a024/pycparser-3.11.tar.gz", hash = "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc", size = 113796, upload-time = "2026-10-09T12:56:59.539Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/90/11/0e6f11117525ff0eec40ebac3d313376f102df93ca44ad9e893ee85e4f89/pycparser-3.11-py3-none-any.whl", hash = "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", size = 51178, upload-time = "2026-10-09T12:56:58.131Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.14.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7c/0b/8e10b2e693af8ec54346a14caf36221334775975a747c9ceaa3f8371d96d/pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26", size = 900362, upload-time = "2026-10-11T18:37:55.396Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/35/ea/a56b9fe5066f3537b7882f77e9c5dfb26d8c2eefdaed9b5fc73d57b4dc22/pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454", size = 488106, upload-time = "2026-10-11T18:37:53.437Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.50.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a6/24/af4ec4be49fbc810f35b0bdcacc3d433b56bbfa469fd3bfd4ae116cd9bf1/pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09", size = 513515, upload-time = "2026-10-11T18:35:44.82Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/33/28b96e81677153715e3eafb9f26663a80841e859fde282a380359b0d3fa1/pydantic_core-2.50.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1cf41f1ae3fa155cf167a72689ad044bcc1e3c97e064123677149bdfb5dafc4a", size = 2224428, upload-time = "2026-10-11T18:33:07.153Z" },
+ { url = "https://files.pythonhosted.org/packages/94/40/15c06410c9b7b8da5805d27b64e09bd3f900e986728106db2689dbc51513/pydantic_core-2.50.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4df197990c15b5a37c5a277d131d9f2c67de6133f2e5dafd80d9bba4b99f46f9", size = 2023740, upload-time = "2026-10-11T18:33:08.763Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/4e/5eb629f6efc2a27e421d789dd4bfacbb5f6d09c80c28b13d1e87d73163d5/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0036473f5583e6a60e50b8b21651511564277a3f05cc5dab8cf579f552cd5f6c", size = 2049435, upload-time = "2026-10-11T18:33:10.366Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/8e/f195aebec49ad12318876ac2368c197a7939f5d52f8e156d2238ef8a4588/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:992c3514ec891fa7858099183e4d64e6bd5a5d4ff452fae29df22faa77a006bb", size = 2172975, upload-time = "2026-10-11T18:33:12.368Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/f5/7ad9fb83010cd5ea0948409db105676ed779c4e709e2d3d89b9fe2558794/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:739dc730e6be3bd5ec2f4ab5cfc7eb047cc45fc1497b3bafec74ff2ed07df597", size = 2364197, upload-time = "2026-10-11T18:33:14.244Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/fb/bf0aab3e78301d202b82a0322ec968cd703b11fc0623fde9a28708936f62/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fad3a91e51b6d2039c572db04a5a873260b399f6bd62c3552671fa7a4a2899", size = 2444259, upload-time = "2026-10-11T18:33:15.79Z" },
+ { url = "https://files.pythonhosted.org/packages/45/35/38f6d6564fae57d9b12e5676dfa947e0e7d5c46dbeaa7eb3352261d6d299/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b54c2c90ad348b5e3a85e03e715d572c1fde357ef104cdfe3b03b697a404ea", size = 2159857, upload-time = "2026-10-11T18:33:17.51Z" },
+ { url = "https://files.pythonhosted.org/packages/65/a0/fb0a3ca10f139dcf765b2d312d10cf0f65c57c59993229d00ad12b14ceff/pydantic_core-2.50.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:2df1ff41884de2bc4b307bafd7c40a691094fad2ff8e767e5b45a319257bcf4e", size = 2233954, upload-time = "2026-10-11T18:33:19.591Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/6a/e63842252702aa4ec6e6b7178ca85e541a77459076592c23ebe2d9840b33/pydantic_core-2.50.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe90228920fd8ff2be62622b6bb8a2b11acd65046d50c6b130614b5879605a20", size = 2243876, upload-time = "2026-10-11T18:33:21.393Z" },
+ { url = "https://files.pythonhosted.org/packages/39/25/5991cf8318b37e0dfab47b87541619a1df8501a793cba0d978846cba37a7/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:844b869f118e22a41a091bdcedda8a71bc1b0f62c38d1a0c3211cece47e1d8fc", size = 2277908, upload-time = "2026-10-11T18:33:23.324Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/3e/3ee8baaa6cc25a6961c69168cf9ff0f002d56f4e0d541ad6724c18fb61f3/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:2eb75304506894a281d346220a4f7481a1b8729577c5ed2a05395991966a8396", size = 2449285, upload-time = "2026-10-11T18:33:24.942Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/c7/acbec6deac13fe697a80c275a9b6661db62c4d323bac8a47347b1f39c7cd/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6b20a4bffabdad0db2927ac034ae3b8a681b1f7a0182f3e60b479ad2fde21ebb", size = 2479013, upload-time = "2026-10-11T18:33:26.925Z" },
+ { url = "https://files.pythonhosted.org/packages/10/08/21a3f237b264389f6219d053ee78fc1b5c2fd402c1b1cb2b6cb8b85f9834/pydantic_core-2.50.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:99ba9bc2b8062ea0c326a990f7f00e6530c23579de66dd246e72c4cafef950a5", size = 1484400, upload-time = "2026-10-11T18:33:28.693Z" },
+ { url = "https://files.pythonhosted.org/packages/09/ce/077a6d262d12ef09108377ac0717f029f420d773ace63cafd6143af75568/pydantic_core-2.50.1-cp314-cp314-win32.whl", hash = "sha256:cf356f70551d40374eaffb1aa63f1eb6d2006681cbd7a9faea173ce0f4dd7cd2", size = 2103222, upload-time = "2026-10-11T18:33:30.326Z" },
+ { url = "https://files.pythonhosted.org/packages/14/4c/350a2415209c43d670eb71d3c040f332c04a30583d39e311b05c7ac15762/pydantic_core-2.50.1-cp314-cp314-win_amd64.whl", hash = "sha256:d32f3acc081cc3923386d88f422cde8892335e95f034e0104bb4cf9310d9915f", size = 2171586, upload-time = "2026-10-11T18:33:32.139Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/92/9bea6ca96580a0902fed366f064f0829e404f41889b34543279a1162b888/pydantic_core-2.50.1-cp314-cp314-win_arm64.whl", hash = "sha256:bed5163e03b98bc1fa2eb05d74c63d9c5c95d8ed6254985481640fbf5e237dea", size = 2155773, upload-time = "2026-10-11T18:33:33.896Z" },
+ { url = "https://files.pythonhosted.org/packages/86/8c/f121f073cf32bdd5cba7e6230b1ce0ac845b0cf43e44dd597d95af272db2/pydantic_core-2.50.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9572c1369e9c9da2d64a7b7992c786d90ff295abc93964cfe3125e4290768070", size = 2234017, upload-time = "2026-10-11T18:33:35.588Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/69/2bb2bcd6146cffe98d760a46c42ae71efd5151d9b2f9c9bf6619a3b32083/pydantic_core-2.50.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2005207aafe1231315718bf6ed5d064a7300fb4772754af35ee72fc68159492e", size = 2001016, upload-time = "2026-10-11T18:33:37.57Z" },
+ { url = "https://files.pythonhosted.org/packages/20/b3/fbf854c7d07ec114260c26e9e2071a4381740f9ae09641dbfcbdf2a18c45/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f6047f62a6c5ae08d0a6afb035667aa2d97c3d20d69762e034c5ea144d92a5", size = 2023015, upload-time = "2026-10-11T18:33:39.433Z" },
+ { url = "https://files.pythonhosted.org/packages/65/20/6de55b2f92cdb614b745c6e9ced639fc4fd7e1e77604825a88bdece6fcbd/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ef800dd7d85bcdadf4c3076e4c94e43939493558a3b69a1ea830c706d4617bb", size = 2192543, upload-time = "2026-10-11T18:33:41.381Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/d9/19e91c94bd5c405945ce2f15526808aea37e162c253160de8ed7bf70b406/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0135bcdcaa0f23573f286e4cb5e0fd2962700964ed13df085b85f2b97aeab9e", size = 2388704, upload-time = "2026-10-11T18:33:43.167Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/bc/2e24c8415eae1a25ee5a5484946a917123bad2e9d01689a759236928175a/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b3a6f334c6a2345ca15318ff894502a90012536404b37c844a976c76c846e0b", size = 2464118, upload-time = "2026-10-11T18:33:44.856Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/1f/945b8053cb061c64e102bcaf7bfb9ed740c0bd4349f9bb978a7d4ddff4ab/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e01fbbfdb9be777b316a71b6c49efaf4a08b615d0a98d678cda3023f79d019", size = 2150142, upload-time = "2026-10-11T18:33:46.661Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/78/96a3e50bf0d64aaae781107eb9335b7529d05a85793ed6e7241c4cd1d931/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a29a061fec0b4e2d714f277e70a3a18125ecff803f2fea6eade2f2e53711d112", size = 2245577, upload-time = "2026-10-11T18:33:48.574Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/5d/a6038a0322232758a6ebfa709f4ca14a60bf5b93940cd0b345056a557da4/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5187624823423e1d1b82b1072ac41dc837389e18d3d0572cc19bbee46cd550a", size = 2263728, upload-time = "2026-10-11T18:33:50.527Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/4b/76ded3333a457a9344c4f2d63f2d82d0101654b05178fa4ddfe7d3627674/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3e46a9eb0a0901dd6275e6b06ac3a464885ef350ec4121fe486869de8053e4bb", size = 2293630, upload-time = "2026-10-11T18:33:52.362Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/00/9eca378335c9c1b72bc779bf6e4c2a82ce784f14ec48a0e87102c9870e05/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:756d669f04e62ec4148ecfe22be6a4484d9b1181a6ef32e205ebfd200540858b", size = 2468048, upload-time = "2026-10-11T18:33:54.118Z" },
+ { url = "https://files.pythonhosted.org/packages/35/ca/e3832e9cf93651251de43c8c5c029ed680a3c1a0a1b17ee26c81bed08cbd/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c516cc5367ca3448995d42cb994bf3f4c9002d2a7c22eac9622551269ad1b807", size = 2512159, upload-time = "2026-10-11T18:33:55.99Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f2/773469b5a10a39116a2cb17edaf6d722f017dd8da07161b371465311c542/pydantic_core-2.50.1-cp314-cp314t-win32.whl", hash = "sha256:9d1bed94af6a63835461f3cf7502058eb166c58c4778e11d0f433cfb1bd69e19", size = 2098406, upload-time = "2026-10-11T18:33:58.043Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/42/0bb74f8f25204b259b11ab7c12dc7f180893b6bd706118b385147fb6efd5/pydantic_core-2.50.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c8dce1f1e0e5358b682a6ad3fa5e31b31d4560997b8e61417e9217c8d60f8a0c", size = 2148481, upload-time = "2026-10-11T18:33:59.913Z" },
+ { url = "https://files.pythonhosted.org/packages/47/0d/d801646c9679a4e630e15cf521d4108b93854b42a6e6e02391bd4c6b1095/pydantic_core-2.50.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ceff0acc940be2715bd6ad17b24c0e5304abf44f6efd0f81ee8499e640f9dc86", size = 2174972, upload-time = "2026-10-11T18:34:01.875Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/84/23984b763d8862a02a13d27a44b6e8169428fd85ecfed88f54c29108604d/pydantic_core-2.50.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:8a6791afa2245e6c6b180122d105941644f5bd410bb18623b408808cc41a3102", size = 2224599, upload-time = "2026-10-11T18:34:04.016Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/90/a63cf8586abc1d1a3f6d9b18f0224789ebf003851f092ebda3c7c863fd32/pydantic_core-2.50.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:84f34323a61a365b4e9295de6028474754829aaddd59c7bf1a040e7487ef8f3c", size = 2024191, upload-time = "2026-10-11T18:34:05.901Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/6a/f34bff9808ffb4907fbf5f5040457d253cacf2da7fce9efbc99ca1b1a44d/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23edad659e8dbd8ca7e4e877fe6c81573abbdf215bd25a68b53e1272f58b80c7", size = 2048252, upload-time = "2026-10-11T18:34:07.757Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/54/13f419bf1eb59852003818e25935aaf175687d978f4c4bca70e08fe40a3b/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a5fce22f1e87d181e924e12da7d81cfe031fb3881a5ddf26ad28f141756ca43", size = 2173376, upload-time = "2026-10-11T18:34:09.592Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/6c/b5a34d24cd0c81669d8f8339d74e6815abcf2f8fb48ab4b49b84c09be1d5/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c73622ef819328873b53109ee4f77ceb598bffedd02daf916102be3228866b78", size = 2364559, upload-time = "2026-10-11T18:34:11.534Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/8d/d64d6216a8df365082665927ff923f183056f9049fee08e9777c9ac05296/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce8c25ca38cc0e3d7753ba180808de2c0c8cb24eae0df64491e40921454e9831", size = 2444697, upload-time = "2026-10-11T18:34:13.535Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/0d/1b1149f60a00ea21ba5f70e28acbd40feb4598af414f80f53c921fac07c9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7689580e72a642ab5ec64d5f55b2e33636fa43b4ebe63c0c2c965ef307c7d1aa", size = 2159961, upload-time = "2026-10-11T18:34:15.56Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/35/f236549299dcc78e71e945495d7ec67e78d20844d0999c60601685003031/pydantic_core-2.50.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d5c0e32fdbce7f1e8ef4d11f655694bf5f4175c757a9f1dc2be09b8864e5bcf5", size = 2234581, upload-time = "2026-10-11T18:34:17.502Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/85/26901a490522b7f75ef9bb9a7afb73e5bb550f0e1cb5b99a0069183e2eb9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40f523349960fa30f3ea51404308ff50f9997a90df639590f47a057c1f32b415", size = 2244090, upload-time = "2026-10-11T18:34:19.402Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/2c/481bcfc70ceeb77a778ca6e5b705fe592cb64735c108157f81b7dd9c280e/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:d4193206b6587047437f6f11d7e776df23e1c1e23af2a54d9347275614791e10", size = 2277948, upload-time = "2026-10-11T18:34:21.317Z" },
+ { url = "https://files.pythonhosted.org/packages/24/eb/f1e09333faa7ba447cde967310f758430cc7c5e987bdab5228816c70030d/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:84bc765b282a9d5b7fe0348b8648904f25a6a04b2139da52b1dd30c8ac3a2c8f", size = 2449688, upload-time = "2026-10-11T18:34:23.321Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/b3/036bde636db8f76d92996e81aefc75678ab5cec4a07eea1ad0c72a893fc3/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:ed1e728b39a383c81035b2459cfcb35d99dfb01f7d6ebe3a913bc1cc5b81e459", size = 2479286, upload-time = "2026-10-11T18:34:25.214Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/a3/07f018294ee18d144afeb6df1a47d9e92960f014be45c5ed13519fa5af95/pydantic_core-2.50.1-cp315-cp315-win32.whl", hash = "sha256:bc94f474417604bd383d2cd445d071b07dd55fedceed3ce33407bf1fcc107290", size = 2103683, upload-time = "2026-10-11T18:34:27.42Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/98/f9bd7e1f9b6709f155acb9ef826d9c3884fe925f811fd8e55b9b52280bad/pydantic_core-2.50.1-cp315-cp315-win_amd64.whl", hash = "sha256:983a662de2571cb2502fc8ff47b6770b03d025d2eb314c92f77b3f07c74720ed", size = 2171681, upload-time = "2026-10-11T18:34:29.508Z" },
+ { url = "https://files.pythonhosted.org/packages/10/87/4bb3e1e7f385c076ab5af4d6dd0571b22042cd8207eb810d5b9fef15ae31/pydantic_core-2.50.1-cp315-cp315-win_arm64.whl", hash = "sha256:94845ff54dc5193f228cab81b2662a04bfbb892e95bdc15edf7399000ce57d54", size = 2156251, upload-time = "2026-10-11T18:34:31.455Z" },
+ { url = "https://files.pythonhosted.org/packages/47/47/83643225b08f2aef6c8cc4bbe6e3f79c5e139c4364a6e450d0f399d87774/pydantic_core-2.50.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:4a53d13cdfbedbfa87f08b83c1a0a5efcc767d785a4b41934fa9cb672670493a", size = 2234363, upload-time = "2026-10-11T18:34:33.65Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/66/127ca649ba2f2462039dc1e694c6c01e00a3689f023a617364d3507e6d97/pydantic_core-2.50.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:efbecf43d321f7b9281441f1f213f7c21c66988b0e06c2730ba13ed47a46bb08", size = 2000972, upload-time = "2026-10-11T18:34:36.037Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/4f/e421e0a5d653b1203b090b2e48745976988d7338a647940704b5b9c2b399/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1f08f68dac9f9e83845a8039880aba2ab553eb9b2259c3243a313182c253fe", size = 2022925, upload-time = "2026-10-11T18:34:37.972Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/4e/ea5568e2491e1a71100f15ae8c2d01ef52db42a184a2d4e716bc79e5eb8f/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5dfe41f232befddb9c4377f6cfc702b51595e2d78ed082672adf8758d2c4619f", size = 2192867, upload-time = "2026-10-11T18:34:39.921Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/5e/3b8c3a35acbe219909ada5defad5d7d9fed845fb2b37bd5ec518f453c119/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adc06d218a1cadfd2ec4628424d7d79ce4eba69c2965e7e7b55106f0da5208c8", size = 2388687, upload-time = "2026-10-11T18:34:42.184Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/aa/7889b4e515f91a2e8c0ae6b5081fec0feb30c4398d1434e14793c60f173a/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cf91809d0721ab81592ba67bea7694821679c10b1a2e3c3460082b286c1918a", size = 2464415, upload-time = "2026-10-11T18:34:44.384Z" },
+ { url = "https://files.pythonhosted.org/packages/08/78/93449e628eb8a6fdcce3eff9043081179d1bc7ce6f1bff32dc5006f41e00/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23923ab9292c40da026330b1ecf4dc2618c8e86e0422e5d1fbf50d94d64ca4f8", size = 2150823, upload-time = "2026-10-11T18:34:46.392Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f1/72c5bc129fceb0d00f05dc1e67f518c1728de1928c55f81fc13d7690de39/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:f3377c8c2b3ce898423c5e5dd94c7982e30aa7717a7e6ab2470b9de364963709", size = 2245806, upload-time = "2026-10-11T18:34:48.805Z" },
+ { url = "https://files.pythonhosted.org/packages/28/2a/922a0e78f3aa6ab837b59f88190233fb8dad546c17995fde9bb3ed3b9b49/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:455a773617b5913bf5c20d0692e5787b119e52c4d40ea644ca31f5758fd31be2", size = 2264160, upload-time = "2026-10-11T18:34:50.876Z" },
+ { url = "https://files.pythonhosted.org/packages/52/a8/0f1449e3e1b20941c9372faa18e7b6a092e30cdfa841902473f7989532ac/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:1a9006395dece0e32e704c315eff8a00bede494f6108546cfc5539c89fef4f9a", size = 2294031, upload-time = "2026-10-11T18:34:52.876Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/d0/1031f492857de70355fb16524bbb03efce5fd34c93ed4a1ec60be07e0d4b/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d2d82aa62521c55ddfb000ae70f88cdd8de974078f6024e821dfe5addd0c818f", size = 2468423, upload-time = "2026-10-11T18:34:54.995Z" },
+ { url = "https://files.pythonhosted.org/packages/46/52/269ffffa645b8e47906395a39cf9db1151ae9fa4bcd7f47b960bc31baf37/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:009634b83993777ddcd69cad0ffcace43dabde692109528e35f0fde91e386a8b", size = 2512598, upload-time = "2026-10-11T18:34:57.149Z" },
+ { url = "https://files.pythonhosted.org/packages/31/5c/e47e28281f20326ff6f3c31d626f0a83e615d2d94ba41bb0ad6ec237184a/pydantic_core-2.50.1-cp315-cp315t-win32.whl", hash = "sha256:3fde4fdc6487a58d944ca87cf5adc95d5f266e872c19599f5f4c0a8a1b1f9f9f", size = 2098535, upload-time = "2026-10-11T18:34:59.313Z" },
+ { url = "https://files.pythonhosted.org/packages/03/ad/759e181e69c1b472c60b2049e5f61d5da1deefdd5ce1df4bb2ebcf771f25/pydantic_core-2.50.1-cp315-cp315t-win_amd64.whl", hash = "sha256:1c8632d4ac04e6f91128fca584b3a8a507d81604c24eeaaad00d4be42765c32b", size = 2148645, upload-time = "2026-10-11T18:35:01.571Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/56/8a702c27e5be9f47e5f19d8669227424290e4c024e7c370279cbaf244b4e/pydantic_core-2.50.1-cp315-cp315t-win_arm64.whl", hash = "sha256:c3ede305158e75510be50869b319550ab072008c13d64d4ab1e094fb286b6f44", size = 2175297, upload-time = "2026-10-11T18:35:04.079Z" },
+]
+
+[[package]]
+name = "pymongo"
+version = "4.19.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "dnspython" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/8b/a9d214044153cb7d9141229d3e1b171cdf4f460fa07cade9354c4ce2f84d/pymongo-4.19.0.tar.gz", hash = "sha256:3c510dd3c5d9b392d3b33bb5d2a594758acfe8f026fca654253f947ce0af9d40", size = 2689381, upload-time = "2026-10-14T19:48:19.629Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ee/e7/6e62d60303a1e5cc816cefaa4d57d74df8ee65753ee9fe154b5fad851de3/pymongo-4.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:08c354566ab8b5dce6d805f35d61b5575455d3ea1835d7b90151d53e8c32e669", size = 826849, upload-time = "2026-10-14T19:46:56.892Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/68/b2f67b99f22c5543a8be397c0ed8dee526c23717b4491405ae513138d88c/pymongo-4.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:06b9ee12c4ceb7fb6ff8a7ab0465814c1cb5e5c6c2c452cb18eab7435b38a5b2", size = 827363, upload-time = "2026-10-14T19:46:58.842Z" },
+ { url = "https://files.pythonhosted.org/packages/02/bb/35e17473d000bc0517190aabe1429853aa142499370dbd6d7ae3743e8833/pymongo-4.19.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ec25ab536e42e48fde356c6fc86e66f548e5af0cc584365e2ec34d3683be5a63", size = 1049786, upload-time = "2026-10-14T19:47:00.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/2f/83cc2961d977c1ba36662f24ae55c9f5dbee2845ca615146fec0f4eda053/pymongo-4.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e65783e95b37c3387ed1105fe01e2be6b1b394c22331c5e8cc2fed2c3a30a06", size = 1059425, upload-time = "2026-10-14T19:47:02.511Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/94/baa32ef582f9edf3112b00f6e271cf5f83c481edcf999e2f462898990e87/pymongo-4.19.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3264b209b6319cae120306e266ed5fa9c7bc071b73ba5e13cbad23a6cbd73d2", size = 1082787, upload-time = "2026-10-14T19:47:04.38Z" },
+ { url = "https://files.pythonhosted.org/packages/37/eb/949a24776ceba31e9b731f7048dce4fbb913047afd16580a61723143afb9/pymongo-4.19.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:212dbc97f8e813a24639aaaef38503d84f7652d00b88b391f87762ba4c1f1709", size = 1073579, upload-time = "2026-10-14T19:47:06.247Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/b0/a577ab8eff3772cf7036118b4e407a8cbb53add7bbe322f011871eb6db44/pymongo-4.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2faa34469b052635c81dcec6b07fc5757d4aba0ec60f94c6658c7fa6f887bc46", size = 1057865, upload-time = "2026-10-14T19:47:08.076Z" },
+ { url = "https://files.pythonhosted.org/packages/95/cf/81b1d8a35ac3e5d5dcd8fc466f9acdd8f67a5035da130afb0d76e2efd6ac/pymongo-4.19.0-cp314-cp314-win32.whl", hash = "sha256:eee3fc70ea4253c8c7a6bd7917be468c5ef0a2860898766dd55497a563ddda94", size = 823938, upload-time = "2026-10-14T19:47:10.086Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/c5/1aa13304c714ad81ab70feb6bd99f6514baafe8e6c84d243ffabae678379/pymongo-4.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:ac673404456b23c568cea326ab996a6b35a6009e41d42bcb774db025d0918b7d", size = 831074, upload-time = "2026-10-14T19:47:12.088Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/a8/5de505ba380af3d10737a2d0ddd2c6752ff6e9a0fe484c992483efe74889/pymongo-4.19.0-cp314-cp314-win_arm64.whl", hash = "sha256:2bb0e7c422c14ff2b31ec8be3e6ecaad326c17fca17071bcfcd13482584a8e0f", size = 826686, upload-time = "2026-10-14T19:47:13.959Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/fc/eddcc314b76ab9f3ab1417ecc088f88336cc2bca5be1356c8aa3d183dda8/pymongo-4.19.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b01cc054878931ea81fc0a57c4c10489db723b8d7275fb10070f7228149012f1", size = 829839, upload-time = "2026-10-14T19:47:15.761Z" },
+ { url = "https://files.pythonhosted.org/packages/87/51/caa4ac1f33d4b8a4de2469a0624ffc7f7fae7441f7d71d41c2be306734a4/pymongo-4.19.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:823f8b2fb59e4e635e296d5e92efa883e3d01a8faa477d515fc9dfe515368026", size = 830216, upload-time = "2026-10-14T19:47:17.789Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/e7/b3eb14aa900cfe7b6f7c0dd2349b5d0a488c17a9db76a8bfdf8bd30afd9d/pymongo-4.19.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1435721737b46be9bab5aa2374cfe57de934dc4ac421d5473308aa94c9fa39c3", size = 1113851, upload-time = "2026-10-14T19:47:19.743Z" },
+ { url = "https://files.pythonhosted.org/packages/00/b7/ec2c2bdde80e23693703f01805a1e37509e088127177f2d5758ca05c9a79/pymongo-4.19.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dee18feff3203fa128798c6673c7795ef8a46d0b32c0e6b920c7b3f46129447", size = 1133130, upload-time = "2026-10-14T19:47:21.617Z" },
+ { url = "https://files.pythonhosted.org/packages/40/df/4f1bada8fa02babd094a5c4ed8f4ea1dc76cfc1366b26238a2ad1fc55b51/pymongo-4.19.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8d866560dfbe44bc5e1110e96af4b8d92ffe6368c345dac1c36c8060188ebba6", size = 1152659, upload-time = "2026-10-14T19:47:23.572Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/cb/a97d315c4c4e362d1f2e216d306122ae0f713ab457f73730684f3606a349/pymongo-4.19.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:47f04522f786dca82c776d5c3ed3ff9d08d6bf4cd0074c42296da5fac4d816ad", size = 1144177, upload-time = "2026-10-14T19:47:25.554Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/59/2a6c68bdee03f326194361149c68ec6720a22460d11a2a43a0742a7d7fce/pymongo-4.19.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac55cf643eaa6146822f5f05f07be4dedbed906f525bb2ee098a865c4892788a", size = 1125673, upload-time = "2026-10-14T19:47:27.582Z" },
+ { url = "https://files.pythonhosted.org/packages/20/c1/b108dda370e09db7a4dccfb2bb003e769a8dd98513135e4429040cb88b83/pymongo-4.19.0-cp314-cp314t-win32.whl", hash = "sha256:3bcebec2536a9aec1d490ad6fa9fc7ffc3329059fb1f99154efa5d594abdc98c", size = 826636, upload-time = "2026-10-14T19:47:29.463Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/55/a0da8479007f149838c094f6f863fc05c973abf6802654881a4dfc68858e/pymongo-4.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24668c6990bef96e1558328ba0802279cc1f752a3bcc7b283c2f39099a01e28c", size = 835337, upload-time = "2026-10-14T19:47:31.313Z" },
+ { url = "https://files.pythonhosted.org/packages/98/d0/9837244d18d8280277e7b2e9366ee2b9d35338052362888a4704d77ad633/pymongo-4.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:542b0f4e47fe68e753c85503f8352d4baa81ac73593601c8ede0fa22ba5c0431", size = 827513, upload-time = "2026-10-14T19:47:33.367Z" },
+ { url = "https://files.pythonhosted.org/packages/97/6c/af80cf714a91b41441e9ad0aeac1af2000d902dfef7bac31388ba05bbfe7/pymongo-4.19.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:cc81d7ceeb7766254bce7ad7644dddb44241fb57555cd7c71de305b6903493b8", size = 826913, upload-time = "2026-10-14T19:47:35.317Z" },
+ { url = "https://files.pythonhosted.org/packages/95/14/2ed9ee6c83fd05a36d310100562b599ea987d2339c57955b1afba80d07ec/pymongo-4.19.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:b602baef46ec5cd876fdf45dfdf864a58f5a507129393b93b8248249008f9a70", size = 827513, upload-time = "2026-10-14T19:47:37.463Z" },
+ { url = "https://files.pythonhosted.org/packages/78/78/cd65885104e7b37f8cb7dd7e33d0b2c2415270afc2644ed643b52f526214/pymongo-4.19.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:179bc536b73fc76ae3d227114123ffc804f002fb45ddd996a81b233e806a0d2d", size = 1052003, upload-time = "2026-10-14T19:47:39.539Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/ed/99fc74ed08dded2351818bf374303ddc400bd2e8b5ab297dac352aa0df56/pymongo-4.19.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4bd5e3ecd44d94b4eeef51f7e20a513206f2fceeab9534e9299c31133cc2e42", size = 1061762, upload-time = "2026-10-14T19:47:41.601Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/8b/ded0ef32a2c4032cbec796f29b7b6067e76ac27714fbcfe06ce9a969b415/pymongo-4.19.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a38cfd2d81daef820a099c28065c6dc2ec9254ae80fefcf7981ea27e5381159", size = 1084314, upload-time = "2026-10-14T19:47:43.874Z" },
+ { url = "https://files.pythonhosted.org/packages/52/64/82099393a7178c80fe1b16cc5dca94f388dec3df7a3f059a7b831bbf10dd/pymongo-4.19.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:567e509e1e01c956bfd5e60805b7d582aae45eeba34e9690d0da6f09560afb4f", size = 1075560, upload-time = "2026-10-14T19:47:45.904Z" },
+ { url = "https://files.pythonhosted.org/packages/09/d2/1eab760f5dc3d09fbc8fec7ad2474def3c8d2efbeb8550bff12fed61f863/pymongo-4.19.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c3a47a6b325ac605352e9825ef658e6cca4f612e3a09838a564859f7d5435ea", size = 1060541, upload-time = "2026-10-14T19:47:47.885Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/7d/426c1b661e8b4bd78671ea063ee66005faff0dfe6731ebdce0fb0000c339/pymongo-4.19.0-cp315-cp315-win32.whl", hash = "sha256:5d684e289cdb687f1508b15a44d3c0268f974c92ba129f658c1ef1fd196854e7", size = 823985, upload-time = "2026-10-14T19:47:50.253Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/e9/f2ece0253d82d34fad0a316ffec848ac4e85357cae849cd5ea29def72ae4/pymongo-4.19.0-cp315-cp315-win_amd64.whl", hash = "sha256:546350d196b01b7feff7f8e6d140b6d4ab47486d5ae70dab858605cdfc2ffe1d", size = 831173, upload-time = "2026-10-14T19:47:52.418Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/e0/be46ba1676cd04f831a9d4f6f8dbe0d3f816034788b8e3157762139f7aa8/pymongo-4.19.0-cp315-cp315-win_arm64.whl", hash = "sha256:d29ea47eebbeec81b67809fbb3440ffc53628d28f5b9f21624eed0038d9fddaa", size = 826603, upload-time = "2026-10-14T19:47:54.538Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/20/3e04d21eab4844372ef141d5cc4f5e03d4fb9ebda057dd5e5ef1db562433/pymongo-4.19.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:b7e8b5b546e31ac63255650b0bf764383885a6c657b3269e83b9e1e5de3ed129", size = 829820, upload-time = "2026-10-14T19:47:56.428Z" },
+ { url = "https://files.pythonhosted.org/packages/44/9c/dbad3291c3614a884285d10e2cc123567386d682bf8a08caf5e0a630bf3e/pymongo-4.19.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:f21109534f5555cf77689ad323a21fbc07e8a397b34f157938a347725d83b7b5", size = 830313, upload-time = "2026-10-14T19:47:58.457Z" },
+ { url = "https://files.pythonhosted.org/packages/68/2d/17e783859c89e749fe63803a08ab5e85ca0ee8416f0cbe84d5fe6efa2981/pymongo-4.19.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3af5ab5a9e490580d3f40660665f0f4d579a324e25acee6372e1508e4b7c7b7a", size = 1117096, upload-time = "2026-10-14T19:48:00.917Z" },
+ { url = "https://files.pythonhosted.org/packages/34/cf/0b23e363eb5856ecfdf3b7edbdfea7f964da664eb78e507bb8375820c7e5/pymongo-4.19.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb9d9bff4f666405cd9d7a17b6127294394847dce60ca38d8ba45f4879ada6c9", size = 1134854, upload-time = "2026-10-14T19:48:03.05Z" },
+ { url = "https://files.pythonhosted.org/packages/23/b8/60758f35a90729d77fdfd36eeff5ddf191d9f198528074884d816865d942/pymongo-4.19.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be75840640e98ea4b5f150bceda8a55f1085e395732e21da028195da30ae79b5", size = 1154121, upload-time = "2026-10-14T19:48:05.638Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/b0/e2b56cf154bf1dff7deca641de160215f9163253609a8beb780dc35f007b/pymongo-4.19.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa39c6ddaf987a48ef073ff7fc225b84282079a46fbabaea9c5fcb6f89476e44", size = 1143786, upload-time = "2026-10-14T19:48:07.734Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/88/39b61ede07785568d47229a01e7e82fc3903f5cac55ad377e0e64a0d324a/pymongo-4.19.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b92aa4cc4b0bf67a18e3c73062ef70e00ca6921c742aa4d0f4770a493193c661", size = 1126770, upload-time = "2026-10-14T19:48:09.891Z" },
+ { url = "https://files.pythonhosted.org/packages/40/f2/391d41d24384545b2a6ed09694b2444f765a6e20932c75ed4eb507c9ef36/pymongo-4.19.0-cp315-cp315t-win32.whl", hash = "sha256:eececca812e8f5b3c12ad33dc90201ac20f5f193da446f7719f4321a0841387b", size = 826638, upload-time = "2026-10-14T19:48:11.962Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/48/96b923a2d29456896c7f11f8e6104339818112f5a8621f42ba51f131a510/pymongo-4.19.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f17b100fdc16b65c12997ec4fcc78eecc0a6395254c7ec92a4596e855ff1f33a", size = 835209, upload-time = "2026-10-14T19:48:14.063Z" },
+ { url = "https://files.pythonhosted.org/packages/46/6b/2ede9f64d96393e8111d250620f5340d64e62f4617322a43800516027ce9/pymongo-4.19.0-cp315-cp315t-win_arm64.whl", hash = "sha256:bfcb5f8912edd9714a52564ad41c0dcd72e5408d1d3d67b41f6145df4a516318", size = 827452, upload-time = "2026-10-14T19:48:17.534Z" },
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.2.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/74/26/2fbeedb218a787a5eea551c7532cac4e009f83d689dd2faa0d0353473f86/python_dotenv-1.2.4.tar.gz", hash = "sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0", size = 60824, upload-time = "2026-10-01T05:36:10Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/d1/38f3a3405989a89ac18390803e70c6ad7c7760da4f9b83cbeca0c44a0c72/python_dotenv-1.2.4-py3-none-any.whl", hash = "sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc", size = 23266, upload-time = "2026-10-01T05:36:08.633Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.34.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
+]
+
+[[package]]
+name = "slrmentor-backend"
+version = "0.1.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "flask" },
+ { name = "flask-cors" },
+ { name = "flask-pymongo" },
+ { name = "google-genai" },
+ { name = "gunicorn" },
+ { name = "openai" },
+ { name = "pydantic" },
+ { name = "pymongo" },
+ { name = "python-dotenv" },
+ { name = "requests" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "flask", specifier = ">=3.1.3" },
+ { name = "flask-cors", specifier = ">=6.0.5" },
+ { name = "flask-pymongo", specifier = ">=2.3.0" },
+ { name = "google-genai", specifier = ">=2.10.0" },
+ { name = "gunicorn", specifier = ">=26.1.0" },
+ { name = "openai", specifier = ">=2.44.0" },
+ { name = "pydantic", specifier = ">=2.13.4" },
+ { name = "pymongo", extras = ["srv"], specifier = ">=4.6.1" },
+ { name = "python-dotenv", specifier = ">=1.2.4" },
+ { name = "requests", specifier = ">=2.34.2" },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+]
+
+[[package]]
+name = "tenacity"
+version = "9.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" },
+]
+
+[[package]]
+name = "truststore"
+version = "0.10.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ee/9f/c5201d42a484c061e528825fc8e2d565f5abd50a4ced6fb7d29c4ec99b2b/truststore-0.10.5.tar.gz", hash = "sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd", size = 28091, upload-time = "2026-10-12T22:27:31.808Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/e9/3a7820be2bb0fe53b6bc9c3be26d3d1158004e4c3ab953aa6840b955b1e9/truststore-0.10.5-py3-none-any.whl", hash = "sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c", size = 19017, upload-time = "2026-10-12T22:27:30.377Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" },
+]
+
+[[package]]
+name = "websockets"
+version = "16.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/21/f7/bc3a25c5ec26ce62ce487690becc2f3710bbc7b33338f005ad390db0b986/websockets-16.1.1.tar.gz", hash = "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57", size = 182204, upload-time = "2026-07-17T22:51:05.858Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/73/a2/ba78a164eeea4620df4a4df4bd2ed6017438c4655cc0f36f2c0bc0432355/websockets-16.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:443aefe96b7fdb132e2a70806cca1f2af49bb3f28e47abcd7c2e9dcf4d8fa1b8", size = 179635, upload-time = "2026-07-17T22:50:05.001Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/08/d26d7a7628cd4ac34cbbdb63ac80914ca842ed8e42938c40a53567806df3/websockets-16.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6456ff333092d509127d75a638cb411afae8ff17f092635015d1902efec8a293", size = 177320, upload-time = "2026-07-17T22:50:06.427Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/45/ebec83e6269536aa5932533c67b0af5c781f3e73fdbcd68672dcf43f4f44/websockets-16.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fce6c48559c86d1ac3632ccb1bebc7d5442fbe79bd9bb0e40379ee54be2a4051", size = 177544, upload-time = "2026-07-17T22:50:07.834Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d5/abc614d2297f6c1c3e01e61260364457a47c25cc1cf6a879038902bc6aa8/websockets-16.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:92b820d345f7a3fc7b8163949ee92df910f290c3fc517b3d5301c78065adafe1", size = 187270, upload-time = "2026-07-17T22:50:09.275Z" },
+ { url = "https://files.pythonhosted.org/packages/52/71/4c99af3b87dff1b2927981f6876607d4acb45338c665242168d3982f7758/websockets-16.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a606d9c24035242a3e256e9d5b77ed9cd6bccfcb7cf993e5ca3c0f6f68fb6a7", size = 188509, upload-time = "2026-07-17T22:50:10.722Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/b4/5c8ca14b0df7eb84ed0524165c5359150210140817a3312aee57bf62a1cf/websockets-16.1.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:414e596c75f74e0994084694189d7dc9229fb278e33064d6784b73ffbba3ca31", size = 189882, upload-time = "2026-07-17T22:50:12.293Z" },
+ { url = "https://files.pythonhosted.org/packages/25/c1/bedfba9e70557129cb8083748d167bdcc01483dedf0f0df143676df05cbe/websockets-16.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:536676848fc5961aca9d20389951f59169508f765637a172403dc5434d722fa0", size = 189114, upload-time = "2026-07-17T22:50:13.789Z" },
+ { url = "https://files.pythonhosted.org/packages/df/09/aa835b2787835aebd839114be5de51b797cb480b63ba42b26d34dfe147cb/websockets-16.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97fd3a0e8b53efa41970ac1dff3d8cf0d2884cadeb4caaf95db7ad1526926ee3", size = 187861, upload-time = "2026-07-17T22:50:15.179Z" },
+ { url = "https://files.pythonhosted.org/packages/20/26/f6408330694dbc9830857d9d23bc14ac4f6875127a480cfdda8d5ca21198/websockets-16.1.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7b1b19636af86a3c7995d4d028dbe376f39b4bf31541146f9c123582a6c94562", size = 185286, upload-time = "2026-07-17T22:50:16.741Z" },
+ { url = "https://files.pythonhosted.org/packages/17/9a/e0675e70dd8a80762cf35bb18799d3f290a4890ffe6439bc51d222796083/websockets-16.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41c8e77f17294c0ac18008a7309b99b34ee72247ef10b6dff4c3f8b5ac29896b", size = 187935, upload-time = "2026-07-17T22:50:18.213Z" },
+ { url = "https://files.pythonhosted.org/packages/33/c1/3234cfb86afde01b81e9bddcc6e534c440975d60a13991259e833069ab3e/websockets-16.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9f63bcef7f4b02b06b35fc01c93b96c43b5e88e1e8868676caacf493d5a31f3a", size = 186444, upload-time = "2026-07-17T22:50:19.67Z" },
+ { url = "https://files.pythonhosted.org/packages/89/87/9c15206e1d778923d8daa9657de07aa62ea815e13448319c98458c37b281/websockets-16.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dab9eb87869da2d6ed3af3f3adf28414baae6ec9d4df355ffc18889132f3436c", size = 188409, upload-time = "2026-07-17T22:50:21.28Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/00/cf5de5c67676de2d3eef8b2a518f168f6796595447a5b7161ba0d012915c/websockets-16.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:43e3a9fdd7cbf7ba6040c31fae0faf84ca1474fef777c4e37912f1540f854499", size = 185958, upload-time = "2026-07-17T22:50:22.719Z" },
+ { url = "https://files.pythonhosted.org/packages/62/c0/731b6ddede2e4136912ec4cff2cffbda35af73546be4762c3d7bd3bd79af/websockets-16.1.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:056ae37939ed7e9974f364f5864e76e49182622d8f9751ac1903c0d09b013985", size = 186911, upload-time = "2026-07-17T22:50:24.108Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/7f/39c634472c4469a24a7c09cecddffb08fac6d0e74f73881a94ee8a40a196/websockets-16.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0eadbbf2c30f01efa58e1f110eb6fa293261f6b0b1aa38f7f48707107690af9", size = 187204, upload-time = "2026-07-17T22:50:25.548Z" },
+ { url = "https://files.pythonhosted.org/packages/26/89/9667c256c256dafcc62d21328ce7a40067da857969b68ee9af375b0aaf72/websockets-16.1.1-cp314-cp314-win32.whl", hash = "sha256:195c978b065fa40910582464f99d6b15c8b314c68e0546549a55ed83f4735328", size = 179603, upload-time = "2026-07-17T22:50:27.086Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/dd/1c099d6c0fc5deb6b46ccdbb6981fdb4b12c917869cb3952408409dc18db/websockets-16.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:4e8d01cc3bcae7bbf8167f944aeafefed590fae5693552bba9794a9df68371cc", size = 179948, upload-time = "2026-07-17T22:50:28.521Z" },
+ { url = "https://files.pythonhosted.org/packages/35/25/9956b2d5e0529d5d23924f21bba1440d4c5c88a562e4f08550871ffa97a7/websockets-16.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0ffd3031ea8bda8d61762e84220186105ba3b748b3c8da2ae4f7816fac03e573", size = 179963, upload-time = "2026-07-17T22:50:29.982Z" },
+ { url = "https://files.pythonhosted.org/packages/17/06/55ffc976c488b6aee9ea05761ff7c4e88e7c1fd82818c8ca7b556ad2f90c/websockets-16.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84a2cef8deffbd9ab8ee0ea546a2a6a7030c28f44e6cdd4547dbfeb489eb8999", size = 177497, upload-time = "2026-07-17T22:50:31.396Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/e8/f7dac2e980bacc92bdc26cebae4ae4d50cae5380732c50980598fc0bbae4/websockets-16.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3df13f73af9b3b38ab1195eb299ecb67a4330c911c97ae04043ff74085728abe", size = 177698, upload-time = "2026-07-17T22:50:32.829Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/39/26762f734113e22da2b942c3aca85798e0c0405d64c256549540ff31e5a1/websockets-16.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:23253dd5bcae3f9aaee0a1d30967a8dbd52e5d3cff93a2e5b84df57b77d4750d", size = 187561, upload-time = "2026-07-17T22:50:34.24Z" },
+ { url = "https://files.pythonhosted.org/packages/11/94/c3f330851806b9b02138b774d593478323e73c99238681b4b93efe64e02d/websockets-16.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1c5705e314449e3308872fe084b8571ce078ee4fc55a98a769bdefe5917392", size = 188732, upload-time = "2026-07-17T22:50:36.088Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/f2/eb2c450f052de334ae33cf200ece6e87b0e14d186807074e4eb1cd2cdea2/websockets-16.1.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69e52d175a0a7d1e13b4b67ad41c560b7d98e8c6f6126eb0bda496c784faf8c7", size = 190872, upload-time = "2026-07-17T22:50:38.008Z" },
+ { url = "https://files.pythonhosted.org/packages/70/31/2ac8cecf3a74f7fed9132129fc3d90b3998a1554570c11a69b2a8c20332d/websockets-16.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f79c89b5eb034d1722938a891916582f8f7f503f58ca22518a63c3f2cd18499", size = 189305, upload-time = "2026-07-17T22:50:39.53Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/cf/8ab19650d3c0d4562c92e70ab47c257c4aa5c6a713ed87fe63766b31fefc/websockets-16.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39f2a024af5c345ffe8fcf1ee18c049c024c94df393bb09b044a6917c77bde43", size = 188033, upload-time = "2026-07-17T22:50:40.912Z" },
+ { url = "https://files.pythonhosted.org/packages/66/d7/a49a38a6127a4acb134fb1912b215d900cc657605cff32445bf519f3acc4/websockets-16.1.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:952303a7318d4cbe1011400839bb2051c9f84fa0a35923267f5daba34b15d458", size = 185748, upload-time = "2026-07-17T22:50:42.559Z" },
+ { url = "https://files.pythonhosted.org/packages/95/3e/ad1fa40388c7f2e0bb2c7930d0090b6c5498594bd1cdaec18864df3d9e97/websockets-16.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:249116b4a76063d930a46391ad56e135c286e4562a18309029fc2c73f4ed4c62", size = 188285, upload-time = "2026-07-17T22:50:43.974Z" },
+ { url = "https://files.pythonhosted.org/packages/35/b8/d5db28ca264b9104f82196f92dc8843e35fd391f763d42e4ad358f5bc97e/websockets-16.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:61922544a0587a13fd3f53e4c0e5e606510c7b0d9d22c8444e5fae22a06b38cb", size = 186777, upload-time = "2026-07-17T22:50:45.474Z" },
+ { url = "https://files.pythonhosted.org/packages/42/9c/726cb39d0cc43ae848dce4aa2acb04eecc6738b1264ec6d700bf6bcfb9f8/websockets-16.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:46dcaa042cd1de6c59e7d9269fa63ff7572b6df40510600b678f0826b3c7af51", size = 188682, upload-time = "2026-07-17T22:50:46.973Z" },
+ { url = "https://files.pythonhosted.org/packages/be/c7/1168704de8c2dd483edabe4a22cbe4465dd8be8dd95561d214f9fe092871/websockets-16.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:38565aca3e01ea8734e578fb2118dade0ecb0250533f29e22b8d1a7a196cf4d0", size = 186377, upload-time = "2026-07-17T22:50:48.413Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/40/f9ff2d630ffce4e7dfea0b2288e1caf9ebbf9ff8a9ec9396136ce8b94935/websockets-16.1.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:42f599f4d48c7e1a3338fdaac3acd075be3b3cf02d4b274f3bf2767aedd3d217", size = 187148, upload-time = "2026-07-17T22:50:49.845Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/71/e177c8299f78d7cbe2d14df228643c10c70c0e86e108e092056bbcc16e46/websockets-16.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcc04fedf83effaeb9cce98abc9469bb1b42ef85f03e01c8c1f4438ef7555737", size = 187578, upload-time = "2026-07-17T22:50:51.619Z" },
+ { url = "https://files.pythonhosted.org/packages/49/b2/b6987faf330f5af5c787a2610124c2e8403d51724f9001ec4fff6311fe7a/websockets-16.1.1-cp314-cp314t-win32.whl", hash = "sha256:8483c2096363120eea8b07c06ae7304d520f686665fffd4811fad423930a65d7", size = 179729, upload-time = "2026-07-17T22:50:53.269Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/6e/fbac6ed878dd362fbad7d415fa4f84d38e3e33fed8cde45c64e783acf826/websockets-16.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bcce07e23e5769375158f5efdcdafa8d5cd014b93c6683865b840ed65b96f231", size = 180072, upload-time = "2026-07-17T22:50:54.969Z" },
+ { url = "https://files.pythonhosted.org/packages/be/4d/2d0d67834092e354d2b0498f014a41249a89556bc406cf86f3e1557bb463/websockets-16.1.1-py3-none-any.whl", hash = "sha256:6abbd3e82c731c8e531714466acd5d87b5e88ac3243465337ba71d68e23ae7e3", size = 173814, upload-time = "2026-07-17T22:51:04.184Z" },
+]
+
+[[package]]
+name = "werkzeug"
+version = "3.1.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a4/34/4dd12fc8bb7d61c91467ec3efe415ffa7d5456f799954b40c5bbaeae470e/werkzeug-3.1.9.tar.gz", hash = "sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060", size = 940188, upload-time = "2026-09-27T18:33:41.637Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a1/38/df03f564f43cec2684823f3cccae1a652ee7face1cbaa76fb223096e64d7/werkzeug-3.1.9-py3-none-any.whl", hash = "sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab", size = 228700, upload-time = "2026-09-27T18:33:39.685Z" },
+]