diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..a2630e11 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: Build +on: + push: + branches: + - dev + - testing + - wip + - main + pull_request: + types: [opened, synchronize, reopened] +jobs: + sonarqube: + name: SonarQube + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: SonarQube Scan + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: + -Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }} + -Dsonar.organization=${{ secrets.SONAR_ORGANIZATION }} \ No newline at end of file diff --git a/src/training/__init__.py b/.github/workflows/workflow.yml similarity index 100% rename from src/training/__init__.py rename to .github/workflows/workflow.yml diff --git a/.gitignore b/.gitignore index 8255c680..30ccf41e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ +tim-db +.vscode/ +.idea/ +# Node modules +node_modules/ + +#Common .ignore files +*.DS_Store +*.log # MLflow generated files mlflow_artifacts/ mlflow_data/ @@ -22,4 +31,4 @@ logs/ test/logs/ **/logs/ *.log.* -*.zip \ No newline at end of file +*.zip diff --git a/DSL/Resql/global-classifier/GET/empty.sql b/DSL/Resql/global-classifier/GET/empty.sql new file mode 100644 index 00000000..c53c2392 --- /dev/null +++ b/DSL/Resql/global-classifier/GET/empty.sql @@ -0,0 +1,2 @@ +-- This is a empty sql for Buerokratt DSL delivery method +SELECT current_date; \ No newline at end of file diff --git a/DSL/Resql/global-classifier/POST/store-classification-feedback.sql b/DSL/Resql/global-classifier/POST/store-classification-feedback.sql new file mode 100644 index 00000000..c551cf4a --- /dev/null +++ b/DSL/Resql/global-classifier/POST/store-classification-feedback.sql @@ -0,0 +1,12 @@ +INSERT INTO classification_feedback ( + chat_id, + inference_id, + actual_agency_id +) +VALUES ( + :chatId, + :inferenceId, + :actualAgencyId, + :feedback_timestamp::timestamp + ) +RETURNING id, chat_id, inference_id, feedback_timestamp; \ No newline at end of file diff --git a/DSL/Resql/global-classifier/POST/store-classification.sql b/DSL/Resql/global-classifier/POST/store-classification.sql new file mode 100644 index 00000000..e563434d --- /dev/null +++ b/DSL/Resql/global-classifier/POST/store-classification.sql @@ -0,0 +1,13 @@ +INSERT INTO classification_results ( + chat_id, + inference_id, + target_agencies, + classification_timestamp +) +VALUES ( + :chatId, + :inferenceId, + :targetAgencies::jsonb, + CURRENT_TIMESTAMP +) +RETURNING id, chat_id, inference_id, classification_timestamp; \ No newline at end of file diff --git a/DSL/Ruuter.public/global-classifier/POST/chats/classify.yml b/DSL/Ruuter.public/global-classifier/POST/chats/classify.yml new file mode 100644 index 00000000..bcbb9725 --- /dev/null +++ b/DSL/Ruuter.public/global-classifier/POST/chats/classify.yml @@ -0,0 +1,73 @@ +declaration: + call: declare + version: 0.1 + description: "Callback endpoint for Global Classifier to indicate routing decisions" + method: post + accepts: json + returns: json + namespace: global-classifier + allowlist: + body: + - field: chatId + type: string + description: "Unique identifier of the chat session" + - field: inferenceId + type: string + description: "Unique identifier of the prediction attempt" + - field: targetAgencies + type: json + description: "Array of agency IDs with confidence scores for routing" + +extractDecisionData: + assign: + chatId: ${incoming.body.chatId} + inferenceId: ${incoming.body.inferenceId} + targetAgencies: ${incoming.body.targetAgencies ?? []} + next: validate_decision + +validate_decision: + switch: + - condition: ${!chatId} + next: return_validation_error + next: log_classification + +return_validation_error: + status: 400 + return: "Invalid classification payload. Chat ID is required." + next: end + +log_classification: + log: "Classification received for the chat ${chatId}: ${JSON.stringify(targetAgencies)}" + next: store_classification_data + +store_classification_data: + call: http.post + args: + url: "[#GLOBAL_CLASSIFIER_RESQL]/store-classification" + body: + chatId: ${chatId} + inferenceId: ${inferenceId} + targetAgencies: ${JSON.stringify(targetAgencies)} + result: store_result + error: handle_storage_error + next: check_storage_result + +check_storage_result: + switch: + - condition: ${200 <= store_result.response.statusCodeValue && store_result.response.statusCodeValue < 300} + next: return_success + next: handle_storage_error + +handle_storage_error: + log: "Error storing classification data: ${store_result.error}" + next: return_storage_error + +return_storage_error: + status: 500 + return: "Failed to store classification data. Please try again later." + next: end + +return_success: + status: 200 + return: "Classification received and stored successfully" + next: end \ No newline at end of file diff --git a/DSL/Ruuter.public/global-classifier/POST/chats/feedback.yml b/DSL/Ruuter.public/global-classifier/POST/chats/feedback.yml new file mode 100644 index 00000000..093af207 --- /dev/null +++ b/DSL/Ruuter.public/global-classifier/POST/chats/feedback.yml @@ -0,0 +1,79 @@ +declaration: + call: declare + version: 0.1 + description: "Endpoint to receive feedback on global classifier predictions" + method: post + accepts: json + returns: json + namespace: global-classifier + allowlist: + body: + - field: chatId + type: string + description: "ID of the chat that was classified" + - field: inferenceId + type: string + description: "Unique identifier of the classification prediction" + - field: actualAgencyId + type: string + description: "The agency that actually handled the request" + +extractFeedbackData: + assign: + chatId: ${incoming.body.chatId} + inferenceId: ${incoming.body.inferenceId} + actualAgencyId: ${incoming.body.actualAgencyId ?? ""} + timestamp: ${new Date().toISOString()} + next: validate_feedback + +validate_feedback: + switch: + - condition: ${!chatId || inferenceId === undefined} + next: return_validation_error + next: log_feedback + +return_validation_error: + status: 400 + return: "Invalid feedback payload. chatId, inferenceId are required fields." + next: end + +log_feedback: + log: "Classification feedback received for chat ${chatId}" + next: store_feedback + +store_feedback: + call: http.post + args: + url: "[#GLOBAL_CLASSIFIER_RESQL]/store-classification-feedback" + body: + chatId: ${chatId} + inferenceId: ${inferenceId} + actualAgencyId: ${actualAgencyId} + result: store_result + error: handle_storage_error + next: check_storage_result + +check_storage_result: + switch: + - condition: ${200 <= store_result.response.statusCodeValue && store_result.response.statusCodeValue < 300} + next: process_feedback_for_model + next: return_storage_error + +process_feedback_for_model: + log: "Feedback stored successfully and will be used for model improvement" + next: return_success + +handle_storage_error: + log: "Error storing feedback: ${store_result.error}" + next: return_storage_error + +return_storage_error: + status: 500 + return: "Failed to store feedback. Please try again later." + next: end + +return_success: + status: 200 + return: "Feedback received and processed successfully" + next: end + diff --git a/DSL/Ruuter.public/global-classifier/POST/chats/incoming.yml b/DSL/Ruuter.public/global-classifier/POST/chats/incoming.yml new file mode 100644 index 00000000..7c4a2363 --- /dev/null +++ b/DSL/Ruuter.public/global-classifier/POST/chats/incoming.yml @@ -0,0 +1,68 @@ +declaration: + call: declare + version: 0.1 + description: "Global classifier service to receive incoming chat messages and classify them." + method: post + accepts: json + returns: json + namespace: global-classifier + allowlist: + body: + - field: chatId + type: string + description: "Chat session identifier" + - field: message + type: string + description: "The user message content to classify" + - field: authorId + type: string + description: "Buerokrat chat bot ID" + - field: conversationHistory + type: json + description: "Previous messages in the conversation for context" + - field: url + type: string + description: "Additional url that might help with classification" + +extractRequestData: + assign: + chatId: ${incoming.body.chatId} + message: ${incoming.body.message ?? ""} + authorId: ${incoming.body.authorId ?? ""} + conversationHistory: ${incoming.body.conversationHistory ?? []} + url: ${incoming.body.url} + next: log_classification_request + +log_classification_request: + log: "Classification request received for chat ${chatId}: ${message}" + next: forward_to_classifier_service + +forward_to_classifier_service: + call: http.post + args: + url: "[#CHATBOT_CLASSIFIER_SERVICE]/incoming-conversation" + body: + chatId: ${chatId} + message: ${message} + authorId: ${authorId} + conversationHistory: ${conversationHistory} + url: ${url} + timestamp: ${new Date().toISOString()} + result: classifier_result + error: return_error + next: process_classification_result + +process_classification_result: + switch: + - condition: ${classifier_result.response.body.suggestedBot != null} + next: return_suggestion + +return_suggestion: + status: 200 + return: ${classifier_result.response.body} + next: end + +return_error: + status: 500 + return: "Classification service unavailable" + next: end diff --git a/README.md b/README.md index 90b787dc..78f8a709 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,168 @@ -# Global Classifier +# Global Classifier +## Setup and Installation +TODO - Exact setup process will be updated here when the initial builds are ready to deploy -*Introduction* -*Project Overview* +## Contributing -*Primary Modules* +This section outlines the guidelines for contributing to the Global Classifier project. Please read through these before submitting any changes. -*Architecture* +### Folder Structure -*Setup and Installation* +The project is organized into several key directories to maintain clarity and modularity: -*Usage* +* `configs/`: Holds global configuration files essential for different parts of the project. +* `docs/`: Contains all project documentation, including architectural diagrams (e.g., `classifier-architecture.drawio`), setup guides, technical explanations, and usage manuals. +* `DSL/`: Contains components related to DSLs belonging different to BYK stack services. +* `GUI/`: Contains the source code, assets, and build configurations for the project's Graphical User Interface. +* `local-classifier/`: A copy of the local-classifier repo for module re-use purposes. Will be discarded after initial release +* `src/`: Contains the core source code for the Global Classifier. This is further divided into modules for specific functionalities like: + * `dataset-generation/`: Scripts and tools for creating and preparing datasets. + * `inference/`: Code related to running model predictions. + * `model-training/`: Scripts and notebooks for training machine learning models. + * `tests/`: Unit, integration, or end-to-end tests for the `src/` components. -*Contributing* +Understanding this structure will help you locate relevant files and understand the project's architecture. -*License* +### Linting with Ruff +We use **Ruff** for linting Python code to ensure consistency and catch potential errors early. Ruff is an extremely fast Python linter and formatter, written in Rust. +**How Ruff Works (Example):** + +Consider the following Python code snippet which has a few style issues: + +```python +import os,sys # Multiple imports on one line + +def process_data(data, unused_param): # Unused function parameter + print ("Processing") # Print statement with extra space + if data is not None: + return True + else: + return False +``` + +When you run Ruff on this code (e.g., `ruff check .` or `ruff format . --check`), it will flag these issues: + +* An error for multiple imports on one line (`import os,sys`). Ruff would suggest `import os; import sys` or separate lines. +* An error for the `unused_param` not being used within the `process_data` function. +* Formatting issues might also be flagged if `ruff format` is used or its rules are enabled in `ruff check`. + +All Python contributions must be free of Ruff linting errors. You can check your code by running `ruff check .` and `ruff format .` in the relevant directory. + +### Package Management with uv + +This project uses **uv** as the primary package manager for Python dependencies. `uv` is a fast Python package installer and resolver, designed as a drop-in replacement for `pip` and `pip-tools`. + +You will typically use `uv` to manage virtual environments and install dependencies listed in `requirements.txt` files found within various modules (especially in the `local-classifier/` subdirectories and `src/`). + +Example command to create a virtual environment and install dependencies for a module: +```bash +uv venv # Create a virtual environment in .venv +uv pip install -r requirements.txt # Install dependencies +``` +Ensure your development environment is set up using `uv` for consistency. +If suppose you have already created your environment using any other framework like `conda` or `venv`, then simply create a new `uv` project and copy your existing code into the project while making sure no path references are broken. + + + +### Build Process and Code Quality + +To maintain a high standard of code quality and ensure project stability, the following practices are enforced: + +* **Ruff Linting is Mandatory:** All submitted Python code must pass Ruff linting checks. +* **Build Success:** Automated builds (e.g., via GitHub Actions) will only succeed if all checks, including Ruff linting, pass. Pull requests with failing checks will not be merged. + +Please run Ruff locally to check your code before pushing changes or creating a pull request. This helps streamline the review process and maintain a clean codebase. + + + +## Branch Management Strategy + +The project follows a three-tier branching workflow to streamline development, testing, and integration. + +### Branch Definitions + +- **wip (work in progress)**: Primary branch for ongoing work. All new features and fixes are merged here first. +- **testing**: Integration branch where code from **WIP** is validated by automated tests and QA. +- **dev**: Development-ready branch. Code that passes testing is merged here for further staging or release processes. + +### Contribution Process + +1. Fork the repository and clone it locally. +2. Create a new feature/fix branch based off `wip`. +3. Make your changes, run Ruff linting and formatting, commit your changes, and ensure all checks pass. +4. Push your branch to the remote and open a Pull Request targeting `wip`. +5. After review approval, maintainers merge your changes into `testing`. +6. Automated tests and QA are executed on `testing`. +7. Once testing is successful, maintainers merge `testing` into `dev`. +8. From `dev`, code may proceed through further release pipelines or staging environments. + +## Testing Requirements + +#### Python Unit Testing + +All Python modules in this project require comprehensive unit tests. Follow these guidelines when writing tests: + +1. **Test Framework**: Use `pytest` for all Python unit tests. +2. **Test Location**: Place tests in the `src/tests/` directory, mirroring the structure of the module being tested. +3. **Naming Convention**: Name test files with the `test_` prefix (e.g., `test_classifier.py`). +4. **Coverage**: Aim for at least 80% code coverage for all modules. +5. **Test Isolation**: Each test should be independent and not rely on the state of other tests. + +Example of a well-structured test: + +```python +import pytest +from src.inference.classifier import classify_text + +def test_classify_text_empty_input(): + """Test classification behavior with empty input.""" + result = classify_text("") + assert result == "unknown" + +def test_classify_text_valid_input(): + """Test classification with valid sample text.""" + sample = "This is a sample technical query about databases." + result = classify_text(sample) + assert result in ["database", "technical"] +``` + +#### Frontend Testing with Playwright + +All frontend components in the GUI directory require automated tests using Playwright: + +1. **Test Directory**: Place Playwright tests in `GUI/tests/`. +2. **Coverage Requirements**: Tests must cover: + - All critical user flows + - Component rendering + - State management + - Error handling scenarios + +3. **Multi-browser Testing**: Tests should run against at least two majors browsers (Chrome and Firefox). + +Example Playwright test structure: + +```typescript +import { test, expect } from '@playwright/test'; + +test.describe('Classifier UI', () => { + test('should display classification results correctly', async ({ page }) => { + await page.goto('/classifier'); + await page.fill('#input-text', 'Sample query about Azure services'); + await page.click('#classify-button'); + + // Check if results appear + const results = await page.locator('.classification-results'); + await expect(results).toBeVisible(); + + // Verify correct classification appears + const category = await page.locator('.category-label').textContent(); + expect(['cloud', 'azure']).toContain(category); + }); +}); +``` + +All tests must pass before PR approval and merge into the `wip` branch. diff --git a/constants.ini b/constants.ini new file mode 100644 index 00000000..3261da6d --- /dev/null +++ b/constants.ini @@ -0,0 +1,15 @@ +[DSL] +GLOBAL_CLASSIFIER_RUUTER_PUBLIC=http://ruuter-public:8086/global-classifier +GLOBAL_CLASSIFIER_RUUTER_PRIVATE=http://ruuter-private:8088/global-classifier +GLOBAL_CLASSIFIER_DMAPPER=http://data-mapper:3000 +GLOBAL_CLASSIFIER_RESQL=http://resql:8082/global-classifier +GLOBAL_CLASSIFIER_PROJECT_LAYER=global-classifier +GLOBAL_CLASSIFIER_TIM=http://tim:8085 +GLOBAL_CLASSIFIER_CRON_MANAGER=http://cron-manager:9010 +GLOBAL_CLASSIFIER_FILE_HANDLER=http://file-handler:8000 +GLOBAL_CLASSIFIER_NOTIFICATIONS=http://notifications-node:4040 +GLOBAL_CLASSIFIER_ANONYMIZER=http://anonymizer:8010 +GLOBAL_CLASSIFIER_MODEL_INFERENCE=http://model-inference:8003 +DOMAIN=localhost +DB_PASSWORD=dbadmin +CHATBOT_CLASSIFIER_SERVICE=http://classifier-service:8090 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..2e7412c0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,180 @@ +services: + ruuter-public: + container_name: ruuter-public + image: ruuter + environment: + - application.cors.allowedOrigins=http://localhost:8086,http://localhost:3001,http://localhost:3003,http://localhost:3004,http://localhost:8080,http://localhost:8000,http://localhost:8090 + - application.httpCodesAllowList=200,201,202,204,400,401,403,500 + - application.internalRequests.allowedIPs=127.0.0.1 + - application.logging.displayRequestContent=true + - application.logging.displayResponseContent=true + - application.logging.printStackTrace=true + - application.internalRequests.disabled=true + - server.port=8086 + volumes: + - ./DSL/Ruuter.public:/DSL + - ./constants.ini:/app/constants.ini + ports: + - 8086:8086 + networks: + bykstack: + ipv4_address: 172.25.0.2 + cpus: "0.5" + mem_limit: "512M" + + ruuter-private: + container_name: ruuter-private + image: ruuter + environment: + - application.cors.allowedOrigins=http://localhost:3001,http://localhost:8088,http://localhost:3002,http://localhost:3004,http://localhost:8000 + - application.httpCodesAllowList=200,201,202,400,401,403,500 + - application.internalRequests.allowedIPs=127.0.0.1 + - application.logging.displayRequestContent=true + - application.logging.displayResponseContent=true + - application.logging.printStackTrace=true + - application.internalRequests.disabled=true + - server.port=8088 + volumes: + - ./DSL/Ruuter.private:/DSL + - ./constants.ini:/app/constants.ini + ports: + - 8088:8088 + networks: + bykstack: + ipv4_address: 172.25.0.3 + cpus: "0.5" + mem_limit: "512M" + + # data-mapper: + # container_name: data-mapper + # image: data-mapper + # environment: + # - PORT=3000 + # - CONTENT_FOLDER=/data + # volumes: + # - ./DSL:/data + # - ./DSL/DMapper/classifier/hbs:/workspace/app/views/classifier + # - ./DSL/DMapper/classifier/lib:/workspace/app/lib + # ports: + # - 3000:3000 + # networks: + # bykstack: + # ipv4_address: 172.25.0.4 + + tim: + container_name: tim + image: tim + depends_on: + - tim-postgresql + environment: + - SECURITY_ALLOWLIST_JWT=ruuter-private,ruuter-public,data-mapper,resql,tim,tim-postgresql,chat-widget,authentication-layer,127.0.0.1,::1 + - KEY_PASS=ppjjpp + ports: + - 8085:8085 + networks: + bykstack: + ipv4_address: 172.25.0.5 + extra_hosts: + - "host.docker.internal:host-gateway" + cpus: "0.5" + mem_limit: "512M" + + tim-postgresql: + container_name: tim-postgresql + image: postgres:14.1 + environment: + - POSTGRES_USER=tim + - POSTGRES_PASSWORD=123 + - POSTGRES_DB=tim + - POSTGRES_HOST_AUTH_METHOD=trust + volumes: + - ./tim-db:/var/lib/postgresql/data + ports: + - 9876:5432 + networks: + bykstack: + ipv4_address: 172.25.0.6 + + # authentication-layer: + # container_name: authentication-layer + # image: authentication-layer + # ports: + # - 3004:3004 + # networks: + # bykstack: + # ipv4_address: 172.25.0.8 + + resql: + container_name: resql + image: resql + depends_on: + - users_db + environment: + - sqlms.datasources.[0].name=byk + - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://users_db:5432/global-classifier #For LocalDb Use + # sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://171.22.247.13:5433/byk?sslmode=require + - sqlms.datasources.[0].username=postgres + - sqlms.datasources.[0].password=dbadmin + - logging.level.org.springframework.boot=INFO + ports: + - 8082:8082 + volumes: + - ./DSL/Resql:/DSL + networks: + bykstack: + ipv4_address: 172.25.0.9 + + users_db: + container_name: users_db + image: postgres:14.1 + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=dbadmin + - POSTGRES_DB=global-classifier + ports: + - 5433:5432 + volumes: + - ~/buerokratt_classifier/db_files:/var/lib/postgresql/data + + networks: + bykstack: + ipv4_address: 172.25.0.10 + restart: always + + init: + image: busybox + command: ["sh", "-c", "chmod -R 777 /shared && chmod -R 777 /app/model_trainer"] + volumes: + - shared-volume:/shared + - ./model_trainer:/app/model_trainer + networks: + bykstack: + ipv4_address: 172.25.0.12 + + classifier-service: + container_name: classifier-service + build: + context: ./src/classifier-service # Create this folder with a Dockerfile and the Node.js code + ports: + - "8090:8090" + networks: + bykstack: + ipv4_address: 172.25.0.27 + volumes: + - ./src/classifier-service:/app + environment: + - NODE_ENV=development + restart: always + +volumes: + shared-volume: + opensearch-data: + +networks: + bykstack: + name: bykstack + driver: bridge + ipam: + config: + - subnet: 172.25.0.0/27 + gateway: 172.25.0.1 \ No newline at end of file diff --git a/src/classifier-service/.gitignore b/src/classifier-service/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/src/classifier-service/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/src/classifier-service/Dockerfile b/src/classifier-service/Dockerfile new file mode 100644 index 00000000..59aacadd --- /dev/null +++ b/src/classifier-service/Dockerfile @@ -0,0 +1,12 @@ +FROM node:18-alpine + +WORKDIR /app + +COPY package.json . +RUN npm install + +COPY . . + +EXPOSE 8090 + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/src/classifier-service/package-lock.json b/src/classifier-service/package-lock.json new file mode 100644 index 00000000..62848e16 --- /dev/null +++ b/src/classifier-service/package-lock.json @@ -0,0 +1,812 @@ +{ + "name": "classifier-service", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "express": "^5.1.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/src/classifier-service/package.json b/src/classifier-service/package.json new file mode 100644 index 00000000..f6fc735d --- /dev/null +++ b/src/classifier-service/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "express": "^5.1.0" + } +} diff --git a/src/classifier-service/server.js b/src/classifier-service/server.js new file mode 100644 index 00000000..b3c6a64d --- /dev/null +++ b/src/classifier-service/server.js @@ -0,0 +1,38 @@ +// This is a simple Node.js logger service that mocks the classification of incoming conversations. + +const express = require("express"); +const app = express(); +const port = 8090; + +app.use(express.json()); + +app.post("/incoming-conversation", (req, res) => { + console.log("Received classification request:"); + console.log(JSON.stringify(req.body, null, 2)); + + // Simple mock response when classifier is unable to classify the chat + // In a real-world scenario, this would be replaced with actual classification logic + + if (req.body.authorId === "1234") { + return res.json({ + message: "unable to classify chat", + operationSuccessful: false, + statusCode: 400, + }); + } else { + res.json({ + predictedAgencies: [ + { burokrattId: "2544", confidence: 0.8 }, + { burokrattId: "2824", confidence: 0.6 }, + { burokrattId: "2713", confidence: 0.2 }, + ], + message: "chat classified successfully", + operationSuccessful: true, + statusCode: 200, + }); + } +}); + +app.listen(port, () => { + console.log(`Classifier service listening at http://localhost:${port}`); +}); diff --git a/src/model-training/__init__.py b/src/model-training/__init__.py new file mode 100644 index 00000000..e69de29b